February 25, 2020
This blog is part of our Rails 6 series.
Rails 6 fixes a bug where after_commit callbacks are called on failed update in a transaction block.
Let's checkout the bug in Rails 5.2 and the fix in Rails 6.
Let's define an
after_commit
callback in User
model and try updating an invalid user object in a
transaction block.
>> class User < ApplicationRecord
>> validates :name, :email, presence: true
>>
>> after_commit :show_success_message
>>
>> private
>>
>> def show_success_message
>> p 'User has been successfully saved into the database.'
>> end
>> end
=> :show_success_message
>> user = User.create(name: 'Jon Snow', email: '[email protected]')
begin transaction
User Create (0.8ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "Jon Snow"], ["email", "[email protected]"], ["created_at", "2019-07-14 15:35:33.517694"], ["updated_at", "2019-07-14 15:35:33.517694"]]
commit transaction
"User has been successfully saved into the database."
=> #<User id: 1, name: "Jon Snow", email: "[email protected]", created_at: "2019-07-14 15:35:33", updated_at: "2019-07-14 15:35:33">
>> User.transaction do
>> user.email = nil
>> p user.valid?
>> user.save
>> end
begin transaction
false
commit transaction
"User has been successfully saved into the database."
=> false
As we can see here, that that the after_commit callback show_success_message
was called even if object was never saved in the transaction.
Now, let's try the same thing in Rails 6.
>> class User < ApplicationRecord
>> validates :name, :email, presence: true
>>
>> after_commit :show_success_message
>>
>> private
>>
>> def show_success_message
>> p 'User has been successfully saved into the database.'
>> end
>> end
=> :show_success_message
>> user = User.create(name: 'Jon Snow', email: '[email protected]')
SELECT sqlite_version(*)
begin transaction
User Create (1.0ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "Jon Snow"], ["email", "[email protected]"], ["created_at", "2019-07-14 15:40:54.022045"], ["updated_at", "2019-07-14 15:40:54.022045"]]
commit transaction
"User has been successfully saved into the database."
=> #<User id: 1, name: "Jon Snow", email: "[email protected]", created_at: "2019-07-14 15:40:54", updated_at: "2019-07-14 15:40:54">
>> User.transaction do
>> user.email = nil
>> p user.valid?
>> user.save
>> end
false
=> false
Now, we can see that after_commit callback was never called if the object was not saved.
Here is the relevant issue and the pull request.
If this blog was helpful, check out our full blog archive.