Migrating with Models
February 24, 2006 00:13 (Sydney Australia)
No, I’m not talking about marrying an international hottie. I’m talking about using or, rather, not using ActiveRecord models in your migrations (though I think for the former topic may have been incredibly more interesting and less geeky. Maybe another time.)
Back in October Scott Laird blogged about Migrating in two dimensions and the problems people had running Typo’s migrations.
Basically, it boils down to one thing: Don’t use your ActiveRecord models in your migrations or your life will be miserable.
...but why, why must you say these things? Because who knows what your class will look at some point in the future when your migration is run. Methods might change, bacon will cease to be chunky… hell, the model might not even exist.
...ok, so what the hell are we supposed to do? Good question.
Option One: Only use straight SQL… don’t use an ActiveRecord magic. Well duh… but I didn’t learn this rails thing fo nothing. Let me use some of my ActiveRecord mojo, baby!
Option Two: Create temporary ActiveRecord classes to go along with your migration. Oh yeah, now that’s what I’m talkin about.
For example, in a project I’m currently working on I wanted to add acts_as_list capability to the Product model so needed to add a ‘position’ attribute. In the migration I also wanted to assign a default position to all the model instances.
One possible implementation could have been:
class AddPositionToProducts < ActiveRecord::Migration
def self.up
add_column :products, :position, :integer
Product.reset_column_information
# Set default list orders
SoftwareProduct.find(:all).inject(1) do |i,p|
p.update_attribute(:position,i)
i+1
end
CourseProduct.find(:all).inject(1) do |i,p|
p.update_attribute(:position,i)
i+1
end
end
def self.down
remove_column :products, :position
end
end
Not so fast cowboy.
Say in a few days time I do a big refactor and remove the Product class altogether. What happens two months from now when this migration is run? Boom!
The solution is to create classes specific to each migration.
class AddPositionToProducts < ActiveRecord::Migration
class Product < ActiveRecord::Base; end
class SoftwareProduct < Product; end
class CourseProduct < Product; end
def self.up
add_column :products, :position, :integer
Product.reset_column_information
# Set default list orders
SoftwareProduct.find(:all).inject(1) do |i,p|
p.update_attribute(:position, i)
i+1
end
CourseProduct.find(:all).inject(1) do |i,p|
p.update_attribute(:position, i)
i+1
end
end
def self.down
remove_column :products, :position
end
end
Wallah! No external code dependencies. Nobody can break our migration… no matter what they can do to the code in /app/models/. And we still get to use our ActiveRecord mojo.


Comments
Tim Lucas
and the same discussion was going on in the rails-core mailing list a few days ago
Tim Lucas
btw if you are initialising positions like above you want to start them from 1, not 0 (i.e.
Model.find(:all).inject(1) { ... })Anonymous
Great article. Definitely something I can use in my development.
p.s. It’s Voila, not Wallah! ;-P
charlie bowman
I hadn’t even thought about that! That’s definately something to think about if you’re working with open source, since it changes so frequently.
Ben Askins
If only I was subscribed to your blog back in feb 2006. I could have avoided some grief over the last couple of days.
Tim Lucas
Well at least you found it when you had the grief! The fact that the migration documentation encourages you to use your models doesn’t help either.
Alex Fetcher
Great site!
29ded3f4d82ae5e6082a52c9b8306a80
To comment on this article you must have javascript enabled.