| name | rails-migrations |
| description | 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. |
| version | 1.0.0 |
ActiveRecord Migrations
Comprehensive guide to creating and managing database migrations in Rails.
Creating Migrations
Generator Commands
rails generate migration AddPartNumberToProducts part_number:string
rails generate migration CreateProducts name:string price:decimal
rails generate migration AddUserRefToProducts user:references
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,
default: "Untitled",
limit: 100,
precision: 8,
scale: 2,
index: true,
unique: true,
comment: "Product name"
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
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|
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
remove_column :products, :discontinued, :boolean, default: false
end
Rename Column
def change
rename_column :products, :upc, :sku
end
Change Column
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
add_reference :products, :category, foreign_key: true
add_reference :comments, :commentable, polymorphic: true, index: true
add_reference :products, :supplier, foreign_key: false
add_reference :products, :brand, null: true, foreign_key: true
end
Foreign Key Options
def change
add_foreign_key :articles, :authors
add_foreign_key :articles, :users, column: :author_id
add_foreign_key :comments, :articles, on_delete: :cascade
add_foreign_key :orders, :users, on_delete: :nullify
remove_foreign_key :articles, :authors
remove_foreign_key :articles, column: :author_id
end
Indexes
def change
add_index :users, :email
add_index :users, :email, unique: true
add_index :products, [:category_id, :status]
add_index :orders, :shipped_at, where: "shipped_at IS NOT NULL"
add_index :products, :sku, name: "idx_products_sku"
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
rails db:migrate
rails db:migrate VERSION=20240101120000
rails db:rollback
rails db:rollback STEP=3
rails db:migrate:redo
rails db:migrate:status
rails db:migrate RAILS_ENV=production
Database Commands
rails db:create
rails db:drop
rails db:reset
rails db:setup
rails db:prepare
rails db:schema:load
rails db:schema:dump
rails db:seed
Safe Migration Practices
Do's
- Always include column type in remove_column for reversibility
- Use
change_column_default with :from and :to
- Add indexes on foreign keys (automatic with
references)
- Test rollback:
rails db:migrate:redo
- Keep migrations small and focused
Don'ts
- Don't edit committed migrations - create new ones
- Don't use model classes directly in migrations (schema may change)
- Don't add data migrations - use seeds or maintenance tasks
Data Migration Alternative
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