aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/action_controller/integration.rb15
-rw-r--r--actionpack/lib/action_controller/middlewares.rb1
-rw-r--r--actionpack/lib/action_controller/rack_ext/parse_query.rb1
-rw-r--r--actionpack/lib/action_controller/resources.rb35
-rw-r--r--actionpack/lib/action_controller/test_process.rb23
-rw-r--r--actionpack/lib/action_controller/url_encoded_pair_parser.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb196
7 files changed, 240 insertions, 33 deletions
diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb
index 163ba84a3e..a0e894108d 100644
--- a/actionpack/lib/action_controller/integration.rb
+++ b/actionpack/lib/action_controller/integration.rb
@@ -26,6 +26,9 @@ module ActionController
# The status message that accompanied the status code of the last request.
attr_reader :status_message
+ # The body of the last request.
+ attr_reader :body
+
# The URI of the last request.
attr_reader :path
@@ -308,7 +311,11 @@ module ActionController
ActionController::Base.clear_last_instantiation!
- app = Rack::Lint.new(@application)
+ app = @application
+ # Rack::Lint doesn't accept String headers or bodies in Ruby 1.9
+ unless RUBY_VERSION >= '1.9.0' && Rack.release <= '0.9.0'
+ app = Rack::Lint.new(app)
+ end
status, headers, body = app.call(env)
@request_count += 1
@@ -326,7 +333,11 @@ module ActionController
end
@body = ""
- body.each { |part| @body << part }
+ if body.is_a?(String)
+ @body << body
+ else
+ body.each { |part| @body << part }
+ end
if @controller = ActionController::Base.last_instantiation
@request = @controller.request
diff --git a/actionpack/lib/action_controller/middlewares.rb b/actionpack/lib/action_controller/middlewares.rb
index f9cfc2b18e..8ea1b5c7ce 100644
--- a/actionpack/lib/action_controller/middlewares.rb
+++ b/actionpack/lib/action_controller/middlewares.rb
@@ -19,3 +19,4 @@ end
use "ActionController::RewindableInput"
use "ActionController::ParamsParser"
use "Rack::MethodOverride"
+use "Rack::Head"
diff --git a/actionpack/lib/action_controller/rack_ext/parse_query.rb b/actionpack/lib/action_controller/rack_ext/parse_query.rb
index 2f21a57770..b1acef8e72 100644
--- a/actionpack/lib/action_controller/rack_ext/parse_query.rb
+++ b/actionpack/lib/action_controller/rack_ext/parse_query.rb
@@ -10,7 +10,6 @@ module Rack
def parse_query(qs, d = '&;')
qs = qs.dup
qs.chop! if qs[-1] == 0
- qs.gsub!(/&_=$/, '')
parse_query_without_ajax_body_cleanup(qs, d)
end
module_function :parse_query
diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb
index e8988aa737..3af21967df 100644
--- a/actionpack/lib/action_controller/resources.rb
+++ b/actionpack/lib/action_controller/resources.rb
@@ -42,7 +42,7 @@ module ActionController
#
# Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
module Resources
- INHERITABLE_OPTIONS = :namespace, :shallow, :actions
+ INHERITABLE_OPTIONS = :namespace, :shallow
class Resource #:nodoc:
DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
@@ -119,7 +119,7 @@ module ActionController
end
def has_action?(action)
- !DEFAULT_ACTIONS.include?(action) || @options[:actions].nil? || @options[:actions].include?(action)
+ !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
end
protected
@@ -135,24 +135,29 @@ module ActionController
end
def set_allowed_actions
- only = @options.delete(:only)
- except = @options.delete(:except)
+ only, except = @options.values_at(:only, :except)
+ @allowed_actions ||= {}
- if only && except
- raise ArgumentError, 'Please supply either :only or :except, not both.'
- elsif only == :all || except == :none
- options[:actions] = DEFAULT_ACTIONS
+ if only == :all || except == :none
+ only = nil
+ except = []
elsif only == :none || except == :all
- options[:actions] = []
- elsif only
- options[:actions] = DEFAULT_ACTIONS & Array(only).map(&:to_sym)
+ only = []
+ except = nil
+ end
+
+ if only
+ @allowed_actions[:only] = Array(only).map(&:to_sym)
elsif except
- options[:actions] = DEFAULT_ACTIONS - Array(except).map(&:to_sym)
- else
- # leave options[:actions] alone
+ @allowed_actions[:except] = Array(except).map(&:to_sym)
end
end
+ def action_allowed?(action)
+ only, except = @allowed_actions.values_at(:only, :except)
+ (!only || only.include?(action)) && (!except || !except.include?(action))
+ end
+
def set_prefixes
@path_prefix = options.delete(:path_prefix)
@name_prefix = options.delete(:name_prefix)
@@ -403,8 +408,6 @@ module ActionController
# # --> POST /posts/1/comments (maps to the CommentsController#create action)
# # --> PUT /posts/1/comments/1 (fails)
#
- # The <tt>:only</tt> and <tt>:except</tt> options are inherited by any nested resource(s).
- #
# If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
#
# Examples:
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index ea17363c47..4b5fc3a3c1 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -15,7 +15,7 @@ module ActionController #:nodoc:
end
def reset_session
- @session = TestSession.new
+ @session.reset
end
# Wraps raw_post in a StringIO.
@@ -284,9 +284,13 @@ module ActionController #:nodoc:
attr_accessor :session_id
def initialize(attributes = nil)
- @session_id = ''
- attributes ||= {}
- replace(attributes.stringify_keys)
+ reset_session_id
+ replace_attributes(attributes)
+ end
+
+ def reset
+ reset_session_id
+ replace_attributes({ })
end
def data
@@ -322,6 +326,17 @@ module ActionController #:nodoc:
def close
ActiveSupport::Deprecation.warn('sessions should no longer be closed', caller)
end
+
+ private
+
+ def reset_session_id
+ @session_id = ''
+ end
+
+ def replace_attributes(attributes = nil)
+ attributes ||= {}
+ replace(attributes.stringify_keys)
+ end
end
# Essentially generates a modified Tempfile object similar to the object
diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
index 57594c4259..b17b8a31aa 100644
--- a/actionpack/lib/action_controller/url_encoded_pair_parser.rb
+++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
@@ -46,7 +46,7 @@ module ActionController
when Array
value.map { |v| get_typed_value(v) }
when Hash
- if value.has_key?(:tempfile) && value[:filename].any?
+ if value.has_key?(:tempfile) && !value[:filename].blank?
upload = value[:tempfile]
upload.extend(UploadedFile)
upload.original_path = value[:filename]
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index a85751c657..2ac2427884 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -269,10 +269,12 @@ module ActionView
options[:url] ||= polymorphic_path(object_or_array)
end
- # Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes
- # fields_for suitable for specifying additional model objects in the same form:
+ # Creates a scope around a specific model object like form_for, but
+ # doesn't create the form tags themselves. This makes fields_for suitable
+ # for specifying additional model objects in the same form.
+ #
+ # === Generic Examples
#
- # ==== Examples
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
# First name: <%= person_form.text_field :first_name %>
# Last name : <%= person_form.text_field :last_name %>
@@ -282,20 +284,166 @@ module ActionView
# <% end %>
# <% end %>
#
- # ...or if you have an object that needs to be represented as a different parameter, like a Client that acts as a Person:
+ # ...or if you have an object that needs to be represented as a different
+ # parameter, like a Client that acts as a Person:
#
# <% fields_for :person, @client do |permission_fields| %>
# Admin?: <%= permission_fields.check_box :admin %>
# <% end %>
#
- # ...or if you don't have an object, just a name of the parameter
+ # ...or if you don't have an object, just a name of the parameter:
#
# <% fields_for :person do |permission_fields| %>
# Admin?: <%= permission_fields.check_box :admin %>
# <% end %>
#
- # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base,
- # like FormOptionHelper#collection_select and DateHelper#datetime_select.
+ # Note: This also works for the methods in FormOptionHelper and
+ # DateHelper that are designed to work with an object as base, like
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ #
+ # === Nested Attributes Examples
+ #
+ # When the object belonging to the current scope has a nested attribute
+ # writer for a certain attribute, fields_for will yield a new scope
+ # for that attribute. This allows you to create forms that set or change
+ # the attributes of a parent object and its associations in one go.
+ #
+ # Nested attribute writers are normal setter methods named after an
+ # association. The most common way of defining these writers is either
+ # with +accepts_nested_attributes_for+ in a model definition or by
+ # defining a method with the proper name. For example: the attribute
+ # writer for the association <tt>:address</tt> is called
+ # <tt>address_attributes=</tt>.
+ #
+ # Whether a one-to-one or one-to-many style form builder will be yielded
+ # depends on whether the normal reader method returns a _single_ object
+ # or an _array_ of objects.
+ #
+ # ==== One-to-one
+ #
+ # Consider a Person class which returns a _single_ Address from the
+ # <tt>address</tt> reader method and responds to the
+ # <tt>address_attributes=</tt> writer method:
+ #
+ # class Person
+ # def address
+ # @address
+ # end
+ #
+ # def address_attributes=(attributes)
+ # # Process the attributes hash
+ # end
+ # end
+ #
+ # This model can now be used with a nested fields_for, like so:
+ #
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # ...
+ # <% person_form.fields_for :address do |address_fields| %>
+ # Street : <%= address_fields.text_field :street %>
+ # Zip code: <%= address_fields.text_field :zip_code %>
+ # <% end %>
+ # <% end %>
+ #
+ # When address is already an association on a Person you can use
+ # +accepts_nested_attributes_for+ to define the writer method for you:
+ #
+ # class Person < ActiveRecord::Base
+ # has_one :address
+ # accepts_nested_attributes_for :address
+ # end
+ #
+ # If you want to destroy the associated model through the form, you have
+ # to enable it first using the <tt>:allow_destroy</tt> option for
+ # +accepts_nested_attributes_for+:
+ #
+ # class Person < ActiveRecord::Base
+ # has_one :address
+ # accepts_nested_attributes_for :address, :allow_destroy => true
+ # end
+ #
+ # Now, when you use a form element with the <tt>_delete</tt> parameter,
+ # with a value that evaluates to +true+, you will destroy the associated
+ # model (eg. 1, '1', true, or 'true'):
+ #
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # ...
+ # <% person_form.fields_for :address do |address_fields| %>
+ # ...
+ # Delete: <%= address_fields.check_box :_delete %>
+ # <% end %>
+ # <% end %>
+ #
+ # ==== One-to-many
+ #
+ # Consider a Person class which returns an _array_ of Project instances
+ # from the <tt>projects</tt> reader method and responds to the
+ # <tt>projects_attributes=</tt> writer method:
+ #
+ # class Person
+ # def projects
+ # [@project1, @project2]
+ # end
+ #
+ # def projects_attributes=(attributes)
+ # # Process the attributes hash
+ # end
+ # end
+ #
+ # This model can now be used with a nested fields_for. The block given to
+ # the nested fields_for call will be repeated for each instance in the
+ # collection:
+ #
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # ...
+ # <% person_form.fields_for :projects do |project_fields| %>
+ # <% if project_fields.object.active? %>
+ # Name: <%= project_fields.text_field :name %>
+ # <% end %>
+ # <% end %>
+ # <% end %>
+ #
+ # It's also possible to specify the instance to be used:
+ #
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # ...
+ # <% @person.projects.each do |project| %>
+ # <% if project.active? %>
+ # <% person_form.fields_for :projects, project do |project_fields| %>
+ # Name: <%= project_fields.text_field :name %>
+ # <% end %>
+ # <% end %>
+ # <% end %>
+ # <% end %>
+ #
+ # When projects is already an association on Person you can use
+ # +accepts_nested_attributes_for+ to define the writer method for you:
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :projects
+ # accepts_nested_attributes_for :projects
+ # end
+ #
+ # If you want to destroy any of the associated models through the
+ # form, you have to enable it first using the <tt>:allow_destroy</tt>
+ # option for +accepts_nested_attributes_for+:
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :projects
+ # accepts_nested_attributes_for :projects, :allow_destroy => true
+ # end
+ #
+ # This will allow you to specify which models to destroy in the
+ # attributes hash by adding a form element for the <tt>_delete</tt>
+ # parameter with a value that evaluates to +true+
+ # (eg. 1, '1', true, or 'true'):
+ #
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # ...
+ # <% person_form.fields_for :projects do |project_fields| %>
+ # Delete: <%= project_fields.check_box :_delete %>
+ # <% end %>
+ # <% end %>
def fields_for(record_or_name_or_array, *args, &block)
raise ArgumentError, "Missing block" unless block_given?
options = args.extract_options!
@@ -760,7 +908,11 @@ module ActionView
case record_or_name_or_array
when String, Symbol
- name = "#{object_name}#{index}[#{record_or_name_or_array}]"
+ if nested_attributes_association?(record_or_name_or_array)
+ return fields_for_with_nested_attributes(record_or_name_or_array, args, block)
+ else
+ name = "#{object_name}#{index}[#{record_or_name_or_array}]"
+ end
when Array
object = record_or_name_or_array.last
name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
@@ -802,6 +954,32 @@ module ActionView
def objectify_options(options)
@default_options.merge(options.merge(:object => @object))
end
+
+ def nested_attributes_association?(association_name)
+ @object.respond_to?("#{association_name}_attributes=")
+ end
+
+ def fields_for_with_nested_attributes(association_name, args, block)
+ name = "#{object_name}[#{association_name}_attributes]"
+ association = @object.send(association_name)
+
+ if association.is_a?(Array)
+ children = args.first.respond_to?(:new_record?) ? [args.first] : association
+
+ children.map do |child|
+ child_name = "#{name}[#{ child.new_record? ? new_child_id : child.id }]"
+ @template.fields_for(child_name, child, *args, &block)
+ end.join
+ else
+ @template.fields_for(name, association, *args, &block)
+ end
+ end
+
+ def new_child_id
+ value = (@child_counter ||= 1)
+ @child_counter += 1
+ "new_#{value}"
+ end
end
end
@@ -809,4 +987,4 @@ module ActionView
cattr_accessor :default_form_builder
self.default_form_builder = ::ActionView::Helpers::FormBuilder
end
-end
+end \ No newline at end of file