
Database Migrations
- 1 installs
- 3 repo stars
- Updated July 14, 2026
- dchuk/source_monitor
This is a copy of database-migrations by dchuk - installs and ranking accrue to the original listing.
Helps with databases tasks.
About
database-migrations is a Claude Code skill for databases. It helps solo builders move faster with AI-assisted coding.
- database-migrations
- Databases
- AI-coding skill
Database Migrations by the numbers
- 1 all-time installs (skills.sh)
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dchuk/source_monitor --skill database-migrationsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 3 |
| Last updated | July 14, 2026 |
| Repository | dchuk/source_monitor ↗ |
What it does
Helps with databases tasks.
Files
Database Migration Patterns for Rails 8
Overview
Safe database migrations are critical for production stability:
- Zero-downtime deployments
- Reversible migrations
- Proper indexing
- Data integrity constraints
Quick Start
bin/rails generate migration AddStatusToEvents status:integer
bin/rails db:migrate
bin/rails db:rollback
bin/rails db:migrate:statusSafety Checklist
Migration Safety:
- [ ] Migration is reversible (has down or uses change)
- [ ] Large tables use batching for updates
- [ ] Indexes added concurrently (if needed)
- [ ] Foreign keys have indexes
- [ ] NOT NULL added in two steps (for existing columns)
- [ ] Default values don't lock table
- [ ] Tested rollback locallySafe Migration Patterns
Pattern 1: Add Column (Safe)
class AddStatusToEvents < ActiveRecord::Migration[8.0]
def change
add_column :events, :status, :integer, default: 0, null: false
end
endPattern 2: Add Column with NOT NULL (Two-Step)
For existing tables with data, add NOT NULL in two migrations:
# Step 1: Add column with default (allows NULL temporarily)
class AddPriorityToTasks < ActiveRecord::Migration[8.0]
def change
add_column :tasks, :priority, :integer, default: 0
end
end
# Step 2: Add NOT NULL constraint after backfill
class AddNotNullToTasksPriority < ActiveRecord::Migration[8.0]
def change
change_column_null :tasks, :priority, false
end
endPattern 3: Add Index (Production Safe)
class AddIndexToEventsStatus < ActiveRecord::Migration[8.0]
disable_ddl_transaction!
def change
add_index :events, :status, algorithm: :concurrently, if_not_exists: true
end
endPattern 4: Add Foreign Key with Index
class AddAccountToEvents < ActiveRecord::Migration[8.0]
def change
add_reference :events, :account, null: false, foreign_key: true, index: true
end
endPattern 5: Rename Column
class RenameNameToTitleOnEvents < ActiveRecord::Migration[8.0]
def change
rename_column :events, :name, :title
end
endPattern 6: Remove Column
First remove references in code, then migrate:
class RemoveLegacyFieldFromEvents < ActiveRecord::Migration[8.0]
def change
safety_assured { remove_column :events, :legacy_field, :string }
end
endPattern 7: Add Enum Column
class AddStatusEnumToOrders < ActiveRecord::Migration[8.0]
def change
add_column :orders, :status, :integer, default: 0, null: false
add_index :orders, :status
end
endIn model:
class Order < ApplicationRecord
enum :status, { pending: 0, confirmed: 1, shipped: 2, delivered: 3, cancelled: 4 }
endPattern 8: Create Table with State Record
For app-wide configuration (single-row tables):
class CreateAppConfigs < ActiveRecord::Migration[8.0]
def change
create_table :app_configs do |t|
t.string :site_name, null: false, default: "My App"
t.boolean :maintenance_mode, null: false, default: false
t.text :settings
t.timestamps
end
end
endDangerous Operations (Avoid)
DON'T: Change Column Type Directly
# DANGEROUS - can lose data or lock table
change_column :events, :budget, :decimal # DON'T DO THISDO: Add New Column, Migrate Data, Remove Old
# Step 1: Add new column
class AddBudgetDecimalToEvents < ActiveRecord::Migration[8.0]
def change
add_column :events, :budget_decimal, :decimal, precision: 10, scale: 2
end
end
# Step 2: Backfill data
class BackfillEventsBudget < ActiveRecord::Migration[8.0]
disable_ddl_transaction!
def up
Event.in_batches.update_all("budget_decimal = budget")
end
def down
# Data migration, no rollback needed
end
end
# Step 3: Remove old column (after code updated)
class RemoveOldBudgetFromEvents < ActiveRecord::Migration[8.0]
def change
safety_assured { remove_column :events, :budget, :integer }
rename_column :events, :budget_decimal, :budget
end
endData Migrations
Safe Backfill Pattern
class BackfillEventStatus < ActiveRecord::Migration[8.0]
disable_ddl_transaction!
def up
Event.unscoped.in_batches(of: 1000) do |batch|
batch.where(status: nil).update_all(status: 0)
sleep(0.1) # Reduce database load
end
end
def down
# No rollback for data migration
end
endIndex Strategies
Composite Indexes
# For queries: WHERE account_id = ? AND status = ?
add_index :events, [:account_id, :status]
# Order matters! Left-to-right prefix matching:
# Helps: WHERE account_id = ? AND status = ?
# Helps: WHERE account_id = ?
# Does NOT help: WHERE status = ?Partial Indexes
# Index only active records
add_index :events, :event_date, where: "status = 0", name: "index_events_on_date_active"
# Index only non-null values
add_index :users, :reset_token, where: "reset_token IS NOT NULL"Unique Indexes
add_index :users, :email, unique: true
add_index :event_vendors, [:event_id, :vendor_id], unique: trueForeign Keys
class AddForeignKeys < ActiveRecord::Migration[8.0]
def change
add_reference :events, :venue, foreign_key: true
add_foreign_key :events, :users, column: :organizer_id
# ON DELETE options
add_foreign_key :comments, :posts, on_delete: :cascade
add_foreign_key :posts, :users, column: :author_id, on_delete: :nullify
end
endTesting Migrations
Schema Integrity Test
# test/db/schema_test.rb
require "test_helper"
class SchemaTest < ActiveSupport::TestCase
test "all foreign keys have indexes" do
connection = ActiveRecord::Base.connection
connection.tables.each do |table|
foreign_keys = connection.foreign_keys(table)
indexes = connection.indexes(table)
foreign_keys.each do |fk|
indexed = indexes.any? { |idx| idx.columns.first == fk.column }
assert indexed, "Missing index for #{fk.column} on #{table}"
end
end
end
endRollback Test (CLI)
bin/rails db:migrate
bin/rails db:rollback
bin/rails db:migrate
bin/rails db:migrate:statusReversible Migrations
Using up/down (Manual Reversal)
class ChangeEventsStructure < ActiveRecord::Migration[8.0]
def up
execute <<-SQL
ALTER TABLE events ADD CONSTRAINT check_positive_budget
CHECK (budget_cents >= 0)
SQL
end
def down
execute <<-SQL
ALTER TABLE events DROP CONSTRAINT check_positive_budget
SQL
end
endIrreversible Migrations
class DropLegacyTable < ActiveRecord::Migration[8.0]
def up
drop_table :legacy_events
end
def down
raise ActiveRecord::IrreversibleMigration, "Cannot restore dropped table"
end
endPerformance Tips
# DON'T - Locks entire table
add_index :large_table, :column
# DO - Non-blocking
disable_ddl_transaction!
add_index :large_table, :column, algorithm: :concurrently
# DON'T - Updates all at once
Event.update_all(status: 0)
# DO - Updates in batches
Event.in_batches(of: 1000) do |batch|
batch.update_all(status: 0)
endChecklist
- [ ] Migration is reversible
- [ ] Indexes on foreign keys
- [ ] Concurrent index creation for large tables
- [ ] NOT NULL added safely (two-step)
- [ ] Data migrations use batching
- [ ] Tested rollback locally
- [ ] No table locks during deploy
Related skills
Databasesdatabases