Database migrations and schema management
Sarah Mitchell
Feb 2026
2 tabs
# Create table migration
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :email, null: false, index: { unique: true }
t.string :name, null: false
t.string :password_digest
t.string :role, default: 'user'
t.integer :posts_count, default: 0
t.timestamp :last_login_at
t.timestamps
end
add_index :users, :email
add_index :users, :role
end
end
# Add column migration
class AddAvatarToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :avatar_url, :string
add_column :users, :bio, :text
end
end
# Remove column
class RemoveAvatarFromUsers < ActiveRecord::Migration[7.0]
def change
remove_column :users, :avatar_url, :string
end
end
# Rename column
class RenameUserNameToFullName < ActiveRecord::Migration[7.0]
def change
rename_column :users, :name, :full_name
end
end
# Change column type
class ChangeUserBioToText < ActiveRecord::Migration[7.0]
def change
change_column :users, :bio, :text
end
end
# Add foreign key
class AddUserIdToPosts < ActiveRecord::Migration[7.0]
def change
add_reference :posts, :user, null: false, foreign_key: true, index: true
end
end
# Custom index
class AddCompoundIndexToPosts < ActiveRecord::Migration[7.0]
def change
add_index :posts, [:user_id, :status]
add_index :posts, [:status, :created_at], where: "status = 'published'"
# Full-text search index (PostgreSQL)
add_index :posts, :content, using: :gin,
opclass: :gin_trgm_ops # Requires pg_trgm extension
end
end
# Data migration
class MigrateUserRoles < ActiveRecord::Migration[7.0]
def up
User.where(admin: true).update_all(role: 'admin')
User.where(admin: false).update_all(role: 'user')
remove_column :users, :admin
end
def down
add_column :users, :admin, :boolean, default: false
User.where(role: 'admin').update_all(admin: true)
end
end
# Reversible block for complex operations
class AddStatusToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :status, :string
reversible do |dir|
dir.up do
User.update_all(status: 'active')
end
dir.down do
# Cleanup logic
end
end
change_column_null :users, :status, false
end
end
# Add constraint
class AddConstraintToUsers < ActiveRecord::Migration[7.0]
def change
add_check_constraint :users, "age >= 18", name: "users_age_check"
# Uniqueness constraint
add_index :users, :email, unique: true
end
end
# Create join table
class CreateJoinTableUsersGroups < ActiveRecord::Migration[7.0]
def change
create_join_table :users, :groups do |t|
t.index :user_id
t.index :group_id
t.timestamps
end
end
end
# Enable PostgreSQL extension
class EnablePgExtensions < ActiveRecord::Migration[7.0]
def change
enable_extension 'uuid-ossp'
enable_extension 'pg_trgm' # Trigram matching for LIKE queries
enable_extension 'hstore' # Key-value storage
end
end
# Create enum type (PostgreSQL)
class CreatePostStatusEnum < ActiveRecord::Migration[7.0]
def up
execute <<-SQL
CREATE TYPE post_status AS ENUM ('draft', 'published', 'archived');
SQL
add_column :posts, :status, :post_status, default: 'draft'
end
def down
remove_column :posts, :status
execute <<-SQL
DROP TYPE post_status;
SQL
end
end
2 files · ruby
Explain with highlit
Rails migrations evolve database schema over time. I use change method for reversible migrations. Migrations create tables, add/remove columns, add indices. up and down methods provide explicit control. Irreversible migrations like data transformations need manual down. Database constraints—foreign keys, uniqueness, not null—ensure integrity. Indices speed queries—I add them to foreign keys and frequently queried columns. migrate:rollback undoes migrations. db:migrate:status shows applied migrations. Migrations are version-controlled, enabling team coordination. I keep migrations small and focused. Understanding SQL helps write efficient schema changes. Proper indexing dramatically improves production query performance.