rails-migrations

bastos's avatarfrom bastos

This skill should be used when the user asks about "migrations", "database schema", "create_table", "add_column", "remove_column", "add_index", "foreign keys", "db:migrate", "db:rollback", "schema.rb", "change_column", "reversible", "references", "timestamps", or needs guidance on modifying database structure in Rails applications.

0stars🔀0forks📁View on GitHub🕐Updated Jan 10, 2026

When & Why to Use This Skill

This Claude skill serves as a comprehensive expert guide for managing Ruby on Rails ActiveRecord migrations. It streamlines database schema evolution by providing precise syntax and best practices for creating tables, modifying columns, managing indexes, and implementing foreign keys, ensuring robust database version control and reversibility.

Use Cases

  • Generating and configuring new database tables with specific data types, constraints, and timestamps using Rails generators.
  • Modifying existing database schemas by adding, renaming, or removing columns while maintaining migration reversibility.
  • Optimizing database performance through the implementation of composite, unique, or concurrent indexes (PostgreSQL).
  • Managing complex data relationships by defining foreign keys, polymorphic references, and 'on delete' behaviors.
  • Troubleshooting and executing database maintenance tasks using CLI commands like db:migrate, db:rollback, and db:schema:load.
namerails-migrations
descriptionThis skill should be used when the user asks about "migrations", "database schema", "create_table", "add_column", "remove_column", "add_index", "foreign keys", "db:migrate", "db:rollback", "schema.rb", "change_column", "reversible", "references", "timestamps", or needs guidance on modifying database structure in Rails applications.
version1.0.0

ActiveRecord Migrations

Comprehensive guide to creating and managing database migrations in Rails.

Creating Migrations

Generator Commands

# Standalone migration
rails generate migration AddPartNumberToProducts part_number:string

# Create table
rails generate migration CreateProducts name:string price:decimal

# Add reference/foreign key
rails generate migration AddUserRefToProducts user:references

# Add index
rails generate migration AddIndexToUsersEmail

Naming Conventions

Migration names determine auto-generated code:

Pattern Generated Code
AddColumnToTable add_column :table, :column, :type
RemoveColumnFromTable remove_column :table, :column, :type
CreateTable create_table block
AddIndexToTable add_index :table, :column

File format: YYYYMMDDHHMMSS_migration_name.rb

Column Types

Type Description
:string Short text (VARCHAR, default 255)
:text Long text (unlimited)
:integer Standard integer
:bigint Large integer (default for PKs in Rails 5.1+)
:float Floating point
:decimal Precise decimal (use for money)
:boolean True/false
:date Date only
:time Time only
:datetime Date and time
:timestamp Same as datetime
:binary Binary data
:json JSON (native if supported)
:jsonb Binary JSON (PostgreSQL)
:uuid UUID type
:references Foreign key column

Column Modifiers

add_column :products, :name, :string,
  null: false,              # NOT NULL constraint
  default: "Untitled",      # Default value
  limit: 100,               # Max length (string) or bytes (integer)
  precision: 8,             # Total digits (decimal)
  scale: 2,                 # Digits after decimal
  index: true,              # Create index
  unique: true,             # Unique index
  comment: "Product name"   # Database comment

Table Operations

Create Table

class CreateProducts < ActiveRecord::Migration[7.1]
  def change
    create_table :products do |t|
      t.string :name, null: false
      t.text :description
      t.decimal :price, precision: 10, scale: 2
      t.integer :quantity, default: 0
      t.boolean :active, default: true
      t.references :category, foreign_key: true
      t.references :user, null: false, foreign_key: true

      t.timestamps  # created_at and updated_at
    end

    add_index :products, :name
    add_index :products, [:category_id, :active]
  end
end

Modify Table

class AddFieldsToProducts < ActiveRecord::Migration[7.1]
  def change
    change_table :products do |t|
      t.string :sku
      t.remove :temporary_field
      t.rename :old_name, :new_name
      t.index :sku, unique: true
    end
  end
end

Drop Table

def change
  drop_table :products do |t|
    # Include schema for reversibility
    t.string :name
    t.timestamps
  end
end

Column Operations

Add Columns

def change
  add_column :products, :weight, :decimal, precision: 8, scale: 2
  add_column :products, :dimensions, :json
  add_column :users, :role, :string, default: "member", null: false
end

Remove Columns

def change
  # Include type for reversibility
  remove_column :products, :discontinued, :boolean, default: false
end

Rename Column

def change
  rename_column :products, :upc, :sku
end

Change Column

