(String: {%- set hs_blog_post_body -%} {%- set in_blog_post_body = true -%} <span id="hs_cos_wrapper_post_body" class="hs_cos_wrapper hs_cos_wrapper_meta_field hs_cos_wrapper_type_rich_text" style="" data-hs-cos-general-type="meta_field" data-hs-cos-type="rich_text"> <div class="blog-post__lead h2"> <p>There is a nice little tool I’d like to tell you about and it’s called <a href="https://github.com/ankane/strong_migrations" rel="noopener" target="_blank">strong_migrations</a>. Let’s dig into it.</p> </div></span>)

Make Your Migrations Strong Again, aka strong_migrations in Action

Photo of Janina Wilk

Janina Wilk

Updated Oct 13, 2023 • 5 min read
birds on the sky

There is a nice little tool I’d like to tell you about and it’s called strong_migrations. Let’s dig into it.

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

Photo of Janina Wilk

More posts by this author

Janina Wilk

Janina was born in Tarnowskie Gory, a small town in Silesia, which is part of Unesco list (nice!),...
How to build products fast?  We've just answered the question in our Digital Acceleration Editorial  Sign up to get access

We're Netguru!

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency
Let's talk business!

Trusted by: