# online_migrations: catching unsafe PostgreSQL migrations in Rails

> online_migrations is a Ruby gem that stops a Rails migration which would lock a table or break a running application, and then tells you the safe way to do the same thing. It checks more than two dozen dangerous operations, from dropping a column to adding an index without the concurrent option, and it provides helpers such as add_column_with_default and background data migrations.

**fatkodima/online_migrations** — Catch unsafe PostgreSQL migrations in development and run them easier in production (code helpers for table/column renaming, changing column type, adding columns with default, background migrations, etc).

- Repository: https://github.com/fatkodima/online_migrations
- Website: https://rubydoc.info/github/fatkodima/online_migrations/
- Stars: 757 · Forks: 28
- Language: Ruby
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/fatkodima-online-migrations

## A gem that stops the migration

OnlineMigrations catches unsafe PostgreSQL migrations in development and runs them more easily in production, and the three checkmarks at the top of the README state the model: it detects potentially dangerous operations, it prevents them from running by default, and it provides instructions and helpers on safer ways to do what you want.

That third one is what separates it from a linter for SQL. A checker that only says no leaves you to work out the fix. This one prints the error and then the procedure.

The gem is Ruby, MIT licensed, and not archived, with the last push on 2026-09-23 and no GitHub releases, which is normal for a gem that is versioned through RubyGems. The documentation is on rubydoc under the gem name, and the requirements are Ruby 3.3 or newer, Rails 7.2 or newer, and PostgreSQL 12 or newer, with older versions of the gem available for older combinations.

There is a caveat in the README that is worth reading before anything else, and it is the kind of honesty that makes the rest of the documentation trustworthy. You probably do not need this gem for smaller projects, because operations that are unsafe at scale can be perfectly safe on smaller, low-traffic tables.

That single sentence decides who should adopt it. A migration that takes a table lock is a non-event on a table of two hundred rows and an outage on a table of two hundred million, and a tool that blocks the first case is a nuisance rather than a protection.

## What the gem counts as dangerous

The definition is short and it is the heart of the tool. An operation is classified as dangerous if it either blocks reads or writes for more than a few seconds after a lock is acquired, or has a good chance of causing application errors.

Those are two different failure modes, and separating them is what makes the guidance useful. A blocking operation is a latency problem, and the fix is to break it into steps that each take short locks. An application error is a compatibility problem, where the running code and the new schema disagree, and the fix is to deploy in the right order so the code tolerates both shapes for a while.

The motivation section explains why this is worth automating. Writing a safe migration can be daunting, numerous articles have been written on the topic, and a few gems are trying to address the problem, but even for someone with a pretty good command of PostgreSQL, remembering all the subtleties of explicit locking can be problematic.

That last point is the real argument. The dangerous operations are not exotic. Most of them are things an experienced developer knows to avoid and then forgets at eleven at night in a hurry. The gem does not know more than the developer, it just knows it every time.

The README also points out that there is already a gem that does something similar, the strong_migrations gem, and links a comparison section. The difference the README argues for here is the third badge, the guidance and the helpers, and the background data and schema migration features that the documentation links describe.

## More than two dozen checks

The list of operations the gem examines is long, and reading it is a good way to review your own migration habits, because every entry is a thing that is fine on a small table and catastrophic on a large one.

The core set covers removing a column, adding a column with a default value, backfilling data, changing the type of a column, renaming a column, renaming a table, creating a table with the force option, adding a check constraint, setting NOT NULL on an existing column, and executing SQL directly.

The index set is there too: adding an index non-concurrently, removing an index non-concurrently, and replacing an index. The concurrency option exists precisely so that the index build does not hold a write lock, and a migration that adds an index without it is the classic long outage in a Rails application.

The constraint set covers adding a reference, adding a foreign key, adding an exclusion constraint, and adding a unique constraint. The column set covers adding a JSON column, adding a stored generated column, using a primary key with a short integer type, and hash indexes. The table set covers adding multiple foreign keys, removing a table with multiple foreign keys, and mismatched reference column types, and there is one for adding a single table inheritance column.

There is also a config-specific check, for changing the default value of a column, and the README points at the configuring documentation for adding custom checks and for disabling specific checks.

That last point is the one to be careful with. The ability to disable a check is a necessary escape hatch, since there are legitimate cases where an operation is fine, but a project that disables several checks has lost most of the protection, and the review of that configuration is worth doing rather than leaving it to whoever needed the migration to run that afternoon.

## The safe column removal, in five steps

The example the README opens with is the error you get when a dangerous migration is detected, and the message contains the fix.

The error explains that Active Record caches database columns at runtime, so if you drop a column it can cause exceptions until your app reboots, and then it gives a five step procedure: ignore the column, deploy, wrap the column removal in a safety assured block, remove the column ignoring from the first step, and deploy again.

The unsafe version is the ordinary Rails migration, and the safe version is the same migration with one wrapper.

```ruby
   class RemoveNameFromUsers < ActiveRecord::Migration[8.0]
     def change
       safety_assured { remove_column :users, :name }
     end
   end
```

The procedure is worth understanding rather than copying, because each step exists for a reason.

Ignoring the column in the model is what tells Active Record not to select it. That is what allows the code to run against a database that no longer has the column, which is the whole point of the deploy in the middle. Once the code no longer references the column, the deploy has been rolled out everywhere, and only then is it safe to actually drop it, which is the last step.

The safety assured block is the escape hatch that tells the gem you have done the work, and the name of it is the point: it is a declaration that you have thought about this, in a place where a reviewer will see it.

