Skip to content
Back to skills

Ecto Migrations

ASecurity

Rules for Operately schema migrations (app/priv/repo/migrations/) and data migrations (app/lib/operately/data/change_*.ex). Use when adding, renaming, reviewing, or generating database migrations, ecto.gen.migration, Operately.Data.Change* modules, backfills, schema_migrations version collisions, mix ecto.migrate, or make gen.migration.

  • 562 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added August 30, 2026
businessgobashnodedatabase

Security analysis

A100/100

Scanned August 30, 2026

npx -y skills add operately/operately --skill ecto-migrations --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ecto Migrations?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ecto Migrations
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/operately-ecto-migrations/badge)](https://www.skillsdirectory.com/skills/operately-ecto-migrations)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ecto-migrations
description: >-
  Rules for Operately schema migrations (app/priv/repo/migrations/) and data
  migrations (app/lib/operately/data/change_*.ex). Use when adding, renaming,
  reviewing, or generating database migrations, ecto.gen.migration,
  Operately.Data.Change* modules, backfills, schema_migrations version
  collisions, mix ecto.migrate, or make gen.migration.
---

# Ecto Migrations

Two layers work together:

| Layer | Location | Role |
| ----- | -------- | ---- |
| Schema migration | `app/priv/repo/migrations/*.exs` | DDL and/or calls a data change; tracked in `schema_migrations` |
| Data migration | `app/lib/operately/data/change_NNN_*.ex` | Backfills and data fixes; invoked from a thin schema migration |

## Creating schema migration files

**Always** create files in `app/priv/repo/migrations/` with:

```bash
make gen.migration NAME=add_foo_to_bars
```

That runs `mix ecto.gen.migration` inside devenv (see root `Makefile` `gen.migration` target) and assigns a unique timestamp version.

**Never** create or copy migration files by hand (including inventing a
`YYYYMMDDHHMMSS_*.exs` name). Manual timestamps collide across PRs that land
close together. Ecto keys migrations by the numeric prefix only, so duplicates
cause skipped migrations, re-runs, and production failures (e.g. `duplicate_column`
or missing columns after a rename).

After generating, edit only the body of the generated file. Do not rename the
file to “fix” a conflict with another branch — coordinate timestamps via
`make gen.migration` on an up-to-date main instead.

## Data migrations

Backfills and one-off data fixes live under `app/lib/operately/data/` as
`Operately.Data.ChangeNNN…` modules. A thin schema migration (created with
`make gen.migration`) calls them:

```elixir
defmodule Operately.Repo.Migrations.BaselineDocumentVersions do
  use Ecto.Migration

  def up do
    Operately.Data.Change110BaselineDocumentVersions.run()
  end

  def down do
    :ok
  end
end
```

**Examples:** `change_110_baseline_document_versions.ex` ←
`20260720190200_baseline_document_versions.exs`;
`change_106_backfill_document_names_from_nodes.ex` ←
`20260720120100_backfill_document_names_from_nodes.exs`.

### Naming

- Next unused number: look at the highest `change_NNN_*.ex` under
  `app/lib/operately/data/` (currently in the 110s).
- Module: `Operately.Data.ChangeNNNDescriptiveName`
- File: `change_NNN_descriptive_name.ex`
- Prefer a `run/0` entry point (some older modules use `up/0`).

### Do not depend on application modules

Data migrations must **not** alias live app schemas such as
`Operately.Goals.Goal` or `Operately.Activities.Activity`. Those modules change
over time; a migration that imports them can break on fresh installs months
later.

Define **minimal inline** structs/modules inside the change module with only the
fields and helpers the migration needs.

```elixir
# ❌ DON'T: referencing application modules directly inside migrations
defmodule Operately.Data.Change155MyMigration do
  alias Operately.Repo
  alias Operately.Activities.Activity

  def run do
    from(a in Activity, where: a.action == "task_description_change")
    |> Repo.update_all(set: [content: %{}])
  end
end

# ✅ DO: declare minimal inline structs/modules needed by the migration
defmodule Operately.Data.Change155MyMigration do
  alias Operately.Repo
  alias __MODULE__.Activity

  def run do
    from(a in Activity, where: a.action == "task_description_change")
    |> Repo.update_all(set: [content: %{}])
  end

  defmodule Activity do
    use Operately.Schema

    schema "activities" do
      field :action, :string
      field :content, :map
    end
  end
end
```

**References:**
`change_082_populate_goal_description_changed_activity_goal_name.ex`,
`change_080_create_subscriptions_list_for_tasks.ex`,
`change_110_baseline_document_versions.ex`.

(Same rule is summarized in root `AGENTS.md` under Data Migration Guidelines.)

### Idempotency and tests

- Prefer idempotent `run/0` (safe if re-run or if some rows already match the
  target state).
- Add tests under `app/test/operately/data/change_NNN_*_test.exs` (see
  `change_110_baseline_document_versions_test.exs`).

## Checklist

- [ ] Schema migration created via `make gen.migration NAME=...`
- [ ] No hand-written or hand-copied `app/priv/repo/migrations/*.exs` filenames
- [ ] Version prefix is unique among existing migrations before opening the PR
- [ ] Data backfill lives in `Operately.Data.ChangeNNN…` with inline schemas only
- [ ] Schema migration’s `up/0` only delegates to `ChangeNNN.run()` (plus any DDL)
- [ ] Data change is idempotent where practical and covered by a unit test

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.