-
Notifications
You must be signed in to change notification settings - Fork 163
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
Make after_commit work reliabliy inside nested transactions #338
Conversation
In the current implementation, after_commit callbacks are ran immediately after the `ActiveRecord::Base.transaction` block, which is not guaranteed to be after the transaction has been committed to the database. Speficially, if statesman code is running inside another transaction, the after_commit block will be running prematurely. This commit fixes that behavious by using ActiveRecord’s add_transaction_record method (which is how after_commit callbacks are implemented in Rails).
@matid This looks really good. |
Thanks @danwakefield. I fixed the Rubocop warnings and it looks like everything is green now. |
@matid Sorry I haven't gotten back to you. I was going to release your change along with The testcase is a bit funky so I could be wrong. What do you think? If that solves things I'm happy to cut a release for it. |
@danwakefield Your test case is succeeding because your side-effect ( This is a test case that will fail without my patch (even with #249): context "placeholder" do
before do
MyStateMachine.class_eval do
cattr_accessor(:after_commit_callback_executed) { false }
after_transition(from: :initial, to: :succeeded, after_commit: true) do
MyStateMachine.after_commit_callback_executed = true
end
end
end
after do
MyStateMachine.class_eval do
callbacks[:after_commit] = []
end
end
let!(:model) do
MyActiveRecordModel.create
end
# rubocop:disable RSpec/ExampleLength
it do
expect do
ActiveRecord::Base.transaction do
model.state_machine.transition_to!(:succeeded)
raise ActiveRecord::Rollback
end
end.to_not change(MyStateMachine, :after_commit_callback_executed)
end
# rubocop:enable RSpec/ExampleLength
end |
@danwakefield I committed an updated version of the after commit wrapper that works correctly with the The test case above should fail on |
Perfect, I suspected I wasn't testing it properly. I'll merge and release today. |
Thanks @danwakefield, appreciate it! FYI: This pull request also closes #281. |
In the current implementation, after_commit callbacks are ran
immediately after the
ActiveRecord::Base.transaction
block,which is not guaranteed to be after the transaction has been
committed to the database.
Speficially, if statesman code is running inside another transaction,
the after_commit block will be running prematurely.
This commit fixes that behavious by using ActiveRecord’s
add_transaction_record method (which is how after_commit
callbacks are implemented in Rails).
This issue was already reported in #281 in December 2017, but never fixed.
Here’s a blog post explaining this behaviour: https://dev.to/evilmartians/rails-aftercommit-everywhere--4j9g
A secondary side-effect of this behaviour is that if you schedule a background job from within the
after_commit: true
callback, it’s not guaranteed that the transition has been committed to the database yet, and the scheduled job might not run correctly.This is a sample state machine exhibiting this behaviour:
and a simple test case:
Without the patch, the after_commit callback will execute even if the transaction is rolled back:
With the patch, only the
after_commit: false
callback will run: