ecto-migrations
GitHub定义 Ecto 数据库迁移规范,涵盖 Schema 与 Data 迁移的创建、命名及数据回填规则。指导使用 make gen.migration 生成文件以避免版本冲突,禁止在数据迁移中依赖应用模块,确保迁移安全与一致性。
Trigger Scenarios
Install
npx skills add operately/operately --skill ecto-migrations -g -y
SKILL.md
Frontmatter
{
"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:
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:
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_*.exunderapp/lib/operately/data/(currently in the 110s). - Module:
Operately.Data.ChangeNNNDescriptiveName - File:
change_NNN_descriptive_name.ex - Prefer a
run/0entry point (some older modules useup/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.
# ❌ 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(seechange_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/*.exsfilenames - 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/0only delegates toChangeNNN.run()(plus any DDL) - Data change is idempotent where practical and covered by a unit test
Version History
- 379b061 Current 2026-08-20 12:01


