Sep 25, 2008

Rails Fixtures Order

So I've been having some trouble with Rails and fixtures. What I have in my fixtures are two tables, we'll call them t1 and t2. There is also a join table between these two tables, which has some info about the relationship between rows in the two tables.

Now suppose I have n1 fixtures for t1, and n2 fixtures for t2. That means there are O(n1n2) fixtures in the join table, in my case I have an entry for every pair. It would be a huge pain in the ass to enter all that data into the fixture manually. So what I do is just
t1 = Table1.find(:all)
t2 = Table2.find(:all)
t1.each do |r1|
t2.each do |r2|
#output fixture YAML
end
end
There is one problem with this. If the fixtures for Table1 and/or Table2 are not run before the fixture for JoinTable, then you're going to run into problems.

There are two things to do. The first one is in any controller test class, when you put your fixtures thing at the top, you put it like this:
fixtures :table1, :table2
fixtures :join_table
This seems fairly intuitive, but my first intuition was to put it like this:
fixtures :table1, :table2, :join_table
Then I had an epic brain fart trying to figure out why it wasn't working.

The second thing you need to do only needs to be done if you use the db:fixtures:load rake task. What this does is it loads your fixtures into your development database, which is very handy for coding. When you're in the development phase of your app, you don't need to create new migrations for your DB, just edit the old migration, and run db:migrate:reset. Makes things cleaner and easier to follow IMO.

However, this loads things in alphabetical order. You could name all your tables to be in the order that they should be loaded, but this is slightly annoying. The solution is to tweak your environment.rb file. Just add (EDIT: the other one didn't always work for me, I changed this so it does work):
ENV["FIXTURES"] ||= "table1,table2,join_table"
to config/environment.rb, and you will get the correct loading order.

EDIT: Always remember that when you add a new model, you'll need to manually add it to this list or your fixtures for that model will not be loaded. Learned this one the hard way, wondering why the fixtures were being loaded properly for tests, but not for the dev database.

EDIT (again): This doesn't always work, but it seems to work more often than if you didn't put this. The best option would be to load in fixtures, load whatever time-dependent stuff you have manually, and write a small script to export the DB into the YAML fixtures. That's what I ended up having to do finally, and it works like a charm.
Another thing if you don't want to do this is to create another script to do it for you. So instead of doing the normal rake task, you can have a script like this:
`rake db:fixtures:load`

# run whatever tasks you need ...
Run this script with script/runner so that it has access to your Rails models and what-not, and you'll be able to generate data automatically. You'll also have to load your file in from test/test_helper.rb during the setup() method so that your fixtures get loaded properly into tests.

3 comments:

Anonymous said...

This tip just helped me on my Ruby class final project. Thanks.

Rob Britton said...

Awesome, glad to have helped! I added a little fine print that I've discovered about this since writing this article, might want to check it out to see if it affects you.

Sanjeev Kumar Mishra said...

I think, here's a promising plugin for loading the fixtures in the correct order