diff options
Diffstat (limited to 'actionpack/lib')
-rw-r--r-- | actionpack/lib/action_controller/integration.rb | 15 | ||||
-rw-r--r-- | actionpack/lib/action_controller/middlewares.rb | 1 | ||||
-rw-r--r-- | actionpack/lib/action_controller/rack_ext/parse_query.rb | 1 | ||||
-rw-r--r-- | actionpack/lib/action_controller/resources.rb | 35 | ||||
-rw-r--r-- | actionpack/lib/action_controller/test_process.rb | 23 | ||||
-rw-r--r-- | actionpack/lib/action_controller/url_encoded_pair_parser.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_view/helpers/form_helper.rb | 196 |
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 |