# Not automatically reversible - use up/down
def up
  change_column :products, :price, :decimal, precision: 12, scale: 2
  change_column_null :products, :name, false
  change_column_default :products, :status, from: nil, to: "draft"
end

def down
  change_column :products, :price, :decimal, precision: 10, scale: 2
  change_column_null :products, :name, true
  change_column_default :products, :status, from: "draft", to: nil
end

References and Foreign Keys

Add Reference

def change
  # Adds category_id column with index and foreign key
  add_reference :products, :category, foreign_key: true

  # Polymorphic reference
  add_reference :comments, :commentable, polymorphic: true, index: true

  # Without foreign key constraint
  add_reference :products, :supplier, foreign_key: false

  # Nullable reference
  add_reference :products, :brand, null: true, foreign_key: true
end

Foreign Key Options

def change
  add_foreign_key :articles, :authors

  # Custom column name
  add_foreign_key :articles, :users, column: :author_id

  # On delete behavior
  add_foreign_key :comments, :articles, on_delete: :cascade
  add_foreign_key :orders, :users, on_delete: :nullify

  # Remove foreign key
  remove_foreign_key :articles, :authors
  remove_foreign_key :articles, column: :author_id
end

Indexes

def change
  # Basic index
  add_index :users, :email

  # Unique index
  add_index :users, :email, unique: true

  # Composite index
  add_index :products, [:category_id, :status]

  # Partial index (PostgreSQL)
  add_index :orders, :shipped_at, where: "shipped_at IS NOT NULL"

  # Named index
  add_index :products, :sku, name: "idx_products_sku"

  # Remove index
  remove_index :users, :email
  remove_index :products, name: "idx_products_sku"
end

The change Method

Auto-reversible operations (use change):

  • add_column / remove_column (with type)
  • add_foreign_key / remove_foreign_key
  • add_index / remove_index
  • add_reference / remove_reference
  • add_timestamps / remove_timestamps
  • create_table / drop_table (with block)
  • create_join_table / drop_join_table
  • rename_column
  • rename_index
  • rename_table
  • change_column_default (with :from and :to)
  • change_column_null

Reversible and Up/Down

Using reversible

def change
  create_table :products do |t|
    t.string :name
    t.timestamps
  end

  reversible do |dir|
    dir.up do
      execute <<-SQL
        CREATE INDEX idx_products_search ON products
        USING gin(to_tsvector('english', name));
      SQL
    end
    dir.down do
      execute "DROP INDEX idx_products_search;"
    end
  end
end

Using up/down

def up
  change_column :products, :price, :decimal, precision: 12, scale: 2
end

def down
  change_column :products, :price, :decimal, precision: 10, scale: 2
end

Irreversible Migration

def up
  drop_table :legacy_products
end

def down
  raise ActiveRecord::IrreversibleMigration
end

Running Migrations

# Run pending migrations
rails db:migrate

# Migrate to specific version
rails db:migrate VERSION=20240101120000

# Rollback last migration
rails db:rollback

# Rollback multiple migrations
rails db:rollback STEP=3

# Redo last migration (rollback + migrate)
rails db:migrate:redo

# Check migration status
rails db:migrate:status

# Run in specific environment
rails db:migrate RAILS_ENV=production

Database Commands

# Create database
rails db:create

# Drop database
rails db:drop

# Reset (drop + create + migrate)
rails db:reset

# Setup (create + load schema + seed)
rails db:setup

# Prepare (idempotent setup)
rails db:prepare

# Load schema directly (faster than migrations)
rails db:schema:load

# Dump current schema
rails db:schema:dump

# Run seeds
rails db:seed

Safe Migration Practices

Do's

  1. Always include column type in remove_column for reversibility
  2. Use change_column_default with :from and :to
  3. Add indexes on foreign keys (automatic with references)
  4. Test rollback: rails db:migrate:redo
  5. Keep migrations small and focused

Don'ts

  1. Don't edit committed migrations - create new ones
  2. Don't use model classes directly in migrations (schema may change)
  3. Don't add data migrations - use seeds or maintenance tasks

Data Migration Alternative

# Instead of data in migrations, use:
# lib/tasks/data_migration.rake

namespace :data do
  desc "Migrate user status values"
  task migrate_status: :environment do
    User.where(status: "old_value").update_all(status: "new_value")
  end
end

Concurrent Index (PostgreSQL)

class AddIndexToUsersEmail < ActiveRecord::Migration[7.1]
  disable_ddl_transaction!

  def change
    add_index :users, :email, algorithm: :concurrently
  end
end

Additional Resources

Reference Files

  • references/migration-patterns.md - Complex migration patterns, zero-downtime migrations