Enforce Zero-Downtime Migrations in Rails
Flags Rails migrations that lock PostgreSQL tables or break running servers: blocking indexes, unsafe column drops, table rewrites, inline backfills.
What it flags
class AddCustomerToInvoices < ActiveRecord::Migration[8.0]
def change
add_reference :invoices, :customer, foreign_key: true # blocks writes to invoices while the index builds
Invoice.find_each do |invoice|
invoice.update!(customer_id: invoice.account.primary_customer_id)
end
change_column_null :invoices, :customer_id, false
end
endclass AddCustomerToInvoices < ActiveRecord::Migration[8.0]
disable_ddl_transaction!
def change
add_reference :invoices, :customer, index: { algorithm: :concurrently }
add_foreign_key :invoices, :customers, validate: false
end
end
# Follow-up deploys, one step each:
# 1. BackfillInvoiceCustomersJob sets customer_id using in_batches.
# 2. A migration calls validate_foreign_key :invoices, :customers.
# 3. A migration adds a NOT VALID check constraint (customer_id IS NOT NULL)
# and validates it, so change_column_null can then skip the table scan.Why it matters
Migrations run while the previous release is still serving traffic, and on a large table a plain add_index or column type change can block writes for as long as the build or rewrite takes. Dropping a column that running processes still know about causes errors until every process restarts, because Active Record caches each table's column list. These migrations finish in seconds against a development database, so the problem first appears during a production deploy.
Agent instructions
cubic checks every pull request against these instructions. Paste them into AGENTS.md or Cursor rules and your AI coding tool follows them while it writes code.
AGENTS.md
Add this section to AGENTS.md at the repository root. Codex, Cursor, and other AI coding tools read it.
## Enforce Zero-Downtime Migrations in Rails Applies to `**/db/migrate/**/*.rb`, `**/app/models/**/*.rb`. Review Rails migrations that run on PostgreSQL for changes that lock busy tables or break app servers still running the previous release. Flag changes that: - Call `add_index`, `remove_index`, or `add_reference` (which adds an index by default) on an existing table without `algorithm: :concurrently` and `disable_ddl_transaction!`. - Add a foreign key to an existing table without `validate: false`, followed by `validate_foreign_key` in a later migration. - Call `remove_column` without first adding the column to the model's `ignored_columns` in an earlier deploy. - Call `rename_column` or `rename_table` on a table the running release still queries, instead of adding the new column or table, backfilling, and switching reads first. - Change a column's type with `change_column`, which rewrites the table for most type changes, or call `change_column_null(..., false)` on a large table, which scans it under an exclusive lock. - Backfill rows (`update_all`, `find_each` with `save`) in the same migration as schema changes, or in one unbatched statement over a large table. - Call application models from a migration; their validations, callbacks, and default scopes can change after the migration is written. Do not flag: - Indexes, references, and constraints on a table created earlier in the same migration. - `change_column_null(..., false)` after a validated `CHECK (column IS NOT NULL)` constraint, which lets PostgreSQL 12+ skip the table scan. - Apps on databases other than PostgreSQL, where `algorithm: :concurrently` does not apply. - Operations wrapped in strong_migrations' `safety_assured` with a comment explaining why they are safe.
Checks files matching **/db/migrate/**/*.rb, **/app/models/**/*.rb.
More Ruby on Rails agents
See all Ruby on Rails code review standards
Run this agent on every pull request
cubic reviews every pull request against the agents you turn on and comments on the lines that break them.