# strong_migrations: Catching Unsafe Rails Migrations Before They Reach Production

> strong_migrations is a Rails gem that intercepts database migration operations classified as potentially dangerous, explains why each one is risky, and provides instructions for a safer alternative, preventing accidental table locks or application errors during deployment.

**ankane/strong_migrations** — Catch unsafe migrations in development

- Repository: https://github.com/ankane/strong_migrations
- Stars: 4,449 · Forks: 200
- Language: Ruby
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/ankane-strong-migrations

## The Problem: Active Record Migrations That Lock Production Tables

A Rails migration is easy to write and easy to get wrong. Adding a column, changing a type, or removing a column can lock a production table for seconds or minutes depending on the database engine, the table size, and the operation. During that lock, reads and writes either queue up or fail. On a table with millions of rows and active traffic, the outcome is visible application errors.

The second class of problem is subtler. Active Record caches column information at startup. If a column is dropped from the database while the app is still running, Active Record references a column that no longer exists. The application throws NoMethodError or AttributeError until it restarts. This is a deployment order problem: the migration runs, the app has not yet restarted, and requests fail.

strong_migrations addresses both classes of problem in development. It intercepts the Rails migration runner before any dangerous operation executes and raises an error with a detailed explanation of why the operation is risky and what to do instead. The gem does not guess at the right fix: it prints the specific steps, in code, that replace the unsafe operation with a safe one.

## Installing strong_migrations and What the Generator Does

Add the gem to the Gemfile:

```ruby
gem "strong_migrations"
```

Run the installer:

```sh
bundle install
rails generate strong_migrations:install
```

The generator creates an initializer at config/initializers/strong_migrations.rb. The README notes that strong_migrations sets a long statement timeout for migrations so that a short statement timeout can be used for the application. This separation is intentional: migration operations need longer to complete safely, but application queries should fail fast.

No database changes are needed. The gem hooks into Active Record's migration infrastructure and intercepts operations at run time in development. It does not modify the migration files themselves or add any database columns.

The README states the gem is "battle-tested at Instacart," giving an indication of the scale at which it has been validated.

## How the Error and Guidance System Works

When a developer runs a migration containing a dangerous operation, strong_migrations raises an error and prints guidance instead of executing the migration. The README reproduces the format of this output for removing a column:

```txt
=== Dangerous operation detected #strong_migrations ===

Active Record caches attributes, which causes problems
when removing columns. Be sure to ignore the column:

class User < ApplicationRecord
  self.ignored_columns += ["name"]
end

Deploy the code, then wrap this step in a safety_assured { ... } block.

class RemoveColumn < ActiveRecord::Migration[8.1]
  def change
    safety_assured { remove_column :users, :name }
  end
end
```

The guidance covers the complete multi-step process: what to do first (ignore the column in the model), when to deploy that change, and how to write the migration that removes the column afterwards. This is a practical workflow, not just a warning.

For the `safety_assured` block: wrapping any operation in safety_assured explicitly tells strong_migrations that the developer has read the guidance, understood the risk, and verified the operation is safe in their specific context. It is not a bypass for careless use; it is a declaration of intent.

## Full Catalogue of Checked Operations

The README documents the checks across three categories. The general checks apply to all supported databases:

Column operations: removing a column, changing a column's type, renaming a column. Table operations: renaming a table, creating a table with the force option (which can silently drop an existing table). Data operations: adding an auto-incrementing column, adding a stored generated column, backfilling data, executing raw SQL.

PostgreSQL-specific checks add: adding an index without CONCURRENTLY (which acquires an exclusive lock), adding a reference, adding a unique constraint, adding an exclusion constraint, adding a json column (the README recommends jsonb instead), adding a column with a volatile default value, setting NOT NULL on an existing column, renaming an enum value, and renaming a schema.

MySQL and MariaDB checks cover: using the COPY algorithm for a schema change, using shared or exclusive locking, and adding a column with an expression default value.

A best-practice check warns about non-unique indexes wider than three columns.

Custom checks can be added for organisation-specific rules, and individual checks can be disabled if a particular constraint does not apply to the project's database or deployment model.

## What Strong_migrations Does Not Do

strong_migrations detects and blocks dangerous operations. It does not provide the implementation of the safe alternative. The column-rename example illustrates this: the correct approach is a four-step, multi-deployment process (create new column, write to both, backfill, switch reads, drop old column). strong_migrations explains those steps in text but does not generate the migration files for them or provide a helper method that batches the backfill.

Batch backfilling is a common companion need: copying data from one column to another on a table with millions of rows without holding a lock. strong_migrations documents why you should not backfill in a single migration but leaves the implementation of a safe batched backfill to the developer or to a companion gem.

The gem only runs in the migration execution path. It cannot catch an operation written in a raw execute block without additional configuration, since it cannot parse arbitrary SQL. The README lists executing SQL directly as a flagged operation, but if a developer wraps it in safety_assured without reading the guidance, the check is bypassed.

## online_migrations as a Complementary Alternative

online_migrations is a Rails gem from fatkodima that also catches unsafe migration operations. The difference in approach is scope: strong_migrations focuses on detection and documentation, while online_migrations provides both detection and helper methods for performing common safe migration patterns (batched backfills, background migrations, safe column renaming steps).

For teams that want the detection layer plus ready-made helpers for the safe alternatives, online_migrations removes more manual work. For teams that prefer to write each migration explicitly and want the documentation inline, strong_migrations provides more detailed guidance per check and a simpler integration.

The two gems serve adjacent use cases. A Rails application can install both, though there would be overlap in which operations each flags. For most teams, the choice is which workflow they prefer: guided detection (strong_migrations) versus guided detection plus infrastructure helpers (online_migrations).

strong_migrations is MIT licensed, has no external dependencies beyond Rails, and works with PostgreSQL, MySQL, and MariaDB.

## Conclusion

strong_migrations suits any Rails team using PostgreSQL, MySQL, or MariaDB that runs zero-downtime deployments and cannot afford table locks or application errors during migration. It is not needed on a single-developer project with a small database where a few seconds of downtime is acceptable. Before installing, read the generated strong_migrations.rb initializer to understand the statement timeout it sets for migrations: that timeout is intentionally longer than the application's own, and adjusting it without understanding the implications can leave migrations competing with live traffic.

## FAQ

### What operations does strong_migrations catch?

strong_migrations checks for removing or renaming columns and tables, changing column types, adding indexes without CONCURRENTLY on PostgreSQL, backfilling data in a migration, creating tables with the force option, and several other database-specific operations. The README lists all checks with explanations for each database engine.

### What does safety_assured do in a Rails migration?

Wrapping an operation in safety_assured { ... } tells strong_migrations that the developer has reviewed the associated risk and determined the operation is acceptable in their specific context. It is a deliberate bypass, not an escape hatch: the README pairs it with multi-step migration guides that must be completed first.

### Does strong_migrations work with MySQL and MariaDB?

Yes. The README documents MySQL and MariaDB-specific checks for the COPY algorithm, shared or exclusive locking, and adding a column with an expression default value. The gem supports PostgreSQL, MySQL, and MariaDB.

## Sources

- [ankane/strong_migrations on GitHub](https://github.com/ankane/strong_migrations)
- [Issues](https://github.com/ankane/strong_migrations/issues)
- [License: MIT](https://github.com/ankane/strong_migrations/blob/master/LICENSE)
- [README](https://github.com/ankane/strong_migrations/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ankane-strong-migrations
