Skip to content

Commit 6daa2d8

Browse files
authored
Merge pull request rails#41759 from p8/rollback-a1a5d37749
Revert "Prevent double save of cyclic associations"
2 parents 9131f08 + 1d574ae commit 6daa2d8

File tree

6 files changed

+4
-105
lines changed

6 files changed

+4
-105
lines changed

activerecord/CHANGELOG.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,6 @@
1717

1818
*Dinah Shi*
1919

20-
* Prevent double saves in autosave of cyclic associations.
21-
22-
Adds an internal saving state which tracks if a record is currently being saved.
23-
If a state is set to true, the record won't be saved by the autosave callbacks.
24-
25-
*Petrik de Heus*
26-
2720
* Fix Float::INFINITY assignment to datetime column with postgresql adapter
2821

2922
Before:

activerecord/lib/active_record/autosave_association.rb

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -233,20 +233,11 @@ def define_autosave_validation_callbacks(reflection)
233233

234234
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
235235
def reload(options = nil)
236-
@_saving = false
237236
@marked_for_destruction = false
238237
@destroyed_by_association = nil
239238
super
240239
end
241240

242-
def save(**options) # :nodoc
243-
_saving { super }
244-
end
245-
246-
def save!(**options) # :nodoc:
247-
_saving { super }
248-
end
249-
250241
# Marks this record to be destroyed as part of the parent's save transaction.
251242
# This does _not_ actually destroy the record instantly, rather child record will be destroyed
252243
# when <tt>parent.save</tt> is called.
@@ -282,21 +273,7 @@ def changed_for_autosave?
282273
new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
283274
end
284275

285-
protected
286-
def _can_save? # :nodoc:
287-
!destroyed? && !@_saving
288-
end
289-
290276
private
291-
# Track if this record is being saved. If it is being saved we
292-
# can skip saving it in the autosave callbacks.
293-
def _saving
294-
previously_saving, @_saving = @_saving, true
295-
yield
296-
ensure
297-
@_saving = previously_saving
298-
end
299-
300277
# Returns the record for an association collection that should be validated
301278
# or saved. If +autosave+ is +false+ only new records will be returned,
302279
# unless the parent is/was a new record itself.
@@ -423,7 +400,7 @@ def save_collection_association(reflection)
423400
end
424401

425402
records.each do |record|
426-
next unless record._can_save?
403+
next if record.destroyed?
427404

428405
saved = true
429406

@@ -460,7 +437,7 @@ def save_has_one_association(reflection)
460437
association = association_instance_get(reflection.name)
461438
record = association && association.load_target
462439

463-
if record&._can_save?
440+
if record && !record.destroyed?
464441
autosave = reflection.options[:autosave]
465442

466443
if autosave && record.marked_for_destruction?
@@ -505,7 +482,7 @@ def save_belongs_to_association(reflection)
505482
return unless association && association.loaded? && !association.stale_target?
506483

507484
record = association.load_target
508-
if record&._can_save?
485+
if record && !record.destroyed?
509486
autosave = reflection.options[:autosave]
510487

511488
if autosave && record.marked_for_destruction?

activerecord/lib/active_record/core.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,6 @@ def init_internals
841841
@readonly = false
842842
@previously_new_record = false
843843
@destroyed = false
844-
@_saving = false
845844
@marked_for_destruction = false
846845
@destroyed_by_association = nil
847846
@_start_transaction_state = nil

activerecord/lib/active_record/transactions.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,6 @@ def restore_transaction_record_state(force_restore_state = false)
404404
attr = attr.with_value_from_user(value) if attr.value != value
405405
attr
406406
end
407-
@_saving = false
408407
@mutations_from_database = nil
409408
@mutations_before_last_save = nil
410409
if @attributes.fetch_value(@primary_key) != restore_state[:id]

activerecord/test/cases/associations/has_many_through_associations_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1556,7 +1556,7 @@ def test_circular_autosave_association_correctly_saves_multiple_records
15561556
fall.sections << sections
15571557
fall.save!
15581558
fall.reload
1559-
assert_equal sections.sort_by(&:id), fall.sections.sort_by(&:id)
1559+
assert_equal sections, fall.sections.sort_by(&:id)
15601560
end
15611561

15621562
private

activerecord/test/cases/autosave_association_test.rb

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1951,72 +1951,3 @@ def test_should_update_children_when_association_redefined_in_subclass
19511951
assert_equal "Updated", valid_project.name
19521952
end
19531953
end
1954-
1955-
class TestCyclicAutosaveAssociationsOnlySaveOnce < ActiveRecord::TestCase
1956-
test "child is saved only once if child is the inverse has_one of parent" do
1957-
ship_reflection = Ship.reflect_on_association(:pirate)
1958-
pirate_reflection = Pirate.reflect_on_association(:ship)
1959-
assert_equal ship_reflection, pirate_reflection.inverse_of, "The pirate reflection's inverse should be the ship reflection"
1960-
1961-
child = Ship.new(name: "Nights Dirty Lightning")
1962-
parent = child.build_pirate(catchphrase: "Aye")
1963-
child.save!
1964-
assert_predicate child, :previously_new_record?
1965-
assert_predicate parent, :previously_new_record?
1966-
end
1967-
1968-
test "child is saved only once if child is an inverse has_many of parent" do
1969-
child = FamousShip.new(name: "Poison Orchid")
1970-
parent = child.build_famous_pirate(catchphrase: "Aye")
1971-
child.save!
1972-
assert_predicate child, :previously_new_record?
1973-
assert_predicate parent, :previously_new_record?
1974-
end
1975-
1976-
test "similar children are saved in the autosave" do
1977-
child1 = FamousShip.new(name: "Poison Orchid")
1978-
parent = child1.build_famous_pirate(catchphrase: "Aye")
1979-
child2 = parent.famous_ships.build(name: "Red Messenger")
1980-
child1.save!
1981-
assert_predicate child2, :persisted?
1982-
assert_predicate child1, :previously_new_record?
1983-
assert_predicate parent, :previously_new_record?
1984-
assert_equal [child2, child1], parent.reload.famous_ships
1985-
end
1986-
1987-
test "parent is saved only once" do
1988-
child = Ship.new(name: "Nights Dirty Lightning")
1989-
parent = child.build_pirate(catchphrase: "Aye")
1990-
parent.save!
1991-
assert_predicate child, :previously_new_record?
1992-
assert_predicate parent, :previously_new_record?
1993-
end
1994-
1995-
test "saving? is reset to false if validations fail" do
1996-
child = Ship.new(name: "Nights Dirty Lightning")
1997-
child.build_pirate
1998-
assert_not child.save
1999-
assert_not child.instance_variable_get(:@_saving)
2000-
end
2001-
2002-
test "saving? is set to false after multiple nested saves" do
2003-
autosave_saving_stack = []
2004-
2005-
ship_with_saving_stack = Class.new(Ship) do
2006-
before_save { autosave_saving_stack << @_saving }
2007-
after_save { autosave_saving_stack << @_saving }
2008-
end
2009-
2010-
pirate_with_callbacks = Class.new(Pirate) do
2011-
after_save { ship.save }
2012-
after_create { ship.save }
2013-
after_commit { ship.save }
2014-
end
2015-
2016-
child = ship_with_saving_stack.new(name: "Nights Dirty Lightning")
2017-
child.pirate = pirate_with_callbacks.new(catchphrase: "Aye")
2018-
child.save!
2019-
assert_equal [true] * 8, autosave_saving_stack
2020-
assert_not child.instance_variable_get(:@_saving)
2021-
end
2022-
end

0 commit comments

Comments
 (0)