Back to Blog

Rails 6 fixes after_commit callback invocation bug

on February 25, 2020
This blog is part of our Rails 6 series.

Rails 6.0 was recently released.

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.

Rails 5.2

Let's define an after_commit callback in User model and try updating an invalid user object in a transaction block.

1>> class User < ApplicationRecord
2>>   validates :name, :email, presence: true
3>>
4>>   after_commit :show_success_message
5>>
6>>   private
7>>
8>>     def show_success_message
9>>       p 'User has been successfully saved into the database.'
10>>     end
11>> end
12
13=> :show_success_message
14
15>> user = User.create(name: 'Jon Snow', email: 'jon@bigbinary.com')
16begin transaction
17User Create (0.8ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "Jon Snow"], ["email", "jon@bigbinary.com"], ["created_at", "2019-07-14 15:35:33.517694"], ["updated_at", "2019-07-14 15:35:33.517694"]]
18commit transaction
19"User has been successfully saved into the database."
20
21=> #<User id: 1, name: "Jon Snow", email: "jon@bigbinary.com", created_at: "2019-07-14 15:35:33", updated_at: "2019-07-14 15:35:33">
22
23>> User.transaction do
24>>   user.email = nil
25>>   p user.valid?
26>>   user.save
27>> end
28begin transaction
29false
30commit transaction
31"User has been successfully saved into the database."
32
33=> 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.

Rails 6.0.0.rc1

Now, let's try the same thing in Rails 6.

1>> class User < ApplicationRecord
2>>   validates :name, :email, presence: true
3>>
4>>   after_commit :show_success_message
5>>
6>>   private
7>>
8>>     def show_success_message
9>>       p 'User has been successfully saved into the database.'
10>>     end
11>> end
12
13=> :show_success_message
14
15>> user = User.create(name: 'Jon Snow', email: 'jon@bigbinary.com')
16SELECT sqlite_version(*)
17begin transaction
18User Create (1.0ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "Jon Snow"], ["email", "jon@bigbinary.com"], ["created_at", "2019-07-14 15:40:54.022045"], ["updated_at", "2019-07-14 15:40:54.022045"]]
19commit transaction
20"User has been successfully saved into the database."
21
22=> #<User id: 1, name: "Jon Snow", email: "jon@bigbinary.com", created_at: "2019-07-14 15:40:54", updated_at: "2019-07-14 15:40:54">
23
24>> User.transaction do
25>>   user.email = nil
26>>   p user.valid?
27>>   user.save
28>>   end
29false
30
31=> 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.