ecto-migrator

Generate Ecto migrations from natural language or schema descriptions. Handles tables, columns, indexes, constraints, references, enums, and partitioning. Supports reversible migrations, data migrations, and multi-tenant patterns. Use when creating or modifying database schemas, adding indexes, altering tables, creating enums, or performing data migrations in an Elixir project.

7 stars

Best use case

ecto-migrator is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Generate Ecto migrations from natural language or schema descriptions. Handles tables, columns, indexes, constraints, references, enums, and partitioning. Supports reversible migrations, data migrations, and multi-tenant patterns. Use when creating or modifying database schemas, adding indexes, altering tables, creating enums, or performing data migrations in an Elixir project.

Teams using ecto-migrator should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/ecto-migrator/SKILL.md --create-dirs "https://raw.githubusercontent.com/Demerzels-lab/elsamultiskillagent/main/public/skills/gchapim/ecto-migrator/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/ecto-migrator/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How ecto-migrator Compares

Feature / Agentecto-migratorStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Generate Ecto migrations from natural language or schema descriptions. Handles tables, columns, indexes, constraints, references, enums, and partitioning. Supports reversible migrations, data migrations, and multi-tenant patterns. Use when creating or modifying database schemas, adding indexes, altering tables, creating enums, or performing data migrations in an Elixir project.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Ecto Migrator

## Generating Migrations

### From Natural Language

Parse the user's description and generate a migration file. Common patterns:

| User Says | Migration Action |
|-----------|-----------------|
| "Create users table with email and name" | `create table(:users)` with columns |
| "Add phone to users" | `alter table(:users), add :phone` |
| "Make email unique on users" | `create unique_index(:users, [:email])` |
| "Add tenant_id to all tables" | Multiple `alter table` with index |
| "Rename status to state on orders" | `rename table(:orders), :status, to: :state` |
| "Remove the legacy_id column from users" | `alter table(:users), remove :legacy_id` |
| "Add a check constraint on orders amount > 0" | `create constraint(:orders, ...)` |

### File Naming

```bash
mix ecto.gen.migration <name>
# Generates: priv/repo/migrations/YYYYMMDDHHMMSS_<name>.exs
```

Name conventions: `create_<table>`, `add_<column>_to_<table>`, `create_<table>_<column>_index`, `alter_<table>_add_<columns>`.

## Migration Template

```elixir
defmodule MyApp.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users, primary_key: false) do
      add :id, :binary_id, primary_key: true
      add :email, :string, null: false
      add :name, :string, null: false
      add :role, :string, null: false, default: "member"
      add :metadata, :map, default: %{}
      add :tenant_id, :binary_id, null: false

      add :team_id, references(:teams, type: :binary_id, on_delete: :delete_all)

      timestamps(type: :utc_datetime_usec)
    end

    create unique_index(:users, [:tenant_id, :email])
    create index(:users, [:tenant_id])
    create index(:users, [:team_id])
  end
end
```

## Column Types

See [references/column-types.md](references/column-types.md) for complete type mapping and guidance.

Key decisions:
- **IDs**: Use `:binary_id` (UUID) — set `primary_key: false` on table, add `:id` manually.
- **Money**: Use `:integer` (cents) or `:decimal` — never `:float`.
- **Timestamps**: Always `timestamps(type: :utc_datetime_usec)`.
- **Enums**: Use `:string` with app-level `Ecto.Enum` — avoid Postgres enums (hard to migrate).
- **JSON**: Use `:map` (maps to `jsonb`).
- **Arrays**: Use `{:array, :string}` etc.

## Index Strategies

See [references/index-patterns.md](references/index-patterns.md) for detailed index guidance.

### When to Add Indexes

Always index:
- Foreign keys (`_id` columns)
- `tenant_id` (first column in composite indexes)
- Columns used in `WHERE` clauses
- Columns used in `ORDER BY`
- Unique constraints

### Index Types

