Database Migrations
Migrations are Ruby classes that make it easy to modify your database schema over time. They're like version control for your database.
Creating Migrations
With Model Generator
bash
1rails generate model Product name:string price:decimal description:textStandalone Migration
bash
1rails generate migration AddCategoryToProducts category:stringMigration Naming Conventions
Rails infers actions from migration names:
bash
1# Add columns
2rails g migration AddFieldsToUsers age:integer bio:text
3# Generates: add_column :users, :age, :integer
4
5# Remove columns
6rails g migration RemoveAgeFromUsers age:integer
7# Generates: remove_column :users, :age
8
9# Create table
10rails g migration CreateProducts name:string
11# Generates: create_table :products
12
13# Add reference
14rails g migration AddUserRefToArticles user:references
15# Generates: add_reference :articles, :userMigration Structure
ruby
1class CreateProducts < ActiveRecord::Migration[7.1]
2 def change
3 create_table :products do |t|
4 t.string :name
5 t.decimal :price, precision: 10, scale: 2
6 t.text :description
7
8 t.timestamps
9 end
10 end
11endAvailable Column Types
ruby
1create_table :examples do |t|
2 # Strings and text
3 t.string :title # VARCHAR(255)
4 t.string :code, limit: 10 # VARCHAR(10)
5 t.text :body # TEXT
6
7 # Numbers
8 t.integer :count # INT
9 t.bigint :big_number # BIGINT
10 t.float :score # FLOAT
11 t.decimal :price, precision: 10, scale: 2 # DECIMAL(10,2)
12
13 # Boolean
14 t.boolean :active, default: true # BOOLEAN
15
16 # Date and time
17 t.date :birth_date # DATE
18 t.time :start_time # TIME
19 t.datetime :published_at # DATETIME
20 t.timestamp :last_login # TIMESTAMP
21
22 # Binary and JSON
23 t.binary :data # BLOB
24 t.json :metadata # JSON
25 t.jsonb :settings # JSONB (PostgreSQL)
26
27 # Special types
28 t.references :user, foreign_key: true # user_id with FK
29 t.belongs_to :category # Same as references
30
31 t.timestamps # created_at and updated_at
32endCommon Migration Methods
Adding Columns
ruby
1class AddFieldsToUsers < ActiveRecord::Migration[7.1]
2 def change
3 add_column :users, :phone, :string
4 add_column :users, :age, :integer, default: 0
5 add_column :users, :verified, :boolean, default: false, null: false
6 end
7endRemoving Columns
ruby
1class RemoveAgeFromUsers < ActiveRecord::Migration[7.1]
2 def change
3 remove_column :users, :age, :integer
4 end
5endRenaming Columns
ruby
1class RenameNameToFullName < ActiveRecord::Migration[7.1]
2 def change
3 rename_column :users, :name, :full_name
4 end
5endChanging Column Types
ruby
1class ChangeDescriptionToText < ActiveRecord::Migration[7.1]
2 def change
3 change_column :products, :description, :text
4 end
5end
6
7# Reversible change
8class ChangeDescriptionToText < ActiveRecord::Migration[7.1]
9 def up
10 change_column :products, :description, :text
11 end
12
13 def down
14 change_column :products, :description, :string
15 end
16endAdding Indexes
ruby
1class AddIndexToUsers < ActiveRecord::Migration[7.1]
2 def change
3 # Simple index
4 add_index :users, :email
5
6 # Unique index
7 add_index :users, :email, unique: true
8
9 # Composite index
10 add_index :articles, [:user_id, :created_at]
11
12 # Named index
13 add_index :users, :email, name: 'idx_users_email'
14
15 # Conditional index (PostgreSQL)
16 add_index :users, :email, where: "active = true"
17 end
18endAdding References
ruby
1class AddUserToArticles < ActiveRecord::Migration[7.1]
2 def change
3 add_reference :articles, :user, null: false, foreign_key: true
4 end
5endCreating Join Tables
ruby
1class CreateArticlesTags < ActiveRecord::Migration[7.1]
2 def change
3 create_join_table :articles, :tags do |t|
4 t.index :article_id
5 t.index :tag_id
6 end
7 end
8endRunning Migrations
bash
1# Run all pending migrations
2rails db:migrate
3
4# Run specific migration
5rails db:migrate VERSION=20240115000000
6
7# Rollback last migration
8rails db:rollback
9
10# Rollback multiple migrations
11rails db:rollback STEP=3
12
13# Check migration status
14rails db:migrate:status
15
16# Redo last migration (rollback + migrate)
17rails db:migrate:redo
18
19# Reset database (drop, create, migrate)
20rails db:resetReversible Migrations
ruby
1class ChangeProductsPrice < ActiveRecord::Migration[7.1]
2 def change
3 # Rails can automatically reverse these:
4 add_column :products, :discount, :decimal
5 rename_column :products, :name, :title
6 add_index :products, :title
7 end
8end
9
10# For non-reversible changes, use up/down:
11class ConvertPriceToInteger < ActiveRecord::Migration[7.1]
12 def up
13 change_column :products, :price, :integer
14 end
15
16 def down
17 change_column :products, :price, :decimal
18 end
19end
20
21# Or use reversible block:
22class AddConstraint < ActiveRecord::Migration[7.1]
23 def change
24 reversible do |dir|
25 dir.up do
26 execute "ALTER TABLE products ADD CONSTRAINT price_positive CHECK (price > 0)"
27 end
28 dir.down do
29 execute "ALTER TABLE products DROP CONSTRAINT price_positive"
30 end
31 end
32 end
33endData Migrations
Sometimes you need to migrate data too:
ruby
1class AddDefaultCategory < ActiveRecord::Migration[7.1]
2 def up
3 # Add column
4 add_column :products, :category, :string
5
6 # Update existing records
7 Product.reset_column_information
8 Product.update_all(category: 'general')
9
10 # Add constraint
11 change_column_null :products, :category, false
12 end
13
14 def down
15 remove_column :products, :category
16 end
17endSchema File
After migrations, Rails updates db/schema.rb:
ruby
1# db/schema.rb
2ActiveRecord::Schema[7.1].define(version: 2024_01_15_000000) do
3 create_table "users", force: :cascade do |t|
4 t.string "name"
5 t.string "email"
6 t.datetime "created_at", null: false
7 t.datetime "updated_at", null: false
8 t.index ["email"], name: "index_users_on_email", unique: true
9 end
10endLoad schema directly (faster than running all migrations):
bash
1rails db:schema:loadBest Practices
ruby
1# 1. Always use reversible migrations
2# Bad
3class AddStatus < ActiveRecord::Migration[7.1]
4 def change
5 execute "UPDATE users SET status = 'active'" # Not reversible!
6 end
7end
8
9# Good
10class AddStatus < ActiveRecord::Migration[7.1]
11 def up
12 execute "UPDATE users SET status = 'active'"
13 end
14 def down
15 execute "UPDATE users SET status = NULL"
16 end
17end
18
19# 2. Use change_column_null for null constraints
20change_column_null :users, :email, false
21
22# 3. Add foreign key constraints
23add_foreign_key :articles, :users
24add_foreign_key :comments, :articles, on_delete: :cascade
25
26# 4. Add indexes for foreign keys and frequently queried columns
27add_index :articles, :user_id
28add_index :users, :email, unique: trueMigrations keep your database schema in sync across all environments!
