Modifying the database schema used to be one of the most complicated tasks a software developer had to do back in the day. Thankfully ActiveRecord migrations simplified this a lot. However, even with this great tool, you need to learn how to use it properly. If you do not, there are still some scenarios in which things could go sideways. Do you remember my post about adding indexes with no downtime? That is an example of a migration that could go wrong if you do not plan it accordingly. If you do not want this to happen to you, stay with me, because in this post you will learn how you can add a “supervisor” to your project that will tell you whether your migrations are safe to go.
Strong migrations
This supervisor is no other than the strong_migrations
gem. Just let me tell you that if you are not already using it in your project you should add it right now. Strong migrations detects potentially dangerous migrations in your application and provides alternatives to make them safer. Oh! And it supports PostgreSQL, MySQL and MariaDB.
But what is a dangerous migration?
Strong migrations will consider a migration dangerous if:
- It blocks reads or writes longer than a few seconds ⏳, or
- It is very likely to cause errors 💥
Let’s see one example of each in action!
Migration that blocks reads or writes ⏳🕸
Believe it or not, but adding a column to a table with a default value used to block reads and writes to that table in some versions of PostgreSQL, MySQL and MariaDB04. blocks reads and writes
because it needed to be rewritten.
If you are adding a column with a default value like this:
1 2 3 4 5 |
class AddContentToPosts < ActiveRecord::Migration[7.0] def change add_column :posts, :content, :text, default: "Empty post" end end |
Strong migrations will complain about it and you will have to rewrite it as it follows if you are using a version earlier than Postgres 11, MySQL 8.0.12 or MariaDB 10.3.2
1 2 3 4 5 6 7 8 9 10 |
class AddContentToPosts < ActiveRecord::Migration[7.0] def up add_column :posts, :content, :text change_column_default :posts, :content, "Empty post" end def down remove_column :posts, :content end end |
Migration that is very likely to cause errors 💥
The other kind of migration that strong_migrations
will flag as dangerous is where errors are very likely to happen. For example, when you remove a column. If you had this code:
1 2 3 4 5 |
class RemoveContentFromPosts < ActiveRecord::Migration[7.0] def change remove_column :posts, :content end end |
How likely would it be that you forget to remove occurrences of Post#content
from your application and alarms start going off once you already have removed the column from your database? 😱
To avoid that strong_migrations
suggest you to do the following:
- Tell Active Record to ignore the column:
1 2 3 |
class Post < ApplicationRecord self.ignored_columns = ["content"] end |
- Deploy your code (If errors happen now you would be able to revert easily since no data has been removed yet).
- Once you are sure everything works, write your migration wrapped in a
safety_assured
block.
1 2 3 4 5 |
class RemoveContentFromPosts < ActiveRecord::Migration[7.0] def change safety_assured { remove_column :posts, :content } end end |
- Deploy and run your migration.
- Remove the line added in step 1.
And that’s it! I recommend you of course to take a look at the strong_migrations
gem. In their GitHub repository
you will find more use cases and instructions on how to solve them.
I hope you found this post valuable! If you did, please consider subscribing so that you do not miss any future updates. If you want me to write about something in particular, please let me know in the comment section below.
See you in my next post! Adiós!