Skip to content

Commit

Permalink
Fixes rails#2415 by creating a new instance of the Model when saving …
Browse files Browse the repository at this point in the history
…attributes to that model and the associated attributes already exist. Tests included. [rails#2415 state:resolved]

Signed-off-by: José Valim <jose.valim@gmail.com>
  • Loading branch information
harking authored and josevalim committed Jun 23, 2010
1 parent cc53229 commit 7d2173e
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 21 deletions.
19 changes: 11 additions & 8 deletions activerecord/lib/active_record/nested_attributes.rb
Expand Up @@ -286,7 +286,9 @@ def assign_nested_attributes_for_one_to_one_association(association_name, attrib
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])

elsif attributes['id']
raise_nested_attributes_record_not_found(association_name, attributes['id'])
existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
self.send(association_name.to_s+'=', existing_record)

elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
Expand Down Expand Up @@ -356,11 +358,16 @@ def assign_nested_attributes_for_collection_association(association_name, attrib
unless reject_new_record?(association_name, attributes)
association.build(attributes.except(*UNASSIGNABLE_KEYS))
end

elsif existing_records.count == 0 #Existing record but not yet associated
existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])

elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
else
raise_nested_attributes_record_not_found(association_name, attributes['id'])

end
end
end
Expand All @@ -380,7 +387,7 @@ def has_destroy_flag?(hash)
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end

# Determines if a new record should be build by checking for
# Determines if a new record should be built by checking for
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
Expand All @@ -396,9 +403,5 @@ def call_reject_if(association_name, attributes)
end
end

def raise_nested_attributes_record_not_found(association_name, record_id)
reflection = self.class.reflect_on_association(association_name)
raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
end
end
28 changes: 15 additions & 13 deletions activerecord/test/cases/nested_attributes_test.rb
Expand Up @@ -175,12 +175,6 @@ def test_should_modify_an_existing_record_if_there_is_a_matching_id
assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
end

def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
@pirate.ship_attributes = { :id => 1234567890 }
end
end

def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' }

Expand Down Expand Up @@ -330,10 +324,13 @@ def test_should_modify_an_existing_record_if_there_is_a_matching_id
assert_equal 'Arr', @ship.pirate.catchphrase
end

def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}" do
@ship.pirate_attributes = { :id => 1234567890 }
end
def test_should_associate_with_record_if_parent_record_is_not_saved
@ship.destroy
@pirate = Pirate.create(:catchphrase => 'Arr')
@ship = Ship.new(:name => 'Nights Dirty Lightning', :pirate_attributes => { :id => @pirate.id, :catchphrase => @pirate.catchphrase})

assert_equal @ship.name, 'Nights Dirty Lightning'
assert_equal @pirate, @ship.pirate
end

def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
Expand Down Expand Up @@ -437,6 +434,11 @@ def test_should_take_a_hash_with_string_keys_and_assign_the_attributes_to_the_as
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
end

def test_should_assign_existing_children_if_parent_is_new
@pirate = Pirate.new({:catchphrase => "Don' botharr talkin' like one, savvy?"}.merge(@alternate_params))
assert_equal ['Grace OMalley', 'Privateers Greed'], [@pirate.send(@association_name)[0].name, @pirate.send(@association_name)[1].name]
end

def test_should_take_an_array_and_assign_the_attributes_to_the_associated_models
@pirate.send(association_setter, @alternate_params[association_getter].values)
@pirate.save
Expand Down Expand Up @@ -500,8 +502,8 @@ def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name]
end

def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
def test_should_not_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
assert_nothing_raised ActiveRecord::RecordNotFound do
@pirate.attributes = { association_getter => [{ :id => 1234567890 }] }
end
end
Expand Down Expand Up @@ -831,4 +833,4 @@ def setup
ShipPart.create!(:ship => @ship, :name => "Stern")
assert_no_queries { @ship.valid? }
end
end
end

0 comments on commit 7d2173e

Please sign in to comment.