diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 88e845fd3b..c5cf10f2df 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -463,7 +463,8 @@ def save_has_one_association(reflection) # If the record is new or it has changed, returns true. def _record_changed?(reflection, record, key) record.new_record? || - association_foreign_key_changed?(reflection, record, key) || + (association_foreign_key_changed?(reflection, record, key) || + inverse_polymorphic_association_changed?(reflection, record)) || record.will_save_change_to_attribute?(reflection.foreign_key) end @@ -473,6 +474,14 @@ def association_foreign_key_changed?(reflection, record, key) record._has_attribute?(reflection.foreign_key) && record._read_attribute(reflection.foreign_key) != key end + def inverse_polymorphic_association_changed?(reflection, record) + return false unless reflection.inverse_of&.polymorphic? + + class_name = record._read_attribute(reflection.inverse_of.foreign_type) + + reflection.active_record != record.class.polymorphic_class_for(class_name) + end + # Saves the associated record if it's new or :autosave is enabled. # # In addition, it will destroy the association if it was marked for destruction. diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 4ebf90a9ef..56f1a6a605 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -36,6 +36,9 @@ require "models/reply" require "models/attachment" require "models/translation" +require "models/chef" +require "models/cake_designer" +require "models/drink_designer" class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase def test_autosave_works_even_when_other_callbacks_update_the_parent_model @@ -1276,6 +1279,8 @@ def destroy(*args) end class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase + fixtures :chefs, :cake_designers, :drink_designers + self.use_transactional_tests = false unless supports_savepoints? def setup @@ -1416,6 +1421,34 @@ def test_mark_for_destruction_is_ignored_without_autosave_true assert_not_predicate ship, :valid? end + + def test_recognises_inverse_polymorphic_association_changes_with_same_foreign_key + chef_a = chefs(:gordon_ramsay) + chef_b = chefs(:marco_pierre_white) + + cake_designer_a = cake_designers(:flora) # id: 1 + cake_designer_a.update!(chef: chef_a) + cake_designer_b = cake_designers(:frosty) # id: 3 + cake_designer_b.update!(chef: chef_b) + + drink_designer_a = drink_designers(:turner) # id: 1 + drink_designer_b = drink_designers(:sparrow) # id: 2 + + swap_chefs(cake_designer_b, drink_designer_b) + assert_predicate cake_designer_b.reload.chef, :present? + assert_not_predicate drink_designer_b.reload.chef, :present? + + swap_chefs(cake_designer_a, drink_designer_a) + assert_predicate cake_designer_a.reload.chef, :present? + assert_not_predicate drink_designer_a.reload.chef, :present? + end + + private + def swap_chefs(cake_designer, drink_designer) + drink_designer.chef = cake_designer.chef + drink_designer.save! + cake_designer.save! + end end class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCase diff --git a/activerecord/test/fixtures/cake_designers.yml b/activerecord/test/fixtures/cake_designers.yml new file mode 100644 index 0000000000..9ba963816c --- /dev/null +++ b/activerecord/test/fixtures/cake_designers.yml @@ -0,0 +1,5 @@ +flora: + id: 1 + +frosty: + id: 2 diff --git a/activerecord/test/fixtures/chefs.yml b/activerecord/test/fixtures/chefs.yml new file mode 100644 index 0000000000..2291e86f14 --- /dev/null +++ b/activerecord/test/fixtures/chefs.yml @@ -0,0 +1,5 @@ +gordon_ramsay: + id: 1 + +marco_pierre_white: + id: 2 diff --git a/activerecord/test/fixtures/drink_designers.yml b/activerecord/test/fixtures/drink_designers.yml new file mode 100644 index 0000000000..aa50b45549 --- /dev/null +++ b/activerecord/test/fixtures/drink_designers.yml @@ -0,0 +1,5 @@ +turner: + id: 1 + +sparrow: + id: 3