Migrations keep every environment's database in sync and are part of every Rails feature you build.
A migration is a Ruby class in db/migrate that changes the schema. Generate one with a descriptive name and Rails pre-fills it:
bin/rails generate migration AddPublishedAtToArticles published_at:datetime:index
bin/rails generate model Comment article:references body:textThe change method describes the change, and Rails knows how to reverse most operations automatically:
create_table :comments do |t| ... t.timestamps end. Tables get anidprimary key by default.t.references :article, null: false, foreign_key: trueaddsarticle_id, an index and a foreign key constraint.add_column,remove_column(give the type so it stays reversible),rename_column,add_index :users, :email, unique: true,change_column_null,change_column_default.- For irreversible changes, write
upanddown, or usereversible do |dir| ... end.
Run bin/rails db:migrate; db:rollback undoes the last one; db:migrate:status shows state. db:prepare creates and migrates as needed, which is useful in deploy scripts.
After migrating, Rails dumps the full current schema to db/schema.rb. Commit it: it is the authoritative description of the database, and db:schema:load builds a fresh database from it far faster than replaying every migration.
Enforce integrity in the database too: null: false, unique indexes and foreign keys catch bugs that model validations miss, such as race conditions.
Example
# db/migrate/20250601120000_create_comments.rb
class CreateComments < ActiveRecord::Migration[8.0]
def change
create_table :comments do |t|
t.references :article, null: false, foreign_key: true
t.references :user, null: false, foreign_key: true
t.text :body, null: false
t.timestamps
end
add_index :comments, [:article_id, :created_at]
end
end
# db/migrate/20250602090000_add_slug_to_articles.rb
class AddSlugToArticles < ActiveRecord::Migration[8.0]
def change
add_column :articles, :slug, :string
add_index :articles, :slug, unique: true
end
end
# $ bin/rails db:migrate -> updates db/schema.rb
# $ bin/rails db:rollback -> reverses AddSlugToArticles automaticallyRemember
- Write changes in change; Rails reverses most of them automatically
- t.references with foreign_key: true adds the column, index and constraint
- Commit db/schema.rb; it is the authoritative schema
Watch out for
- Editing a migration that already ran on other machines instead of adding a new one.