.claude_37signals/skills/migration-patterns/SKILL.md
Creates database migrations with UUIDs, account scoping, and no foreign key constraints. Use when creating tables, adding columns, modifying schema, or writing data migrations. WHEN NOT: For model business logic (see model-patterns skill). For multi-tenant scoping logic (see multi-tenant-setup skill).
npx skillsauth add ThibautBaissac/rails_ai_agents migration-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert Rails database migration architect specializing in schema design.
account_id to every multi-tenant tableSimple schemas. UUIDs everywhere. No foreign key constraints.
account_id on every table: Multi-tenancy, data isolation, query performanceTech Stack: Rails 8.2 (edge), PostgreSQL or MySQL, UUIDs via id: :uuid
Pattern: Every table has account_id, no foreign keys, simple indexes
Location: db/migrate/
bin/rails generate migration CreateCards title:string body:textbin/rails db:migrate / bin/rails db:rollbackbin/rails db:migrate:status / bin/rails db:schema:dumpclass CreateCards < ActiveRecord::Migration[8.2]
def change
create_table :cards, id: :uuid do |t|
t.references :account, null: false, type: :uuid, index: true
t.references :board, null: false, type: :uuid, index: true
t.references :creator, null: false, type: :uuid, index: true
t.string :title, null: false
t.text :body
t.string :status, default: "draft", null: false
t.integer :position
t.timestamps
end
add_index :cards, [:board_id, :position]
add_index :cards, [:account_id, :status]
# No foreign key constraints!
end
end
class CreateClosures < ActiveRecord::Migration[8.2]
def change
create_table :closures, id: :uuid do |t|
t.references :account, null: false, type: :uuid, index: true
t.references :card, null: false, type: :uuid, index: true
t.references :user, null: true, type: :uuid, index: true
t.text :reason
t.timestamps
end
add_index :closures, :card_id, unique: true
end
end
class CreateAssignments < ActiveRecord::Migration[8.2]
def change
create_table :assignments, id: :uuid do |t|
t.references :account, null: false, type: :uuid, index: true
t.references :card, null: false, type: :uuid, index: true
t.references :user, null: false, type: :uuid, index: true
t.timestamps
end
add_index :assignments, [:card_id, :user_id], unique: true
add_index :assignments, [:user_id, :card_id]
end
end
class CreateComments < ActiveRecord::Migration[8.2]
def change
create_table :comments, id: :uuid do |t|
t.references :account, null: false, type: :uuid, index: true
t.references :commentable, null: false, type: :uuid, polymorphic: true
t.references :creator, null: false, type: :uuid, index: true
t.text :body, null: false
t.timestamps
end
add_index :comments, [:commentable_type, :commentable_id]
add_index :comments, [:account_id, :created_at]
end
end
class AddColorToCards < ActiveRecord::Migration[8.2]
def change
add_column :cards, :color, :string
add_column :cards, :priority, :integer, default: 0
add_index :cards, :color
end
end
class AddParentToCards < ActiveRecord::Migration[8.2]
def change
add_reference :cards, :parent, type: :uuid, null: true, index: true
# No foreign key constraint
end
end
# Single column -- for exact matches and FK lookups
add_index :cards, :status
add_index :identities, :email_address, unique: true
# Composite -- order matters! [:a, :b] helps WHERE a=? and WHERE a=? AND b=?
add_index :cards, [:board_id, :position]
add_index :cards, [:account_id, :status]
# Unique -- enforce at database level
add_index :closures, :card_id, unique: true
add_index :assignments, [:card_id, :user_id], unique: true
# Partial (PostgreSQL) -- index subset of rows
add_index :cards, :board_id, where: "status = 'published'"
add_index :cards, :parent_id, where: "parent_id IS NOT NULL"
# Always null: false for:
t.references :account, null: false, type: :uuid # Required associations
t.string :title, null: false # Required attributes
t.string :status, default: "draft", null: false # Columns with defaults
# null: true (or omit) for:
t.references :parent, null: true, type: :uuid # Optional associations
t.text :body # Optional attributes
t.datetime :published_at # Set only when published
t.string :status, default: "draft", null: false
t.boolean :admin, default: false, null: false
t.integer :position, default: 0
t.jsonb :settings, default: {}
# No default for timestamps -- Rails handles this
CreateCards, CreateBoardPublications # Creating tables
AddColorToCards, AddParentToCards # Adding columns
RemoveClosedFromCards # Removing columns
ChangeCardPositionToBigint # Changing columns
BackfillAccountIdOnCards # Data migrations
MigrateClosedToClosures # State migrations
# Tables
create_table :cards, id: :uuid
drop_table :cards
rename_table :old_name, :new_name
# Columns
add_column :cards, :color, :string
remove_column :cards, :color
rename_column :cards, :body, :description
change_column :cards, :position, :bigint
change_column_default :cards, :status, "draft"
change_column_null :cards, :title, false
# Indexes
add_index :cards, :status
add_index :cards, [:board_id, :position]
remove_index :cards, :status
# References (no foreign_key!)
add_reference :cards, :board, type: :uuid, null: false, index: true
id: :uuid), add account_id, index foreign keys, include t.timestamps, use null: false for required fields, make migrations reversibleaccount_id on multi-tenant tables, use booleans for business statereferences/uuid-setup.md -- UUID generator config, base36 encoding, fixture UUID generationreferences/data-migrations.md -- Safe backfill patterns, zero-downtime strategiesdevelopment
Creates Turbo Streams, Turbo Frames, and morphing patterns for real-time UI updates. Use when adding real-time updates, partial page rendering, form submissions, or broadcasting. WHEN NOT: For Stimulus JavaScript controllers (see stimulus-patterns skill). For general view conventions (see rules/views.md).
testing
Writes Minitest tests with fixtures following 37signals conventions. Uses Minitest (not RSpec) and fixtures (not factories). Use when writing tests, adding test coverage, or creating fixtures. WHEN NOT: For RSpec or FactoryBot patterns (this project uses Minitest + fixtures exclusively). For test configuration/CI setup (see project docs).
tools
Builds focused, single-purpose Stimulus controllers for progressive enhancement. Use when adding JavaScript behavior, UI interactions, form enhancements, or building reusable client-side components. WHEN NOT: For Turbo Stream/Frame patterns (see turbo-patterns skill). For server-side view logic (see rules/views.md).
testing
Implements the state-as-records-not-booleans pattern for rich state tracking. Use when modeling state changes, replacing boolean flags with record-based state, or when user mentions state records, closures, publications, or toggling state. WHEN NOT: Technical flags like cached/processed (use booleans), concern extraction (use concern-patterns), general model work (use model-patterns).