How to use models in your migrations (without killing kittens)

Sometimes a migration needs to do more than add or remove a column. When you must convert existing data, migrations can get nasty because SQL is all you can use. You can never call models in a migration because your models will evolve away from your migrations and everyone will die. Seriously, don't do that. Think of the children!

Fortunately, there is a way to use ActiveRecord magic in your migrations and still get the girl. By inlining classes you decouple your migration from any future changes in your models. Here is an example:

Say you have two classes Article and Vendor. Each Article has_many :vendors. Now you want to introduce a boolean flag current to your Vendor class, which determines whether the article is currently being procured from that vendor.

But what about the 14000 articles already in the database? You call up your client and decide on the following migration rule: The first vendor created for an article is to be flagged as "current".

While you can express that rule in pure SQL, you'd much rather use vanilla Ruby. So you write your migration like this:

class AddCurrentToVendor < ActiveRecord::Migration

  class Vendor < ActiveRecord::Base
  end

  class Article < ActiveRecord::Base
    has_many :vendors, :class_name => 'AddCurrentToVendor::Vendor', :order => 'created_at'
  end

  def self.up
    add_column :vendors, :current, :boolean
    Article.all.each do |article|
      article.vendors.first.andand.update_attribute(:current, true)
    end
  end

  def self.down
    remove_column :vendors, :current
  end
end

Notice how our two classes were namespaced into the migration class, so Vendor becomes AddCurrentToVendor::Vendor.

You can follow any response to this post through the Atom feed.

Avatar

Thu, 25 Mar 2010 21:12:00 GMT

by henning

Tags: