Irreversible Rails Migrations

What are irreversible migrations and how might we use them to our advantage?

Today I want to talk about irreversible migrations, they can be a powerful communication and safety device if used well, or a pain if you accidentally create them.

An irreversible migration is a database migration in Rails that when a rollback is attempted generates an ActiveRecord::IrreversibleMigration error.

There are some database changes that are one way. For example if you change a database column from being an integer to being a string, it won’t know what to do if you say “actually, be an integer again” and the data held within it is “hello, I am a string”. Or if you delete a database table, but don’t tell Rails how to recreate it again if it needed to.

It is these one way migrations that are irreversible.

There are two main ways to these types of errors, one is by accident, and one is by design.

Generating irreversible migrations by mistake

This is how most folk learn about irreversible migrations. They write a migration that is unintentionally irreversible and then they try and reverse it with a rails db:rollback.

I accidentally covered this in a blog post about changing the column type in a Rails migration, my original code was unintentionally irreversible.

My migration looked like this;

  def change
    change_column :customers, :phone, :string
  end

This changes the phone column on the customers table to be a string.

Note that I haven’t specified what it used to be, because of that Rails has no way of knowing how to undo this migration.

The fix, in this case, was to use up and down;

  def up
    change_column :customers, :phone, :string
  end

  def down
    change_column :customers, :phone, :integer
  end

Now Rails will know what to do during a migration, and also during a rollback.

Generating irreversible migrations on purpose

There are times you do something and you don’t want there to be a way back, even if you know how to write the code in such a way that you could come back.

For example, let’s say we were storing something against our users that we later found out we didn’t need and shouldn’t have been storing. Once that decision has been made it would be weird for our code to allow the possibility of production backing out of that decision without good reason. That is where we would use an irreversible migration.

Let’s see an example, the way you normally write a remove_column migration is to pass in the column type at the end so Rails knows how to rollback from it.

  def change
    remove_column :users, :eye_colour, :string
  end

In our case we want to force it to be an irreversible migration. We can reach for the up and down methods again.


  def up
    remove_column :users, :eye_colour
  end

  def down
    raise ActiveRecord::IrreversibleMigration, 'This migration cannot be reverted because it destroys sensitive data.'
  end

The Rails guide has another good example of irreversible migrations.

Rolling back something you’ve said you shouldn’t

If your irreversible migration has made it onto production, and you realise you want to roll it back, the easiest course of action is to create a new migration to add it again, just make sure to make it reversible!

This article is a part of the "Rails migrations" series

Recent posts View all

MacProductivity

Some Mac Tips

Some settings or tips I've learned over the years to make using your Mac an even nicer experience

Writing Git

How to speed up Rubocop

A small bit of config that could speed up your Rubocop runs