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.
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: '[email protected]') 16begin transaction 17User 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"]] 18commit transaction 19"User has been successfully saved into the database." 20 21=> #<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"> 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: '[email protected]') 16SELECT sqlite_version(*) 17begin transaction 18User 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"]] 19commit transaction 20"User has been successfully saved into the database." 21 22=> #<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"> 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.