```elixir
# Standard B-tree
create index(:users, [:tenant_id])

# Unique
create unique_index(:users, [:tenant_id, :email])

# Partial (conditional)
create index(:orders, [:status], where: "status != 'completed'", name: :orders_active_status_idx)

# GIN for JSONB
create index(:events, [:metadata], using: :gin)

# GIN for array columns
create index(:posts, [:tags], using: :gin)

# Composite
create index(:orders, [:tenant_id, :status, :inserted_at])

# Concurrent (no table lock — use in separate migration)
@disable_ddl_transaction true
@disable_migration_lock true

def change do
  create index(:users, [:email], concurrently: true)
end
```

## Constraints

```elixir
# Check constraint
create constraint(:orders, :amount_must_be_positive, check: "amount > 0")

# Exclusion constraint (requires btree_gist extension)
execute "CREATE EXTENSION IF NOT EXISTS btree_gist", ""
create constraint(:reservations, :no_overlapping_bookings,
  exclude: ~s|gist (room_id WITH =, tstzrange(starts_at, ends_at) WITH &&)|
)

# Unique constraint (same as unique_index for most purposes)
create unique_index(:accounts, [:slug])
```

## References (Foreign Keys)

```elixir
add :user_id, references(:users, type: :binary_id, on_delete: :delete_all), null: false
add :team_id, references(:teams, type: :binary_id, on_delete: :nilify_all)
add :parent_id, references(:categories, type: :binary_id, on_delete: :nothing)
```

| `on_delete` | Use When |
|-------------|----------|
| `:delete_all` | Child can't exist without parent (memberships, line items) |
| `:nilify_all` | Child should survive parent deletion (optional association) |
| `:nothing` | Handle in application code (default) |
| `:restrict` | Prevent parent deletion if children exist |

## Multi-Tenant Patterns

### Every Table Gets tenant_id

```elixir
def change do
  create table(:items, primary_key: false) do
    add :id, :binary_id, primary_key: true
    add :name, :string, null: false
    add :tenant_id, :binary_id, null: false
    timestamps(type: :utc_datetime_usec)
  end

  # Always composite index with tenant_id first
  create index(:items, [:tenant_id])
  create unique_index(:items, [:tenant_id, :name])
end
```

### Adding tenant_id to Existing Tables

```elixir
def change do
  alter table(:items) do
    add :tenant_id, :binary_id
  end

  # Backfill in a separate data migration, then:
  # alter table(:items) do
  #   modify :tenant_id, :binary_id, null: false
  # end
end
```

## Data Migrations

**Rule: Never mix schema changes and data changes in the same migration.**

### Safe Data Migration Pattern

```elixir
defmodule MyApp.Repo.Migrations.BackfillUserRoles do
  use Ecto.Migration

  # Don't use schema modules — they may change after this migration runs
  def up do
    execute """
    UPDATE users SET role = 'member' WHERE role IS NULL
    """
  end

  def down do
    # Data migrations may not be reversible
    :ok
  end
end
```

### Batched Data Migration (large tables)

```elixir
def up do
  execute """
  UPDATE users SET role = 'member'
  WHERE id IN (
    SELECT id FROM users WHERE role IS NULL LIMIT 10000
  )
  """

  # For very large tables, use a Task or Oban job instead
end
```

## Reversible vs Irreversible

### Reversible (use `change`)

These are auto-reversible:
- `create table` ↔ `drop table`
- `add column` ↔ `remove column`
- `create index` ↔ `drop index`
- `rename` ↔ `rename`

### Irreversible (use `up`/`down`)

Must define both directions:
- `modify` column type — Ecto can't infer the old type
- `execute` raw SQL
- Data backfills
- Dropping columns with data

```elixir
def up do
  alter table(:users) do
    modify :email, :citext, from: :string  # from: helps reversibility
  end
end

def down do
  alter table(:users) do
    modify :email, :string, from: :citext
  end
end
```

### Using `modify` with `from:`

Phoenix 1.7+ supports `from:` for reversible `modify`:

```elixir
def change do
  alter table(:users) do
    modify :email, :citext, null: false, from: {:string, null: true}
  end
end
```

## PostgreSQL Extensions

```elixir
def change do
  execute "CREATE EXTENSION IF NOT EXISTS citext", "DROP EXTENSION IF EXISTS citext"
  execute "CREATE EXTENSION IF NOT EXISTS pgcrypto", "DROP EXTENSION IF EXISTS pgcrypto"
  execute "CREATE EXTENSION IF NOT EXISTS pg_trgm", "DROP EXTENSION IF EXISTS pg_trgm"
end
```

