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
Let’s have a look at a migration I wrote recently:
Argh… articles, articles everywhere! Firstly, let’s come up with how we’d like the API to look:
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
class ActiveRecord::Migration def self.with_table(table_name) # Funky magic end end
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.