The unsafe original is the same code without the wrapper, and the difference between the two is the entire value proposition of the gem in one line.

## Adding a column with a default, and why the helper exists

The second worked example is the one that changes over time with PostgreSQL itself, and the README is careful to explain the version dependency.

In earlier versions of PostgreSQL, adding a column with a non-null default value to an existing table blocks reads and writes while the entire table is rewritten. The unsafe migration is the plain Active Record one, adding a boolean column with a default of false.

```ruby
class AddAdminToUsers < ActiveRecord::Migration[8.0]
  def change
    add_column :users, :admin, :boolean, default: false
  end
end
```

In PostgreSQL 11 and later this no longer requires a table rewrite and is safe. Volatile expressions, however, such as a random call, will still result in table rewrites.

That caveat is the interesting part, because it means the gem's rules have to track the database version, and it means a migration that was safe in 2018 can become unsafe again in 2026 without anything in your application changing. A default of a constant is fine on a modern server, and a default computed by a volatile function is not, and the difference is invisible in the migration file.

The safe approach has three steps: add the column without a default value, change the column default, and backfill the existing rows with the new value. The helper takes care of all of these steps.

```ruby
class AddAdminToUsers < ActiveRecord::Migration[8.0]
  disable_ddl_transaction!

  def change
    add_column_with_default :users, :admin, :boolean, default: false
  end
end
```

The note at the end of that example is one of the more useful sentences in the README, because it is the kind of failure that produces a confusing stack trace. If you forget to call disable_ddl_transaction, the migration will fail.

The reason is that the backfill of a large table inside a single transaction holds locks for the whole operation, and the helper's job is to do the work in a way that the transaction wrapper would prevent. It is a small API with a specific failure mode, which is exactly the kind of thing a helper should have.

## Installation, the view caveat, and the upgrade step

The installation is three commands, and the first one is a line in the Gemfile.

```ruby
gem 'online_migrations'
```

```bash
$ bundle install
$ bin/rails generate online_migrations:install
$ bin/rails db:migrate
```

The install generator creates a migration, and there is a note about it. If you do not plan on using the background data migrations or the background schema migrations features, you can delete the generated migration and regenerate it later if needed.

The upgrade path has its own generator, and the reason is a schema change rather than a code change. If you are already using the background data migrations or the background schema migrations, your background migrations tables may require additional columns, so after every upgrade you run the upgrade generator and migrate.

```bash
$ bin/rails generate online_migrations:upgrade
$ bin/rails db:migrate
```

There is one infrastructure caveat that will cause confusion if you do not know it in advance, and it is about schema dumps. Since some migration helpers use database views to implement their logic, it is recommended to use the structure.sql schema format, or otherwise add a gem such as scenic to be able to dump them into the schema.rb.

In other words, the helpers create views in your database, and the default Rails schema format cannot represent them, so either change the format or add the gem that can.

The repository layout is conventional for a gem, with the library in lib, the tests in test, the documentation in docs, a gemspec, a Rakefile, a rubocop configuration, a yard options file for the generated documentation, and a gemfiles directory for testing against multiple dependency versions. The changelog and the licence file are both at the top level, and the continuous integration badge points at a test workflow that runs against the supported version matrix.

## Conclusion

online_migrations fits a Rails team on PostgreSQL whose tables have grown large enough that a locking migration is a production incident waiting to happen, and who would rather be stopped by a tool than by a customer. It does not fit a small project, and the README says so plainly, and it does not fit a team that has already standardised on another checker. Verify first that the version of Ruby, Rails, and PostgreSQL meets the stated requirements, that your schema is dumped as structure.sql or that you have a gem such as scenic in place, because the helpers use database views, and that you have read which of the checks your existing migrations would trip, since the gem is designed to make you stop and change the migration rather than to approve it silently.

## FAQ

### What does the online_migrations gem do?

It catches unsafe PostgreSQL migrations in development, prevents them from running by default, and provides instructions and code helpers on safer ways to do what you wanted. An operation is classified as dangerous if it blocks reads or writes for more than a few seconds or has a good chance of causing application errors.

### How do I install online_migrations in a Rails application?

Add the gem to the Gemfile, run bundle install, generate the install migration with the online_migrations install generator, and then run your migrations. If you are not using the background data or background schema migrations, you can delete the generated migration and regenerate it later.

### What is the safe way to remove a column with online_migrations?

Ignore the column in the model, deploy, wrap the removal in a safety_assured block, remove the column ignoring from the first step, and deploy again. The reason is that Active Record caches database columns at runtime, so dropping a column can cause exceptions until the app reboots.

### What requirements does online_migrations have?

Ruby 3.3 or newer, Rails 7.2 or newer, and PostgreSQL 12 or newer, with older versions of the gem available for older combinations. It also recommends the structure.sql schema format, or a gem such as scenic, because some helpers use database views to implement their logic.

### How do I upgrade an application that uses online_migrations?

After every upgrade run the upgrade generator and then migrate, because the background migrations tables may require additional columns. The README also notes that a strong_migrations gem already exists for the detection part, with a comparison section in the documentation.

## Sources

- [fatkodima/online_migrations on GitHub](https://github.com/fatkodima/online_migrations)
- [Issues](https://github.com/fatkodima/online_migrations/issues)
- [License: MIT](https://github.com/fatkodima/online_migrations/blob/master/LICENSE)
- [Project website](https://rubydoc.info/github/fatkodima/online_migrations/)
- [README](https://github.com/fatkodima/online_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/fatkodima-online-migrations
