log4p

Peter Maas’s Weblog

Archive for January, 2008

Nesting ActiveRecord transactions

I was experimenting with transactions in Rails/ActiveRecord to see what they can and what they can't do. At first it seemed I couldn't get transactions to work at all. I made sure I was using the InnoDB storage engine. I rechecked my code. But the test kept failing.

This, by the way, is the test:

  1. require File.dirname(__FILE__) + '/../test_helper'
  2.  
  3. class BlogTest <ActiveSupport::TestCase
  4.  
  5.   def test_transaction_rollback
  6.     b = Blog.find(2);
  7.     assert_equal(b.title, "Title 2")
  8.    
  9.     assert_raise RuntimeError do
  10.       Blog.transaction do
  11.         b.title = "changed title"
  12.         b.save
  13.         raise "forced error" # force exception
  14.       end
  15.     end
  16.     b.reload # get the current state from the database
  17.  
  18.     assert_equal(b.title, "Title 2")
  19.   end
  20. end

Luckily a colleague pointed out that it was probably related to Rails' limited support for nested transactions; tests run within a transaction for performance reasons... which makes it impossible to roll back a transaction within a test.

Consider the following code:

  1. Account.transaction do
  2.   User.transaction do
  3.     # do some stuff
  4.   end
  5.  
  6.   # the previous transaction has already been committed, if we
  7.   # raise an exception now it can not be rolled back
  8.   raise
  9. end

The inner transaction won't be rolled back if the outer transaction fails after it's execution!

Googling around I found a simple fix for my test. Turn off the transactional behavior of the fixtures in the test:

  1. class BlogTest <ActiveSupport::TestCase
  2.   self.use_transactional_fixtures = false
  3.   ...
  4. end

Which works, but will slow down the execution of all tests. And it actually doesn't really solve the problem, it is a work-around.

I decided to see why nested transactions where such a big issue, and how it could be solved and found the ActiveRecord NestedTransactions (ARNE) pluging. ARNE is a plugin which makes ActiveRecord use SAVEPOINTS to achieve nested transactions functionality.

Savepoints save the state within a transaction to provide the ability to jump back to this state when something goes wrong later on. In SQL it looks something like this:

  1. BEGIN; # start transaction
  2. SAVEPOINT my_first_savepoint # capture the current state
  3.  
  4. # do some work here
  5.  
  6. ROLLBACK TO SAVEPOINT my_first_savepoint  # optionally roll back to the specified state.
  7. RELEASE SAVEPOINT my_first_savepoint # clean up
  8. END; # end the transaction

The ARNE plugin wraps transaction blocks in savepoints to provide nesting functionality and actually works beautifully; my test succeeded. I did however find a small bug for which I've submitted a patch. The name of a safepoint must be unique for nesting to work without problems. If a name is used twice the initial savepoint is overwritten. Since the name was originally generated using a random generator this was in no way guaranteed:

  1. # method to generate savepoint name
  2. def generate_savepoint_name
  3.   # name must start with letters
  4.   "SP#{MD5.md5(rand.to_s)}"
  5. end

A better solution would (IMHO) be to use a UUID generator:

  1. # method to generate savepoint name
  2. def generate_savepoint_name
  3.   # name must start with letters
  4.   "SP#{UUID.random_create.to_s.gsub('-','_')}"
  5. end

I used the UUID generator from the uuidtools Gem. Note the random_create (a random generator is used to seeded the UUID algoritm) this guarantees that the above will also work when there is no standard MAC address (I heard about VPS solutions with this problem).

I learned a lot by looking at the way transaction handling is implemented in ActiveRecord. The code is clean, compact and readable. It made me like Ruby even more; although some people will probably say (and I tend to agree) that nesting should have been part of ActiveRecord all along.

No comments

Did you pass math

Some people complained about the fact that they had to register to add comments on this blog. Requiring people to login was the easiest way to get rid of spam after problems I had with SpamKarma.

While waiting for eclipse/subversion to finish its' tasks I decided to install Did You Pass Math? (DYPM). DYPM asks the person making the comment to answer a simple math question; you've seen it on other blogs.

So... let's see if it works for me!

1 comment

amsterdam.rb

logo123Januari 28 will be the day of the first gathering of the Amsterdam's Ruby User Group. I'd like to be there but due to the fact that at that time my wife will be over 40 weeks pregnant... I just wont...

Still, I think it's a great idea and will certainly be joining future gatherings! For those of you without pregnant wives, come and talk Ruby at De bekeerde suster near the Nieuwmarkt in Amsterdam (Duh!!) arround 20:00.

No comments

Learning Ruby at the Free Online Ruby Programming Course

A couple of weeks ago a I read about the free online Ruby course by Satish Talim. The course is aimed at people who want to start learning Ruby. I've probably passed the 'starting stage' a while ago, but was intrigued and registerd (as did over 2000 enthousiasts from all over the globe).

At the beginning of this week the course started. The training system is based on moodle; a free, open source course management system for online learning. If you are used to fancy looking websites like Javablackbelt the format feels a bit harsh; but it offers everything needed (forums, pols, wiki, exams, rating systems, schedules etc.).

I made this weeks' exam before I actually followed the course, and answered all questions correctly (as I hoped).

Afterwards I skipped through the training materials. They contain some Google ads (which is understandable), and most of the images are of horrible quality but does however cover the basics fairly well. Mr. Talim also offers audio versions; but I prefer to read.

I think this is a great resource for people who don't have access to all resources I (or the company I work for) have at my disposal and applaude the initiative! Kudos to mr. Talim!

No comments