I figured I would take some time to write about a nifty feature that I'm not sure many developers know about, or have had an opportunity to use yet: the reversible method in Active Record migrations.

Introduced in Rails 4.0, reversible makes it possible to tell a migration using change (instead of up and down ) how to reverse migrations that Active Record doesn't know how to reverse by default, so that you can specify code to be executed whether migrating forward or rolling back, even inside a migration implemented within a change method.

But first, let's make sure we understand change in migrations.

The change method

The change method is the new (as of Rails 3.1) and preferred way of writing migrations, replacing the up and down methods (although you can still use them if you want to). Using change , Active Record can automatically figure out how to reverse your migration, negating the need for you to implement both the up and down methods. For example, if you have a simple migration to add a new field, like:

def change change_table :products do | t | t .string :color end end

Active Record knows that to reverse the migration, all it has to do is the equivalent of:

def down change_table :products do | t | t .remove :color end end

Or even:

remove_column :products , :color

If you have multiple instructions in the migration, Active Record will also run the inverse instructions in reverse (i.e. with the last instructions being reversed first), just as they would have been written in a down method.

However, Active Record isn't magic. It can only automatically reverse migrations with a limited set of instructions. For example, add_column , add_index , create_table , and so on can all be easily reversed, as all Active Record has to do is drop the column, index or table. It is pretty easy for Active Record to figure out the inverse of these instructions. To see how this happens, you can peruse ActiveRecord::Migration::CommandRecorder, which also documents the limitations of Active Record's automatic reversing of migrations (it isn't as complicated as it sounds).

We can see this in action. If we have a migration with the following:

def change add_column :products , :size , :string end

Then we can see Rails reversing it on rake db:rollback :

glen@~/rails_app > rake db:migrate == 20140411040853 AddSizeToProducts: migrating ================================ -- add_column(:products, :size, :string) -> 0.0005s == 20140411040853 AddSizeToProducts: migrated (0.0006s) ======================= glen@~/rails_app > rake db:rollback == 20140411040853 AddSizeToProducts: reverting ================================ -- remove_column(:products, :size, :string) -> 0.0074s == 20140411040853 AddSizeToProducts: reverted (0.0180s) =======================

However, what happens if you want to do something that Active Record can't automatically figure out how to reverse? For example, you might want to remove a column, say, a not-null decimal column (with precision and scale) called gst , as in the following:

def change change_table :products do | t | t .remove :gst end end

The inverse of that is to create the column again, adding a column called gst to the products table. But there's more than that. The gst column had other properties: the decimal type, the not-null constraint, and the precision and scale. Thus, Active Record doesn't know everything that it needs to know to automatically add the gst column again and reverse the migration. So you will be greeted with an ActiveRecord::IrreversibleMigration exception when you run rake db:rollback . This would have been the same as if you had written:

remove_column :products , :gst

Another example would be changing the precision and scale of a decimal field. Consider the following migration to change a column that is currently a not-null decimal with a precision and scale of 7 and 2, respectively:

def change change_table :products do | t | t .change :price , :decimal , :precision => 10 , :scale => 5 end end

Active Record can't automatically reverse this migration either, as it doesn't know what the previous precision and scale values were. Once again, rake db:rollback will fail with an ActiveRecord::IrreversibleMigration exception.

To handle this case, you would previously have been forced to split your migration into both up and down methods, and implement both. But now, with reversible , we no longer need to do that.

Using the reversible method

The reversible method lets you specify a block of code to be run in each direction, when migrating forward, or rolling back. The reversible method yields an instance of ActiveRecord::Migration::ReversibleBlockHelper. This class has only two very basic methods: up and down . Check out the source of this class:

class ReversibleBlockHelper < Struct .new ( :reverting ) # :nodoc: def up yield unless reverting end def down yield if reverting end end

This means that you can use reversible to define code to be executed when migrating or reverting, like so:

reversible do | dir | dir .up do # Run on rake db:migrate. end dir .down do # Run on rake db:rollback. end end

So we can now write a migration with change that Active Record knows how to reverse, meaning we don't need to implement both the up and down methods. This can be done like so:

def change reversible do | dir | change_table :products do | t | dir .up { t .change :price , :decimal , :precision => 10 , :scale => 5 } dir .down { t .change :price , :decimal , :precision => 7 , :scale => 2 } end end end

And in the case of our previously-irreversible migration to remove the gst field from the products table, we can now implement it like this, so that it is fully reversible:

def change reversible do | dir | change_table :products do | t | dir .up { t .remove :gst } dir .down { t .decimal :gst , :precision => 7 , :scale => 2 } end end end

Now that we're back at using up and down again, you might be wondering why you should use up / down blocks when you could just use up / down methods, like you used to. For starters, it can save you a bit of repetition; if you had two separate methods you would need to write out the change_table :products do...end lines twice. But more importantly, you will often find that your migrations are more complex than the ones in these examples, with more instructions. If all of the instructions in a migration were reversible except for one, it would be a pain to have to split it into two methods and write out the down method by hand. With reversible , you only need to define the inverse of the instructions that need it, while still enjoying the benefits of Active Record automatically taking care of the rest of them.

One more thing: reversible remove_column

As of Rails 4.0, remove_column can now be reversed, with just a little bit more effort. You can pass in the column's type and options (default, limit, etc.) into remove_column . It may seem a bit redundant to do so, but when used inside a migration within a change method, those options will be converted into an add_column call, and the instruction becomes reversible.

Conclusion