Skip to navigation

Migrating with Models

Published February 23, 2006

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.

Archived comments

Comments were previously allowed on articles. Though no new comments are being accepted you can see the old comments below.

  1. Tim Lucas

    and the same discussion was going on in the rails-core mailing list a few days ago

  2. 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) { ... })

  3. Anonymous

    Great article. Definitely something I can use in my development.

    p.s. It’s Voila, not Wallah! ;-P

  4. 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.

  5. 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.

  6. 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.

Thoughts

toolmantim

I’m Tim Lucas, a user experience developer currently in Sydney Australia.

I occasionally write, snap photos, present on various technical topics, tweet my going-ons, share teh codes and post tidbits to the scrapbook.

Most recently I published Simplifying ticket sales on sydneyoperahouse.com (February 16, 2010)

Work with me via Agency Rainford, or shoot an email to and say hello.

Powered by the motion of the ocean