diff options
author | Andrew White <andyw@pixeltrix.co.uk> | 2012-06-10 16:23:34 +0100 |
---|---|---|
committer | Andrew White <andyw@pixeltrix.co.uk> | 2012-06-10 16:23:34 +0100 |
commit | c2e61aa61540802e5a5d9b54ff04b803d770eff6 (patch) | |
tree | 1b9ba45c96e8d30f12c63928e2b8e0c476221819 /activerecord | |
parent | 42259f69ae3fa2daacfb751dbc663adb14159e37 (diff) | |
download | rails-c2e61aa61540802e5a5d9b54ff04b803d770eff6.tar.gz rails-c2e61aa61540802e5a5d9b54ff04b803d770eff6.tar.bz2 rails-c2e61aa61540802e5a5d9b54ff04b803d770eff6.zip |
Ensure that mass assignment options are preserved
There are two possible scenarios where the @mass_assignment_options
instance variable can become corrupted:
1. If the assign_attributes doesn't complete correctly, then
subsequent calls to a nested attribute assignment method will use
whatever options were passed to the previous assign_attributes call.
2. With nested assign_attributes calls, the inner call will overwrite
the current options. This will only affect nested attributes as the
attribute hash is sanitized before any methods are called.
To fix this we save the current options in a local variable and then
restore these options in an ensure block.
Diffstat (limited to 'activerecord')
-rw-r--r-- | activerecord/lib/active_record/attribute_assignment.rb | 4 | ||||
-rw-r--r-- | activerecord/lib/active_record/core.rb | 19 | ||||
-rw-r--r-- | activerecord/test/cases/mass_assignment_security_test.rb | 22 | ||||
-rw-r--r-- | activerecord/test/models/person.rb | 21 |
4 files changed, 55 insertions, 11 deletions
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index bf9fe70b31..e0bf80142a 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -69,6 +69,7 @@ module ActiveRecord attributes = new_attributes.stringify_keys multi_parameter_attributes = [] nested_parameter_attributes = [] + previous_options = @mass_assignment_options @mass_assignment_options = options unless options[:without_protection] @@ -94,8 +95,9 @@ module ActiveRecord send("#{k}=", v) end - @mass_assignment_options = nil assign_multiparameter_attributes(multi_parameter_attributes) + ensure + @mass_assignment_options = previous_options end protected diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 1fa6c701bb..dbad561ca2 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -380,15 +380,16 @@ module ActiveRecord @attributes[pk] = nil unless @attributes.key?(pk) - @aggregation_cache = {} - @association_cache = {} - @attributes_cache = {} - @previously_changed = {} - @changed_attributes = {} - @readonly = false - @destroyed = false - @marked_for_destruction = false - @new_record = true + @aggregation_cache = {} + @association_cache = {} + @attributes_cache = {} + @previously_changed = {} + @changed_attributes = {} + @readonly = false + @destroyed = false + @marked_for_destruction = false + @new_record = true + @mass_assignment_options = nil end end end diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 2f98d3c646..1b5421c25e 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -878,4 +878,26 @@ class MassAssignmentSecurityNestedAttributesTest < ActiveRecord::TestCase assert_all_attributes(person.best_friends.first) end + def test_mass_assignment_options_are_reset_after_exception + person = NestedPerson.create!({ :first_name => 'David', :gender => 'm' }, :as => :admin) + person.create_best_friend!({ :first_name => 'Jeremy', :gender => 'm' }, :as => :admin) + + attributes = { :best_friend_attributes => { :comments => 'rides a sweet bike' } } + assert_raises(RuntimeError) { person.assign_attributes(attributes, :as => :admin) } + assert_equal 'm', person.best_friend.gender + + person.best_friend_attributes = { :gender => 'f' } + assert_equal 'm', person.best_friend.gender + end + + def test_mass_assignment_options_are_nested_correctly + person = NestedPerson.create!({ :first_name => 'David', :gender => 'm' }, :as => :admin) + person.create_best_friend!({ :first_name => 'Jeremy', :gender => 'm' }, :as => :admin) + + attributes = { :best_friend_first_name => 'Josh', :best_friend_attributes => { :gender => 'f' } } + person.assign_attributes(attributes, :as => :admin) + assert_equal 'Josh', person.best_friend.first_name + assert_equal 'f', person.best_friend.gender + end + end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index d5c0b351aa..33cd6020a1 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -88,6 +88,25 @@ class TightDescendant < TightPerson; end class RichPerson < ActiveRecord::Base self.table_name = 'people' - + has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures' end + +class NestedPerson < ActiveRecord::Base + self.table_name = 'people' + + attr_accessible :first_name, :best_friend_first_name, :best_friend_attributes + attr_accessible :first_name, :gender, :comments, :as => :admin + attr_accessible :best_friend_attributes, :best_friend_first_name, :as => :admin + + has_one :best_friend, :class_name => 'NestedPerson', :foreign_key => :best_friend_id + accepts_nested_attributes_for :best_friend, :update_only => true + + def comments=(new_comments) + raise RuntimeError + end + + def best_friend_first_name=(new_name) + assign_attributes({ :best_friend_attributes => { :first_name => new_name } }) + end +end
\ No newline at end of file |