Skip to content

Commit 1d574ae

Browse files
committed
Revert "Prevent double save of cyclic associations"
Commit a1a5d37 doesn't properly set the foreign key on associations. Instead of trying to patch the change revert it for now, so we can investigate a better solution. This reverts commit a1a5d37 It also reverts the follow-up commits: * Revert "Rename internal `@saving` state to `@_saving`" This reverts commit 2eb5458 * Revert "Add `_` prefix for the internal methods" This reverts commit 12c0bec. * Revert "protected :can_save?" This reverts commit 8cd3b65. * Revert "Exclude #saving? from API docs" This reverts commit 35d3923.
1 parent 9131f08 commit 1d574ae

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)