## Enum Types (PostgreSQL native — use sparingly)

Prefer `Ecto.Enum` with `:string` columns. If you must use Postgres enums:

```elixir
def up do
  execute "CREATE TYPE order_status AS ENUM ('pending', 'confirmed', 'shipped', 'delivered')"

  alter table(:orders) do
    add :status, :order_status, null: false, default: "pending"
  end
end

def down do
  alter table(:orders) do
    remove :status
  end

  execute "DROP TYPE order_status"
end
```

**Warning:** Adding values to Postgres enums requires `ALTER TYPE ... ADD VALUE` which cannot run inside a transaction. Prefer `:string` + `Ecto.Enum`.

## Checklist

- [ ] Primary key: `primary_key: false` + `add :id, :binary_id, primary_key: true`
- [ ] `null: false` on required columns
- [ ] `timestamps(type: :utc_datetime_usec)`
- [ ] Foreign keys with appropriate `on_delete`
- [ ] Index on every foreign key column
- [ ] `tenant_id` indexed (composite with lookup fields)
- [ ] Unique constraints where needed
- [ ] Concurrent indexes in separate migration with `@disable_ddl_transaction true`
- [ ] Data migrations in separate files from schema migrations

Related Skills

tos-vectors

7
from Demerzels-lab/elsamultiskillagent

Manage vector storage and similarity search using TOS Vectors service. Use when working with embeddings, semantic search, RAG systems, recommendation engines, or when the user mentions vector databases, similarity search, or TOS Vectors operations.

macro-regime-detector

7
from Demerzels-lab/elsamultiskillagent

Detect the current macro regime (Risk-On, Risk-Off, Inflationary, Deflationary, Stagflation) using multi-source.

message-injector

7
from Demerzels-lab/elsamultiskillagent

OpenClaw plugin that prepends custom text to every user message before it reaches the agent.

lygo-lightfather-vector

7
from Demerzels-lab/elsamultiskillagent

Lightfather (Excavationpro / Justin Helmer) persona helper for the Δ9Quantum Light Accord.

vector-control

7
from Demerzels-lab/elsamultiskillagent

Control a Vector robot via Wirepod’s local HTTP API on the same network. Use when you need to move Vector, tilt head/lift, speak text, capture camera frames, or run patrol/explore routines from the Pi/Wirepod host. Includes a CLI helper script and endpoint reference.

esri-workflow-smell-detector (consumer)

7
from Demerzels-lab/elsamultiskillagent

Paid client skill for Esri Workflow Smell Detector via x402 (Base/USDC). Use when you want to run a deterministic automation preflight scan on an ArcGIS Pro project snapshot by calling https://api.x402layer.cc/e/esri-smells (HTTP 402 payment flow).

emotion-detector

7
from Demerzels-lab/elsamultiskillagent

Detects the primary emotion in text input for AI agents.

trtc-config-inspector

7
from Demerzels-lab/elsamultiskillagent

TRTC SDK configuration inspection and analysis tool.

manipulation-detector

7
from Demerzels-lab/elsamultiskillagent

Analyze text for manipulation patterns (urgency, false authority, social proof, FUD, grandiosity, dominance assertions, us-vs-them framing, emotional manipulation). Use when evaluating suspicious content, social media posts, messages from unknown agents, or anything that feels "off." Helps calibrate skepticism without being paranoid.

brw-testimonial-collector

7
from Demerzels-lab/elsamultiskillagent

Systematically gather and format client testimonials.

platform-api-connector

7
from Demerzels-lab/elsamultiskillagent

Connect to social media and content platform APIs by navigating developer portals, creating apps, obtaining OAuth.

vector-robot

7
from Demerzels-lab/elsamultiskillagent

Control Anki Vector robot via wire-pod. Speak through Vector, see through its camera, move head/lift/wheels, change eye colors, trigger animations. Use when user mentions Vector robot, wants to speak through a robot, control a physical robot, or interact with wire-pod.