1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Make sure to not add autosave callbacks multiple times. [#3575 state:resolved]

This makes sure that, in a HABTM association, only one join record is craeted.
This commit is contained in:
Eloy Duran 2009-12-15 16:40:02 +01:00
parent ff508640e2
commit 9c771a9608
4 changed files with 65 additions and 17 deletions

View file

@ -155,6 +155,13 @@ module ActiveRecord
# Adds a validate and save callback for the association as specified by
# the +reflection+.
#
# For performance reasons, we don't check whether to validate at runtime,
# but instead only define the method and callback when needed. However,
# this can change, for instance, when using nested attributes. Since we
# don't want the callbacks to get defined multiple times, there are
# guards that check if the save or validation methods have already been
# defined before actually defining them.
def add_autosave_association_callbacks(reflection)
save_method = "autosave_associated_records_for_#{reflection.name}"
validation_method = "validate_associated_records_for_#{reflection.name}"
@ -162,18 +169,22 @@ module ActiveRecord
case reflection.macro
when :has_many, :has_and_belongs_to_many
unless method_defined?(save_method)
before_save :before_save_collection_association
define_method(save_method) { save_collection_association(reflection) }
# Doesn't use after_save as that would save associations added in after_create/after_update twice
after_create save_method
after_update save_method
end
if force_validation || (reflection.macro == :has_many && reflection.options[:validate] != false)
if !method_defined?(validation_method) &&
(force_validation || (reflection.macro == :has_many && reflection.options[:validate] != false))
define_method(validation_method) { validate_collection_association(reflection) }
validate validation_method
end
else
unless method_defined?(save_method)
case reflection.macro
when :has_one
define_method(save_method) { save_has_one_association(reflection) }
@ -182,8 +193,9 @@ module ActiveRecord
define_method(save_method) { save_belongs_to_association(reflection) }
before_save save_method
end
end
if force_validation
if !method_defined?(validation_method) && force_validation
define_method(validation_method) { validate_single_association(reflection) }
validate validation_method
end

View file

@ -235,7 +235,7 @@ module ActiveRecord
end
reflection.options[:autosave] = true
add_autosave_association_callbacks(reflection)
self.nested_attributes_options[association_name.to_sym] = options
if options[:reject_if] == :all_blank
@ -250,8 +250,6 @@ module ActiveRecord
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
end
}, __FILE__, __LINE__
add_autosave_association_callbacks(reflection)
else
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
end

View file

@ -31,11 +31,40 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
assert base.valid_keys_for_has_and_belongs_to_many_association.include?(:autosave)
end
def test_should_not_add_the_same_callbacks_multiple_times_for_has_one
assert_no_difference_when_adding_callbacks_twice_for Pirate, :ship
end
def test_should_not_add_the_same_callbacks_multiple_times_for_belongs_to
assert_no_difference_when_adding_callbacks_twice_for Ship, :pirate
end
def test_should_not_add_the_same_callbacks_multiple_times_for_has_many
assert_no_difference_when_adding_callbacks_twice_for Pirate, :birds
end
def test_should_not_add_the_same_callbacks_multiple_times_for_has_and_belongs_to_many
assert_no_difference_when_adding_callbacks_twice_for Pirate, :parrots
end
private
def base
ActiveRecord::Base
end
def assert_no_difference_when_adding_callbacks_twice_for(model, association_name)
reflection = model.reflect_on_association(association_name)
assert_no_difference "callbacks_for_model(#{model.name}).length" do
model.send(:add_autosave_association_callbacks, reflection)
end
end
def callbacks_for_model(model)
model.instance_variables.grep(/_callbacks$/).map do |ivar|
model.instance_variable_get(ivar)
end.flatten
end
end
class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase

View file

@ -371,6 +371,15 @@ module NestedAttributesOnACollectionAssociationTests
assert_respond_to @pirate, association_setter
end
def test_should_save_only_one_association_on_create
pirate = Pirate.create!({
:catchphrase => 'Arr',
association_getter => { 'foo' => { :name => 'Grace OMalley' } }
})
assert_equal 1, pirate.reload.send(@association_name).count
end
def test_should_take_a_hash_with_string_keys_and_assign_the_attributes_to_the_associated_models
@alternate_params[association_getter].stringify_keys!
@pirate.update_attributes @alternate_params