Using Allocate to Create ActiveRecord Objects When Stubbing find_by_sql
find_by_sql. What a method. I love the flexibility it gives me when I need to get information from my database with as little fuss as possible. Yet when I want to stub find_by_sql and return real objects in my rspec tests, a little part of me gets frustrated. The real pain comes in when you want to have an object with attributes that aren't part of the original class and typically you're going to run into that when you use find_by_sql.
In my particular use case, I had two different tables, the first was a Shop table and the second was a Job table. In terms of the association, a Shop could have many jobs. The find_by_sql method was aggregating data from the two tables and creating each instance of the result as Job object. So it looked something like this:
I'm sure that you're thinking to yourself that I could have just created some factories or fixtures and then had my tests just query the database for the records. That probably would have been the easy route, but I was wondering if I could pull this off without the database call. So how do we do this?
First, we need to understand how ActiveRecord actually creates new objects. The post from Peter Bui is a great first step in explaining what goes on when ActiveRecord queries a database and creates a new object. The important thing to note in his article is Ruby's ability to create an object without calling the initialize method via allocate.
From there it was time to look at the core ActiveRecord code to see what is going on. Just to note, since Peter's article, ActiveRecord::Base has changed a little bit. First I started with the code for find_by_sql:
You can see in this piece of code that ActiveRecord is hitting the database with your custom SQL and then for each result creating a new instance. Now let's see how that instance is created:
The first line of code is looking for the appropriate class to use to create the object via find_sti_class. Then it allocates an object based upon the class name. The next line is really where the meat is. init_with will actually then create the object with the attributes that you specify. Sweet! I think we struck gold.
Now that we know about the init_with and allocate methods we can now create real objects to use in our tests if we need attributes outside of what the object provides. So in the example that I provided above, I could do something like this:
Excellent! Now I can stub the find_by_sql call and I can create objects that will be in the correct class that I need.
Do note that what I did above is for edge rails. If you're on 3.0.1 or below you're going to have to do this to create your objects:
Also, pay attention to the way the hash was created. You'll need to use strings for the keys otherwise you'll get a bunch of errors.
Posted 10/17/2010 03:08:00 PM - activerecord, rails
WHO AM I?
I'm Rimas Silkaitis, a mild mannered Ruby dev living in San Francisco. I like building stuff for the web and am particularly interested in machine learning. I also own a set of turntables.
Posts

