askill
ecto-migration-helper

ecto-migration-helperSafety 95Repository

Create, manage, and safely run Ecto database migrations with proper rollback handling and best practices. Use when working with database schema changes, adding columns, or modifying constraints.

0 stars
1.2k downloads
Updated 2/5/2026

Package Files

Loading files...
SKILL.md

Ecto Migration Helper

This skill helps create and manage Ecto migrations safely with proper patterns and rollback support.

When to Use

  • Creating new migrations
  • Modifying existing tables
  • Adding/removing indexes
  • Changing constraints
  • Data migrations
  • Rolling back migrations

Creating Migrations

Generate Empty Migration

mix ecto.gen.migration add_email_to_users

Creates: priv/repo/migrations/TIMESTAMP_add_email_to_users.exs

Migration Naming Conventions

  • create_table_name - Creating new table
  • add_field_to_table - Adding column
  • remove_field_from_table - Removing column
  • add_index_to_table_on_field - Adding index
  • modify_field_in_table - Changing column type
  • add_constraint_to_table - Adding constraint

Common Migration Patterns

Adding a Column

defmodule MyApp.Repo.Migrations.AddEmailToUsers do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :email, :string
    end
  end
end

Adding Column with Default

def change do
  alter table(:users) do
    add :active, :boolean, default: true, null: false
  end
end

Adding Column with Index

def change do
  alter table(:users) do
    add :email, :string
  end

  create unique_index(:users, [:email])
end

Adding Foreign Key

def change do
  alter table(:posts) do
    add :user_id, references(:users, on_delete: :delete_all), null: false
  end

  create index(:posts, [:user_id])
end

Removing a Column

def change do
  alter table(:users) do
    remove :old_field
  end
end

WARNING: Removing columns is irreversible with change. Use up/down:

def up do
  alter table(:users) do
    remove :old_field
  end
end

def down do
  alter table(:users) do
    add :old_field, :string
  end
end

Modifying Column Type

def change do
  alter table(:products) do
    modify :price, :decimal, precision: 10, scale: 2
  end
end

Renaming Column

def change do
  rename table(:users), :username, to: :name
end

Adding Composite Index

def change do
  create index(:posts, [:user_id, :published_at])
end

Adding Unique Constraint

def change do
  create unique_index(:users, [:email])
  create unique_index(:users, [:organization_id, :email])  # Composite unique
end

Adding Check Constraint

def change do
  create constraint(:products, :price_must_be_positive, check: "price > 0")
end

Safe Migration Patterns

Making Columns NOT NULL

WRONG (will fail if existing NULLs):

def change do
  alter table(:users) do
    modify :email, :string, null: false  # FAILS!
  end
end

RIGHT (two-step approach):

# Migration 1: Add default, fill NULLs
def change do
  # Set default for new rows
  alter table(:users) do
    modify :email, :string, default: "unknown@example.com"
  end

  # Fill existing NULLs
  execute(
    "UPDATE users SET email = 'unknown@example.com' WHERE email IS NULL",
    ""  # No rollback needed
  )
end

# Migration 2: Add NOT NULL constraint
def change do
  alter table(:users) do
    modify :email, :string, null: false
  end
end

Removing Columns Safely

Step 1: Deploy code that doesn't use the column Step 2: Run migration to remove column (after deployment)

# Deploy this migration AFTER code no longer references the field
def up do
  alter table(:users) do
    remove :old_field
  end
end

def down do
  alter table(:users) do
    add :old_field, :string  # Specify type for rollback
  end
end

Large Data Migrations

Use batching to avoid locking:

def up do
  execute """
  UPDATE users
  SET status = 'active'
  WHERE status IS NULL
  AND id IN (SELECT id FROM users WHERE status IS NULL LIMIT 1000)
  """

  # Repeat in batches or use recursive function
end

Data Migrations

Backfilling Data

defmodule MyApp.Repo.Migrations.BackfillUserDefaults do
  use Ecto.Migration
  import Ecto.Query
  alias MyApp.Repo
  alias MyApp.Accounts.User

  def up do
    # Use application code in migrations carefully
    User
    |> where([u], is_nil(u.status))
    |> Repo.update_all(set: [status: "active"])
  end

  def down do
    # Usually no rollback for data migrations
    :ok
  end
end

Complex Data Migration (Separate Module)

defmodule MyApp.Repo.Migrations.MigrateUserData do
  use Ecto.Migration

  def up do
    MyApp.ReleaseTasks.migrate_user_data()
  end

  def down do
    :ok
  end
