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.
Follow @bigbinary on X. Check out our full blog archive.