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
- Rails Migrations for Beginners
- How to change the column type with Rails migrate
- Removing fields with a Rails migration
- Running Rails migrations automatically on Heroku
- How to comment Rails migrations
- Create or remove columns or tables with Rails migrations
- Rails migrations - add default value to existing column
- What are the square brackets in my Rails migrations?
- What are Rails Migrations
- Forcing a Rails database column to be not null
- This Article