| name | ecto-patterns |
| description | Ecto patterns for Phoenix/Elixir apps. Covers schemas, changesets, migrations, queries, Ecto.Multi, transactions, constraints, associations, pagination, tenant partitioning, performance, and testing. |
| user-invocable | false |
| disable-model-invocation | true |
| version | 1.0.0 |
| category | toolchain |
| author | Claude MPM Team |
| license | MIT |
| progressive_disclosure | {"entry_point":{"summary":"Ecto schemas/changesets, migrations, queries, transactions, constraints, pagination, tenancy, and performance for Phoenix apps.","when_to_use":["Designing schemas and changesets with validation/constraints","Running migrations safely (DDL ordering, locks) and managing data migrations","Executing transactional flows with Ecto.Multi","Implementing pagination, preloading, and multi-tenant partitioning"],"quick_start":["mix ecto.create && mix ecto.migrate","Define schemas + changesets; enforce DB constraints (unique/foreign keys)","Use Repo.transaction / Ecto.Multi for multi-step writes","Preload associations; paginate with limit/offset or cursor libs (Scrivener/Flop)"]},"token_estimate":{"entry":170,"full":5400}} |
Ecto Patterns for Phoenix/Elixir
Ecto is the data layer for Phoenix applications: schemas, changesets, queries, migrations, and transactions. Good Ecto practice keeps domain logic in contexts, enforces constraints in the database, and uses transactions for multi-step workflows.
Schemas and Changesets
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :email, :string
field :hashed_password, :string
field :confirmed_at, :naive_datetime
has_many :memberships, MyApp.Orgs.Membership
timestamps()
end
def registration_changeset(user, attrs) do
user
|> cast(attrs, [:email, :password])
|> validate_required([:email, :password])
|> validate_format(:email, ~r/@/)
|> validate_length(:password, min: 12)
|> unique_constraint(:email)
|> hash_password()
end
defp hash_password(%{valid?: true} = cs),
do: put_change(cs, :hashed_password, Argon2.hash_pwd_salt(get_change(cs, :password)))
defp hash_password(cs), do: cs
end
Guidelines
- Keep casting/validation in changesets; keep business logic in contexts.
- Always pair validation with DB constraints (
unique_constraint, foreign_key_constraint).
- Use
changeset/2 for updates; avoid mass assigning without casting.
Migrations
def change do
create table(:users) do
add :email, :citext, null: false
add :hashed_password, :string, null: false
add :confirmed_at, :naive_datetime
timestamps()
end
create unique_index(:users, [:email])
end
Safe migration tips
- Prefer additive changes: add columns nullable, backfill, then enforce null: false.
- For large tables: use
concurrently: true for indexes; disable in change and wrap in up/down for Postgres.
- Data migrations belong in separate modules called from
mix ecto.migrate via execute/1 or in distinct scripts; ensure idempotence.
- Coordinate locks: avoid long transactions; break migrations into small steps.
Queries and Preloads
import Ecto.Query
def list_users(opts \\ %{}) do
base =
from u in MyApp.Accounts.User,
preload: [:memberships],
order_by: [desc: u.inserted_at]
Repo.all(apply_pagination(base, opts))
end
defp apply_pagination(query, %{limit: limit, offset: offset}),
do: query |> limit(^limit) |> offset(^offset)
defp apply_pagination(query, _), do: query