Skip to content

Commit 56fbc63

Browse files
authored
Merge pull request rails#52333 from joshuay03/fix-52332
[Fix rails#52332] Handle record changed case in `#save_has_one_association`'s inverse `belongs_to` association updated check
2 parents f74dc0d + 9920ad3 commit 56fbc63

File tree

2 files changed

+46
-16
lines changed

2 files changed

+46
-16
lines changed

activerecord/lib/active_record/autosave_association.rb

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -436,9 +436,6 @@ def save_has_one_association(reflection)
436436
record = association && association.load_target
437437
return unless record && !record.destroyed?
438438

439-
inverse_belongs_to_association = inverse_belongs_to_association_for(reflection, record)
440-
return if inverse_belongs_to_association && inverse_belongs_to_association.updated?
441-
442439
autosave = reflection.options[:autosave]
443440

444441
if autosave && record.marked_for_destruction?
@@ -447,22 +444,27 @@ def save_has_one_association(reflection)
447444
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
448445
primary_key_value = primary_key.map { |key| _read_attribute(key) }
449446

450-
if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
451-
unless reflection.through_reflection
452-
foreign_key = Array(reflection.foreign_key)
453-
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
447+
return unless (autosave && record.changed_for_autosave?) || (record_changed = _record_changed?(reflection, record, primary_key_value))
454448

455-
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
456-
association_id = _read_attribute(primary_key)
457-
record[foreign_key] = association_id unless record[foreign_key] == association_id
458-
end
459-
association.set_inverse_instance(record)
460-
end
449+
unless record_changed
450+
inverse_belongs_to_association = inverse_belongs_to_association_for(reflection, record)
451+
return if inverse_belongs_to_association && inverse_belongs_to_association.updated?
452+
end
453+
454+
unless reflection.through_reflection
455+
foreign_key = Array(reflection.foreign_key)
456+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
461457

462-
saved = record.save(validate: !autosave)
463-
raise ActiveRecord::Rollback if !saved && autosave
464-
saved
458+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
459+
association_id = _read_attribute(primary_key)
460+
record[foreign_key] = association_id unless record[foreign_key] == association_id
461+
end
462+
association.set_inverse_instance(record)
465463
end
464+
465+
saved = record.save(validate: !autosave)
466+
raise ActiveRecord::Rollback if !saved && autosave
467+
saved
466468
end
467469
end
468470

activerecord/test/cases/autosave_association_test.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,17 @@ def test_callbacks_on_child_when_parent_autosaves_child
314314
assert_equal 1, eye.iris.after_save_callbacks_counter
315315
end
316316

317+
def test_callbacks_on_child_when_parent_autosaves_child_twice
318+
eye = Eye.create!(iris: Iris.new)
319+
eye.update!(iris: Iris.new)
320+
assert_equal 1, eye.iris.before_validation_callbacks_counter
321+
assert_equal 1, eye.iris.before_create_callbacks_counter
322+
assert_equal 1, eye.iris.before_save_callbacks_counter
323+
assert_equal 1, eye.iris.after_validation_callbacks_counter
324+
assert_equal 1, eye.iris.after_create_callbacks_counter
325+
assert_equal 1, eye.iris.after_save_callbacks_counter
326+
end
327+
317328
def test_callbacks_on_child_when_parent_autosaves_polymorphic_child_with_inverse_of
318329
drink_designer = DrinkDesigner.create!(chef: ChefWithPolymorphicInverseOf.new)
319330
assert_equal 1, drink_designer.chef.before_validation_callbacks_counter
@@ -334,6 +345,17 @@ def test_callbacks_on_child_when_child_autosaves_parent
334345
assert_equal 1, iris.after_save_callbacks_counter
335346
end
336347

348+
def test_callbacks_on_child_when_child_autosaves_parent_twice
349+
iris = Iris.create!(eye: Eye.new)
350+
iris.update!(eye: Eye.new)
351+
assert_equal 2, iris.before_validation_callbacks_counter
352+
assert_equal 1, iris.before_create_callbacks_counter
353+
assert_equal 2, iris.before_save_callbacks_counter
354+
assert_equal 2, iris.after_validation_callbacks_counter
355+
assert_equal 1, iris.after_create_callbacks_counter
356+
assert_equal 2, iris.after_save_callbacks_counter
357+
end
358+
337359
def test_callbacks_on_child_when_polymorphic_child_with_inverse_of_autosaves_parent
338360
chef = ChefWithPolymorphicInverseOf.create!(employable: DrinkDesigner.new)
339361
assert_equal 1, chef.before_validation_callbacks_counter
@@ -1812,6 +1834,12 @@ def test_should_save_with_non_nullable_foreign_keys
18121834
child.save!
18131835
assert_equal child.reload.post, parent.reload
18141836
end
1837+
1838+
def test_should_save_if_previously_saved
1839+
ship = Ship.create(name: "Nights Dirty Lightning", pirate: Pirate.new(catchphrase: "Arrrr"))
1840+
ship.create_pirate(catchphrase: "Savvy?")
1841+
assert_equal "Savvy?", ship.reload.pirate.catchphrase
1842+
end
18151843
end
18161844

18171845
module AutosaveAssociationOnACollectionAssociationTests

0 commit comments

Comments
 (0)