end

# In lib/my_app/release_tasks.ex
defmodule MyApp.ReleaseTasks do
  def migrate_user_data do
    # Complex logic here
  end
end

Running Migrations

Development

# Run all pending migrations
mix ecto.migrate

# Run to specific version
mix ecto.migrate --to 20250101120000

# Rollback last migration
mix ecto.rollback

# Rollback last 3 migrations
mix ecto.rollback --step 3

# Rollback to specific version
mix ecto.rollback --to 20250101120000

Test Environment

# Create test database
MIX_ENV=test mix ecto.create

# Run migrations in test
MIX_ENV=test mix ecto.migrate

# Reset test database (drop, create, migrate)
MIX_ENV=test mix ecto.reset

Production

# Run on production (typically via release task)
bin/my_app eval "MyApp.ReleaseTasks.migrate()"

# Or if mix is available
MIX_ENV=prod mix ecto.migrate

Migration Status

# Check migration status
mix ecto.migrations

# Output shows:
# Status    Migration ID    Migration Name
# --------------------------------------------------
# up        20250101120000  create_users
# up        20250101130000  add_email_to_users
# down      20250101140000  add_profile_to_users

Reversible vs Non-Reversible

Reversible (use change)

  • Adding columns
  • Creating tables
  • Adding indexes
  • Adding references

Non-Reversible (use up/down)

  • Removing columns (data loss)
  • execute() with SQL
  • Data transformations
  • Dropping tables

Best Practices

1. One Logical Change Per Migration

# Good: Focused migration
mix ecto.gen.migration add_email_to_users

# Bad: Multiple unrelated changes
mix ecto.gen.migration update_users_and_posts_and_comments

2. Always Add Indexes for Foreign Keys

add :user_id, references(:users)
create index(:posts, [:user_id])  # Always add this!

3. Specify on_delete for Foreign Keys

# Be explicit about cascade behavior
add :user_id, references(:users, on_delete: :delete_all)  # Cascade
add :user_id, references(:users, on_delete: :nilify_all)  # Set NULL
add :user_id, references(:users, on_delete: :restrict)    # Prevent delete
add :user_id, references(:users, on_delete: :nothing)     # No action

4. Use Precision for Decimals

# Good
add :price, :decimal, precision: 10, scale: 2

# Bad (database decides precision)
add :price, :decimal

5. Make Constraints Explicit

# Email should be unique and not null
add :email, :string, null: false
create unique_index(:users, [:email])

6. Test Rollbacks Locally

# After creating migration
mix ecto.migrate
mix ecto.rollback
mix ecto.migrate

Troubleshooting

Migration Fails

Column already exists:

# Check current schema
mix ecto.migrations

# Drop and recreate if in development
mix ecto.drop && mix ecto.create && mix ecto.migrate

Can't rollback:

  • Check if migration uses change vs up/down
  • Review the migration for non-reversible operations
  • May need to write custom down function

Lock timeout:

# Add timeout to migration
@disable_ddl_transaction true  # For operations that can't run in transaction
@disable_migration_lock true   # For long-running migrations

def change do
  # Migration code
end

Data Migration Issues

Timeout on large tables:

  • Use batching
  • Consider running outside of migration (Rails-style rake task)
  • Use @disable_ddl_transaction true

References to application code:

  • Be careful with schema changes
  • Application code might change, migration won't
  • Consider using raw SQL for data migrations

Advanced Patterns

Concurrent Index Creation (PostgreSQL)

@disable_ddl_transaction true

def change do
  create index(:posts, [:user_id], concurrently: true)
end

Conditional Migrations

def change do
  if function_exported?(MyApp.Repo, :__adapter__, 0) do
    # Migration code
  end
end

Timestamps Helper

create table(:users) do
  add :name, :string
  timestamps()  # Adds inserted_at and updated_at
end

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

94/100Analyzed 4/6/2026

Excellent, comprehensive Ecto migration helper skill. Provides extensive coverage of migration patterns, safety best practices, code examples, and troubleshooting. Well-structured with clear when-to-use section, proper metadata, and reusable patterns. Tags (ci-cd, database, testing) improve discoverability. High-density technical reference appropriate for skill registry. No project-specific content detected.

95
90
95
95
95

Metadata

Licenseunknown
Version-
Updated2/5/2026
Publishermajiayu000

Tags

ci-cddatabasetesting