I want an ActiveRecord transaction to fail on purpose. The issue is that the rollback isn't happening when I raise an ActiveRecord::Rollback exception (or any other exception). In the code bellow, the perfom
method imports a file. For the sake of simplicity, let's assume it just updates a record:
puts "+++ Before Transaction +++"
ActiveRecord::Base.transaction do
puts "+++ In Transaction +++"
perform
puts "+++ Before Rollback +++"
raise ActiveRecord::Rollback
end
puts "+++ After Transaction +++"
When the code runs here's the output of the server:
+++ Before Transaction +++
+++ In Transaction +++
TRANSACTION (0.1ms) BEGIN
↳ app/models/import.rb:103:in `file_content'
Disk Storage (0.1ms) Downloaded file from key: abcdefg
Car Load (0.2ms) SELECT "cars".* FROM "cars" WHERE "cars"."make" = '207' LIMIT 1
↳ app/models/application_record.rb:82:in `try_update_or_create_report'
Car Exists? (0.3ms) SELECT 1 AS one FROM "rolling_shutters_automatism_prices" WHERE
"cars"."make" = '207' AND "cars"."id" != 5 LIMIT 1
↳ app/models/application_record.rb:106:in `try_update_report'
Car Update (0.2ms) UPDATE "cars" SET "price" = 20.0, "updated_at" = '2023-12-26 15:17:47.978023' WHERE "cars"."id" = 5
↳ app/models/application_record.rb:106:in `try_update_report'
+++ Before Rollback +++
+++ After Transaction +++
The behaviour I would expect is to have ROLLBACK
logged in between the 2 last line, but nothing is logged. Why is that?
I use rails 7.0.4 with postgres 15 and ruby 3.2.2
EDIT: I left out an important piece of information. The transaction happens within an ActiveRecord callback.
The issue was that the transaction happened within an ActiveRecord callback. Since the callback is itself wrapped in a transaction, the transaction I was trying to perform was a nested transaction. Therefore, raising an ActiveRecord::Rollback exception caused the outer transaction to rollback.
According to the documentation:
In order to get a ROLLBACK for the nested transaction you may ask for a real sub-transaction by passing requires_new: true. If anything goes wrong, the database rolls back to the beginning of the sub-transaction without rolling back the parent transaction.
So the fixed example would look like this:
puts "+++ Before Transaction +++"
ActiveRecord::Base.transaction(requires_new: true) do
puts "+++ In Transaction +++"
perform
puts "+++ Before Rollback +++"
raise ActiveRecord::Rollback
end
puts "+++ After Transaction +++"