This blog is part of our Rails 6.1 series.

Rails 6.1 deprecates the use of return, break or throw to exit a transaction block.

return / break

>> Post.transaction do
>>   @post.update(post_params)
>>
>>   break # or return
>> end

# => TRANSACTION (0.1ms)  begin transaction
# => DEPRECATION WARNING: Using `return`, `break` or `throw` to exit a transaction block is
# => deprecated without replacement. If the `throw` came from
# => `Timeout.timeout(duration)`, pass an exception class as a second
# => argument so it doesn't use `throw` to abort its block. This results
# => in the transaction being committed, but in the next release of Rails
# => it will rollback.
# => TRANSACTION (0.8ms)  commit transaction

throw

>> Timeout.timeout(1) do
>>   Post.transaction do
>>     @post.update(post_params)
>>
>>     sleep 3 # simulate slow request
>>   end
>> end

# => TRANSACTION (0.1ms)  begin transaction
# => DEPRECATION WARNING: Using `return`, `break` or `throw` to exit a transaction block is
# => deprecated without replacement. If the `throw` came from
# => `Timeout.timeout(duration)`, pass an exception class as a second
# => argument so it doesn't use `throw` to abort its block. This results
# => in the transaction being committed, but in the next release of Rails
# => it will rollback.
# => TRANSACTION (1.6ms)  commit transaction
# => Completed 500 Internal Server Error in 1022ms (ActiveRecord: 3.2ms | Allocations: 9736)
# => Timeout::Error (execution expired)

Here, even when the error was thrown the transaction is committed. This is something which is going to change in the future versions.

This is done because currently, when a transaction block is wrapped in Timeout.timeout(duration) i.e. without second argument(an exception class) then it uses throw to exit the transaction.

Solution

>> Timeout.timeout(1, Timeout::Error) do
>>   Post.transaction do
>>     @post.update(post_params)
>>
>>     sleep 3 # simulate slow request
>>   end
>> end

# => TRANSACTION (0.1ms)  begin transaction
# => TRANSACTION (0.7ms)  rollback transaction
# => Timeout::Error (execution expired)

Check out the pull request for more details on this.