Transaction in Rails
Posted by Giovanni Intini | Filed under Nimboo, Programming, Ruby, Ruby on Rails
In an application I wrote for a client I needed transactions to handle a batch import of records from a legacy table into different models.
I browsed the documentation, googled for info and even asked in #rubyonrails, but wasn’t able to get any help, so I had to resort to good old trial and error.
First I tried nested transactions (the old way):
FirstModel.transaction do SecondModel.transaction do ThirdModel.transaction do FourthModel.transaction do fourth.do_stuff third.save second.handle_your_stuff first.good_old_foo_bar end end end end
I had a couple of problems with this code:
- Ugly
- Deprecated
It also didn’t seem to work for me, so I banged my head against my laptop for half a hour or so and suddenly the rails documentation started to make sense, so I wrote this code instead:
transaction do first.save! second.save! third.save! fourth.save! end
The reason I used save! was that the documentation says that a transaction block catches exceptions. Unfortunately that’s not true and to make it work I had to remove the exclamation marks.
Now I had another problem. I included the transaction code in a class I put in lib/, ImportJob, and I was using it with script/runner:
script/runner 'ImportJob.run' -e ENVIRONMENT
All of a sudden I started getting method missing errors on transaction. Now I could investigate and do the right thing or just hack a solution – I decided to hack a solution
class ImportJob < ActiveRecord::Migration end
Deriving ImportJob from Migration solved my problems. Now if anyone has a cleaner solution I will be happy to implement it but at least I solved my problem with transactions (and I hope this will be helpful for someone else too).
Update: Tim pointed out some flaws in my code and some more testing revealed that I needed to have an exception to trigger a Rollback, so I modified the block:
begin transaction do first.save! second.save! third.save! fourth.save! end rescue ActiveRecord::RecordInvalid => invalid # do whatever you wish to warn the user, or log something end
This works fine, thanks Tim!
May 23rd, 2007 at 5:15 pm
Transactions roll back if you raise an exception within one. You’ll need to raise the exception if you want the transaction to roll back, and you’ll need to catch it yourself if you don’t want it to propagate further.
I don’t think the documentation says anything to contradict this does it?
May 23rd, 2007 at 5:26 pm
The documentation doesn’t say anything to contradict what you say.
What it doesn’t say is that my code works. If one of the saves is false then it gets rolled back.
It also doesn’t say I should catch it to avoid interrupting my application.
May 23rd, 2007 at 6:20 pm
This sounded curious so I thought I’d double check.
There is a section called ‘Exception Handling’ at the bottom of the top part of the transaction document page:
http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
It says “Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you should be ready to catch those in your application code.”
Perhaps it’s recent
A transaction shouldn’t roll back unless something raises an exception – if you think your code rolls back a transaction without raising an exception then check it out more carefully. You may be mistaken, or it may be a bug in rails.
May 23rd, 2007 at 6:40 pm
As we say in Italy “you put the cricket into my ear”, that means you convinced me to test the code some more.
I’m pretty sure rollbacks are triggered this way because I did tests with live data, but who knows what’s happening under the hood?
May 24th, 2007 at 6:35 pm
Ok, I did some more testing and you were correct
I’ll update the post with the right code.
October 27th, 2008 at 5:05 am
if FirstModel,SecondModel,ThirdModel,FourthModel all of them use same db connection, code like this will work right
*Model.transaction do
fourth.do_stuff
third.save
second.handle_your_stuff
first.good_old_foo_bar
end
*=[First|Second|Third|Forth]