Hello everyone, this is my first post on Dev.to, I want to use this post to share some knowledge and practice my skills as writer. Thank you for reading my post.
Firstly, some information about our project
We use a legacy project with Rails 4, RSpec (also FactoryGirl) with use_transaction_fixtures activated (see more details). This means that every example runs within a transaction, and then it removes that data by simply rolling back the transaction at the end of the example.
When we need to test after_commit
callback we execute the method object.run_callbacks(:commit)
after the operation (create, update or destroy).
What did I want to do?
I was writing a test for my model and there were three callbacks of type after_commit, one for each transaction type (create, update and destroy). I wanted to ensure that the system would generate the information correctly.
Now, imagine you create one record, then you update the same record and after you delete the record.
Below is an example of the code.
it 'notify service at every change' do
total_jobs = NotifyWorker.jobs.count
expect(total_jobs).to be_zero
person = create(:person)
person.run_callbacks(:commit)
expect(NotifyWorker.jobs.count).to eq(total_jobs + 1)
expect(NotifyWorker.jobs[0]['args']).to eq(
[
'create',
{
'id' => person.id,
'name' => person.name
}
]
)
person.update(name: 'test')
person.run_callbacks(:commit)
expect(NotifyWorker.jobs.count).to eq(total_jobs + 2)
expect(NotifyWorker.jobs[1]['args']).to eq(
[
'update',
{
'id' => person.id,
'name' => person.name
}
]
)
person.destroy
person.run_callbacks(:commit)
expect(NotifyWorker.jobs.count).to eq(total_jobs + 3)
expect(NotifyWorker.jobs[2]['args']).to eq(
[
'destroy',
{
'id' => person.id
}
]
)
end
I expected the app to create three jobs in sequence, one for each transaction type, but that's not what happened.
In this case of the update it genetared a job of type "create" and when I deleted the record, it generated a job correctly, so the problem was in the update, but I didn't understand why.
I searched on google some information that might help and found a question on StackOverflow about the same problem I had.
After create the record (person = create(:person)
), a instance of @_start_transaction_state
is initialized, but never cleared. See more details
This variable is used to control which is the action in transaction.
So, I just had to clear the variable, for that you can use the clear_transaction_record_state
method before executing after_commit
, but this is a protected method, so you should use the send
method, like that.
object.send(:clear_transaction_record_state)
Below you can see the final result.
it 'notify service at every change' do
total_jobs = NotifyWorker.jobs.count
expect(total_jobs).to be_zero
person = create(:person)
person.run_callbacks(:commit)
expect(NotifyWorker.jobs.count).to eq(total_jobs + 1)
expect(NotifyWorker.jobs[0]['args']).to eq(
[
'create',
{
'id' => person.id,
'name' => person.name
}
]
)
person.send(:clear_transaction_record_state)
person.update(name: 'test')
person.run_callbacks(:commit)
expect(NotifyWorker.jobs.count).to eq(total_jobs + 2)
expect(NotifyWorker.jobs[1]['args']).to eq(
[
'update',
{
'id' => person.id,
'name' => person.name
}
]
)
person.send(:clear_transaction_record_state)
person.destroy
person.run_callbacks(:commit)
expect(NotifyWorker.jobs.count).to eq(total_jobs + 3)
expect(NotifyWorker.jobs[2]['args']).to eq(
[
'destroy',
{
'id' => person.id
}
]
)
end
Again thank you for reading my post and I see you later.
References:
- https://relishapp.com/rspec/rspec-rails/v/5-0/docs/transactions
- https://apidock.com/rails/ActiveRecord/Transactions/clear_transaction_record_state
- https://stackoverflow.com/questions/33940268/after-commit-callback-on-update-doesnt-trigger
- https://apidock.com/rails/v4.0.2/ActiveRecord/Transactions/clear_transaction_record_state
- https://github.com/rails/rails/blob/4-0-stable/activerecord/lib/active_record/transactions.rb#L384
- https://github.com/rails/rails/blob/main/activerecord/lib/active_record/transactions.rb
Top comments (3)
Awesome post. Thanks for sharing.
This holds for apps before Rails 5 right? github.com/rails/rails/pull/18458/...
Thank you, I believe you're right, unfortunelly I work in legacy project, I will change the post.