New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Commit callbacks for the same AR model are not all executed #39400
Comments
It seems that callbacks run at the end of
block. I added some
|
Therefore, I’m not sure that calling callbacks on all records would be appropriate. |
In def commit_records
ite = records.uniq(&:__id__)
already_run_callbacks = {}
while record = ite.shift
if @run_commit_callbacks
trigger_callbacks = record.trigger_transactional_callbacks?
should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
already_run_callbacks[record] ||= trigger_callbacks
record.committed!(should_run_callbacks: should_run_callbacks)
else
# if not running callbacks, only adds the record to the parent transaction
connection.add_transaction_record(record)
end
end
ensure
ite.each { |i| i.committed!(should_run_callbacks: false) }
end If we replace should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
already_run_callbacks[record] ||= trigger_callbacks with should_run_callbacks = !already_run_callbacks[record.object_id] && trigger_callbacks
already_run_callbacks[record.object_id] ||= trigger_callbacks we will get the desired behavior (we can also use Link to this file in rails repo: rails/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb Line 119 in 39de115
|
Test output (after suggested change):
|
But, again, I'm not sure that this change should be commited. |
@evgeniy-r ahh nice find! That definitely seems to be the right place, along with I do still think this is a bug, though. Just because the objects happen to be the same AR model type + ID, I don't think that means AR should be skipping the callbacks of all but the first occurrence of it in a transaction. That definitely feels a little surprising! If the Rails Core team agrees, I'd be happy to issue a PR to fix |
I agree that we need the opinion of some member of the core team. |
I had found the same thing in #36190, and that is the reason to introduce Practically the enrollment same records (but different object_id) in a transaction easily happens if manipulate an association records and save in callbacks (e.g. So if we will change the behavior, it is considered as a breaking change which will break existing apps. |
That's some awesome context @kamipo, thanks! I've been experiencing it with Active Storage's And totally agree, if we change the behavior, it should be considered a breaking change and potentially should be done behind a framework configuration that can be turned on by default in new apps while leaving existing apps with the current behavior. |
This issue has been automatically marked as stale because it has not been commented on for at least three months. |
I do still think this is a bug, but I haven't been able to circle back and make the appropriate change to AR. I will try and get back to it soon! |
This issue has been automatically marked as stale because it has not been commented on for at least three months. |
I've just encountered this bug in Rails 6.1.4.7 – same issue! Only the first instance of a given record in a transaction block has the |
@agrobbin this seems to still be popping up on Rails 7.1 — for us it's around active storage. A much simplified example of the issue: class Post
has_many :comments
end
class Comment
has_one_attached :avatar
has_one_attached :screenshot
end
Post.transaction do
post.comments.each do |comment|
comment.avatar.attach(avatar_file)
end
# some other code
post.comments.each do |comment|
comment.screenshot.attach(screenshot_file)
end
end Because we end up with different object ID references to the I see that a note was added to the active record callbacks guide in #49831, but seems like the same warning should be applied to active storage. |
Steps to reproduce
If you are interacting with 2 instances of the same AR model in a transaction, Active Record only executes commit callbacks for the first instance of the model, not any others.
This is not a super-common real-world scenario, though I'm sure it does happen! For us, it has come up a few times in our system tests, when we have a controller action updating an AR model, then enqueuing a background job, which does further updates to that same model. With system tests executing background jobs inline, those 2 updates to the same AR model occur within the same transaction.
Here is a reproducible test case:
I believe this is due to the internals of AR's commit callback management tracking the callbacks to be executed based on the AR model, which implements
==/eql?
like this:rails/activerecord/lib/active_record/core.rb
Lines 428 to 443 in 34991a6
I'd love to fix this myself, but have been unable to track down the precise area in AR's transaction/callback internals.
Expected behavior
after_commit
is called 3 times, once for eachCompany
instance.Actual behavior
after_commit
is only called 2 times, for objectA
andB
.System configuration
Rails version: 6.0.3.1
Ruby version: 2.6.6
The text was updated successfully, but these errors were encountered: