DRY your Migrations

Share this article

Rails 1.1 introduced Object#with_options which allows you to remove duplication for method calls with common options, but wouldn’t it be nice if we could use this in migrations too?

For example, the following routing code:


map.connect 'atom.xml', :controller => 'feed', :action => 'everything_atom'
map.connect 'comments-atom.xml', :controller => 'feed', :action => 'comments_atom'

can use the Object#with_options magic to read:


map.with_options(:controller => 'feed') do |feed|
  feed.connect 'atom.xml', :action => 'everything_atom'
  feed.connect 'comments-atom.xml', :action => 'comments_atom'
end

Nice, eh?

Let’s have a look at a migration I wrote recently:


class AddExcerptSlugAndImageToArticles 

Argh... articles, articles everywhere! Firstly, let's come up with how we'd like the API to look:


class AddExcerptSlugAndImageToArticles 

Ahh... much better. How do we go about adding this with_table method? Firstly, we need to add the the with_table method to the ActiveRecord::Migration class:


class ActiveRecord::Migration
  def self.with_table(table_name)
    # Funky magic
  end
end

The with_table method will need to yield an object which acts just like the migration but adds the table name to the front of the argument list. The simplest way to do this is to create a new Object which we'll use as a proxy. We'll define the proxy's method_missing method from which we'll call the original migration, but with the addition of the table name to the front of the argument list. If we call the proxy with add_column :blurb, :string it will call the original migration with add_column :articles, :blurb, :string.

Putting all this together, we end up with:


class ActiveRecord::Migration
  def self.with_table(table_name)
    proxy = Object.new
    proxy.instance_variable_set(:@migration, self)
    proxy.instance_variable_set(:@table_name, table_name)
    def proxy.method_missing(method_name, *args)
      @migration.send(method_name, *(args.to_a.insert(0, @table_name)))
    end
    yield proxy
  end
end

Nice eh? Try chucking this into the top of one of your migration files and testing it out, and if you want to read more about this method_missing madness check out Chapter 6 of Why's Poignant Guide to Ruby.

Tim LucasTim Lucas
View Author
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week