Rails 7 ensures has_one autosave association callbacks get called once
source link: https://blog.saeloun.com/2021/06/08/rails-7-ensure-has-one-association-callbacks
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Rails 7 ensures has_one autosave association callbacks get called once
Jun 8, 2021 , by Jijo Bose 1 minute readActiveRecord AutosaveAssociation is a module that takes care of automatically saving associated records when their parent is saved. In addition to saving, it also destroys any associated records that were marked for destruction.
Association with autosave option defines several callbacks on your model
(around_save
, before_save
, after_create
, after_update
).
When saving a record, autosave adds callbacks to save its associations.
Since the associations can have similar callbacks for the inverse,
endless loops could occur.
To prevent these endless loops, the callbacks for has_many
and belongs_to
are made non-cyclic (methods that only execute once).
This is implemented in the Ruby define_non_cyclic_method
method and
the same wasn’t used for the has_one
callbacks.
Before
While has_one
association callbacks didn’t result in endless loops,
they could execute multiple times.
For a bidirectional has_one
with autosave enabled,
the save_has_one_association gets called twice creating inconsistency.
class Supplier < ActiveRecord::Base
has_one :account, autosave: true
def save_has_one_association(reflection)
@count ||= 0
@count += 1 if reflection.name == :account
super
end
end
class Account < ActiveRecord::Base
belongs_to :supplier, autosave: true
end
supplier = Supplier.new(name: "ACME")
supplier.build_account(account_number: "ACCN007")
supplier.save!
# this returns 2 instead of 1.
puts supplier.instance_variable_get(:@count)
After
Starting with Rails 7, these
changes
are added for the has_one
autosave callbacks to be non-cyclic.
By doing this the autosave callbacks are made more consistent for all 3 cases: has_many
,
has_one
and belongs_to
.
class Supplier < ActiveRecord::Base
has_one :account, autosave: true
def save_has_one_association(reflection)
@count ||= 0
@count += 1 if reflection.name == :account
super
end
end
class Account < ActiveRecord::Base
belongs_to :supplier, autosave: true
end
supplier = Supplier.new(name: "ACME")
supplier.build_account(account_number: "ACCN007")
supplier.save!
# this returns 1
puts supplier.instance_variable_get(:@count)
How Rails fixed it?
In the previous versions of Rails, the callbacks were defined as follows:
if reflection.collection?
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
after_create save_method
after_update save_method
elsif reflection.has_one?
define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
after_create save_method
after_update save_method
else
define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
before_save save_method
end
The Rails team added define_non_cyclic_method
under reflection.has_one?
condition.
if reflection.collection?
around_save :around_save_collection_association
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
after_create save_method
after_update save_method
elsif reflection.has_one?
define_non_cyclic_method(save_method) { save_has_one_association(reflection) }
after_create save_method
after_update save_method
else
define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
before_save save_method
end
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK