Migrations and the schema

Ruby on Rails · Essentials · 12 min

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:text

The change method describes the change, and Rails knows how to reverse most operations automatically:

  • create_table :comments do |t| ... t.timestamps end. Tables get an id primary key by default.
  • t.references :article, null: false, foreign_key: true adds article_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 up and down, or use reversible 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 automatically

Remember

  • 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.

Check yourself

  1. What does t.references :article, foreign_key: true create?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: guides.rubyonrails.org/active_record_migrations.html