Make Your Migrations Strong Again, aka strong_migrations in Action
Imagine an ordinary day in the life of an RoR developer. You are building an app that stores data about Strongmen. Your task is to store all the data about our heroes' achievements. Before, someone told us to use the strong_migrations
gem, so we followed the readme and added gem 'strong_migrations'
to our Gemfile and then ran bundle install
and rails generate strong_migrations:install
. And trust me, this gem is not added to your app just because its name contains the word strong
.
Let's say that we have a table named strongmen
. We want to add the column atlas_stones_lifted
which will tell us, how many concrete stones the competitor has lifted during his entire career. Let’s also add 0 as a default value, piece of cake!
class AddAtlasStonesLiftedToStrongmen < ActiveRecord::Migration[6.0]
def change
add_column :strongmen, :atlas_stones_lifted, :integer, default: 0
end
end
But what happened when we ran rails db:migrate
? Could our innocent migration cause errors? Strong migration keeps us away from such a mistake, we got rake aborted!
with a super detailed message.
rake aborted!
StandardError: An error has occurred, all later migrations canceled:
=== Dangerous operation detected #strong_migrations ===
Adding a column with a non-null default causes the entire table to be rewritten.
Instead, add the column without a default value, then change the default.
class AddAtlasStonesLiftedToStrongmen < ActiveRecord::Migration[6.0]
def up
add_column :strongmen, :atlas_stones_lifted, :integer
change_column_default :strongmen, :atlas_stones_lifted, 1
end
def down
remove_column :strongmen, :atlas_stones_lifted
end
end
Why did this happen? In older versions of Postgres adding a column with a default value to an existing table causes the entire table to be rewritten. Thanks to strong_migratrions
we not only got a report about the issue but also suggestions on how to resolve it. Now we can be sure that Pudzian’s effort will be saved in our application in the correct way! I encourage you to check out other examples which you can find in the readme, there are lots of perfectly described cases there.
What is it for?
So, first a little intro for those who might not know what it is. Its sole purpose is to let you know that you’re running an unsafe migration and instruct you what you can do to make it better.
How to use it?
Option 1: Add it to the codebase
First, there is the conventional way - adding it to your Gemfile
so that it runs every time database migration is run. Any migrations considered dangerous will be stopped and a warning message will be displayed with a brief instruction on what to do.
There is something worth mentioning though.
Let's assume your last production deployment was 1 week ago, you found strong_migrations
, and decided to hook it up to your project because it looks great. You add it, merge to your main branch, deploy staging - everything without any problem.
Then you want to deploy production, you start your CI, wait and... Running migrations failed because some days ago (between the last production deployment and hooking up strong_migrations
) there was a migration that is considered unsafe. So now you need to go through migrations that are to be run on production and put every unsafe one into safety_assured { }
or mark the last migration as safe using StrongMigrations.start_after
(a way better thing to do)
TL;DR
When you add ‘strong_migrations’ make sure to mark all existing migrations as safe.
Option 2: Use is as a cheat sheet
...and that’s the way I use it.
Just go through the README once and then every time you run potentially unsafe migration, go back to it treating it as a cheat sheet ;) No additional dependencies while still being safe. However, this may not be enough if the team has very little experience. Then adding the gem will probably be the best option to ensure migrations quality.
When is it too much?
If you have an application that doesn’t handle a lot of traffic, thus the table are not big, then most potentially-unsafe operations might be totally safe for your app and there’s no point wasting time going the extra mile.
Photo by Aryan Singh on Unsplash