agentsclimarketplace

Writing migrations

Skill chrisrackstallion/pitchd-rails-cursor-plugin/skills/writing-migrations

Write Rails database migrations following DHH/37signals conventions — reversible changes, database constraints as the source of integrity, safe operations on live tables, and never editing past migrations. Use when creating or modifying migrations, adding columns or indexes, changing constraints, or when the user mentions migrations, schema changes, or database columns.From its SKILL.md

Install
npx -y skills add chrisrackstallion/pitchd-rails-cursor-plugin --skill writing-migrations

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.4 KB, as published. Nobody here has run it

Writing Rails Migrations

<objective> Migrations are the authoritative record of how the database schema evolved. They should be boring, reversible where possible, and safe to run on a live production database. The schema is a contract — break it carefully, document intent in the migration name, and enforce integrity at the database level, not only in model validations. </objective>

Process

1. Determine What You're Building

TaskAction
Adding a columnRead references/patterns.md § Adding Columns
Removing a columnRead references/patterns.md § Removing Columns (three-step)
Adding an indexRead references/patterns.md § Indexes
Adding a foreign keyRead references/patterns.md § Foreign Keys
Adding a NOT NULL constraintRead references/patterns.md § Null Constraints
Data migrationRead references/patterns.md § Data Migrations
Reversibility questionRead references/patterns.md § Reversibility
Code reviewRead all references, review against conventions

2. Migration Structure

Reversible change (preferred)

Use def change when Rails can automatically reverse the migration. This is the default for adding columns, adding tables, adding indexes, and adding foreign keys:

class AddPublishedAtToArticles < ActiveRecord::Migration[8.0]
  def change
    add_column :articles, :published_at, :datetime
    add_index :articles, :published_at
  end
end

Irreversible change (explicit)

Use def up and def down when the operation cannot be automatically reversed, or when reverting requires a different operation:

class ChangeArticleStatusToEnum < ActiveRecord::Migration[8.0]
  def up
    add_column :articles, :status, :integer, default: 0, null: false
    execute "UPDATE articles SET status = 1 WHERE published = TRUE"
    remove_column :articles, :published
  end

  def down
    add_column :articles, :published, :boolean, default: false, null: false
    execute "UPDATE articles SET published = TRUE WHERE status = 1"
    remove_column :articles, :status
  end
end

Each direction carries the data across before dropping the old column — up without the UPDATE would silently reset every article to draft. A one-shot conversion like this couples DDL and DML by necessity; that is acceptable on small tables, but on large live tables use the multi-deploy pattern instead (references/patterns.md § Renaming a Column Safely).

If a migration truly cannot be reversed (data is destroyed), raise in down:

def down
  raise ActiveRecord::IrreversibleMigration
end

3. Decision Framework

"Is this operation safe on a large table?"

Some operations lock the table and block reads/writes while they run. On a small or new table this is fine. On a large production table, evaluate before running:

OperationSafetyNotes
add_column (nullable)SafeNo table rewrite
add_column with default:Safe on Postgres 11+ / MySQL 8+The database applies the default without rewriting rows — a database-version property, not a Rails one
add_column NOT NULL, no default, existing rowsUnsafe on large tablesAdd nullable first, backfill, then add constraint
add_indexUnsafe without algorithm: :concurrentlyLocks table; use concurrent index on Postgres
remove_columnRequires two-deploy processApp must ignore the column before it is removed
rename_columnUnsafeApp breaks between deploy and migration; use three-step
change_column_typeOften unsafeFull table rewrite; use new column + backfill instead

For Postgres: add_index :table, :column, algorithm: :concurrently adds the index without a full table lock. Declare disable_ddl_transaction! in the migration yourself — concurrent index builds cannot run inside a transaction, and Rails does not disable it for you.

"Does this need a data migration?"

Schema changes (DDL) and data migrations (DML) should usually be separate migrations. Run schema changes in the standard deploy; run data migrations independently, or in a separate migration file after the schema is stable.

"Is this already enforced at the model level?"

Database constraints are the source of truth for data integrity. Model validations are for user-facing error messages. Add the constraint in the migration; mirror it as a validation for clear error reporting.

4. Anti-Patterns

Anti-PatternInstead
Editing a past migrationNever — create a new migration to change the schema
Data migration in the same file as DDLSeparate migration for data; DDL is instant, DML can be slow
add_column :col, :type, null: false without a default on an existing tableAdd nullable, backfill, then add constraint
add_index without algorithm: :concurrently on Postgres production tablesUse algorithm: :concurrently and disable_ddl_transaction!
rename_column with a live appThree-step: add new column, dual-write, remove old
Putting business rules in changeSchema only in migrations; business logic belongs on models
Hardcoding data in migrationsSeeds or a separate data migration rake task
Migration that only works in one direction without def down or raise IrreversibleMigrationExplicit reversibility

5. Naming Conventions

Migration names are the permanent record of what changed. Be specific:

PatternExample
add_column_to_tableadd_published_at_to_articles
remove_column_from_tableremove_legacy_status_from_articles
create_tablecreate_publications
add_index_to_tableadd_index_to_articles_on_published_at
add_foreign_key_to_tableadd_foreign_key_to_articles_on_author_id
change_column_in_tablechange_body_to_text_in_articles
Data-only migrationbackfill_published_at_from_publications

6. Verification

Before finishing, verify:

  • Migration uses def change where reversible; def up / def down or raise ActiveRecord::IrreversibleMigration where not
  • Migration name describes exactly what changes (add_X_to_Y, not update_articles)
  • Adding NOT NULL column to existing table: added nullable first, backfill scheduled, constraint added in follow-up migration
  • Adding index to Postgres: algorithm: :concurrently with disable_ddl_transaction! for production tables
  • Removing a column: app ignores it first (deploy 1), migration removes it (deploy 2)
  • No data migration mixed with DDL (separate files)
  • Database constraints added: add_foreign_key, change_column_null, add_index unique: where data integrity requires it
  • No hardcoded IDs or environment-specific data in the migration
  • bin/rails db:migrate followed by bin/rails db:rollback passes (verify reversibility in development)
  • schema.rb (or structure.sql) committed alongside the migration

References

For detailed patterns and examples, see references/patterns.md.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.