Permalink
Browse files

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.
  • Loading branch information...
pixeltrix committed Jun 10, 2012
1 parent 42259f6 commit c2e61aa61540802e5a5d9b54ff04b803d770eff6
@@ -69,6 +69,7 @@ def assign_attributes(new_attributes, options = {})
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 @@ def assign_attributes(new_attributes, options = {})
send("#{k}=", v)
end
@mass_assignment_options = nil
assign_multiparameter_attributes(multi_parameter_attributes)
ensure
@mass_assignment_options = previous_options
end
protected
@@ -380,15 +380,16 @@ def init_internals
@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
@@ -878,4 +878,26 @@ def test_has_many_create_with_bang_without_protection
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
@@ -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

0 comments on commit c2e61aa

Please sign in to comment.