Ecto Patterns Reference
SkillAI & modelsEcto patterns — schemas, changesets, queries, migrations, Multi, associations,
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Ecto Patterns Reference skill
What this skill tells your AI
The instructions your AI receives, as published by oliver-kriska/claude-elixir-phoenix in targets/pi/skills/ecto-patterns/SKILL.md and read by ahel’s review.
Reference for working with Ecto schemas, queries, and migrations.
Iron Laws — Never Violate These
- CHANGESETS ARE FOR EXTERNAL DATA — Use
cast/4for user/API input,change/2orput_change/3for internal trusted data - NEVER USE
:floatFOR MONEY — Always use:decimalor:integer(cents) - NO RAILS-STYLE POLYMORPHIC ASSOCIATIONS — They break foreign key constraints; use multiple nullable FKs or separate join tables
- ALWAYS PIN VALUES IN QUERIES —
u.name == ^user_inputis safe, string interpolation causes SQL injection - PRELOAD COLLECTIONS, NOT INDIVIDUALS — Preloading in loops = N+1 queries
- CONSTRAINTS BEAT VALIDATIONS FOR RACE CONDITIONS — Validations provide quick feedback, constraints provide DB-level safety
- SEPARATE QUERIES FOR
has_many, JOIN FORbelongs_to— Avoids row multiplication - NO IMPLICIT CROSS JOINS —
from(a in A, b in B)withouton:creates Cartesian product - DEDUP BEFORE
cast_assocWITH SHARED DATA — When multiple parents share child data, deduplicate child records BEFORE building changesets. Dedup only works within a single changeset
Quick Schema Template
defmodule MyApp.Context.Entity do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "entities" do
field :name, :string
field :status, Ecto.Enum, values: [:draft, :active, :archived]
field :amount_cents, :integer # Never :float for money!
belongs_to :user, MyApp.Accounts.User
timestamps(type: :utc_datetime_usec)
end
def changeset(entity, attrs) do
entity
|> cast(attrs, [:name, :status, :amount_cents])
|> validate_required([:name])
|> foreign_key_constraint(:user_id)
end
end
Quick Decisions
cast vs put_change vs change
| Function | Use When |
|---|---|
cast/4 | External data (user input, API) |
put_change/3 | Internal trusted data (timestamps, computed) |
change/2 | Internal data from existing struct |
Preload Strategy
| Relationship | Strategy |
|---|---|
belongs_to | JOIN (single query) |
has_many | Separate queries (avoid row multiplication) |
Common Anti-patterns
| Wrong | Right |
|---|---|
field :amount, :float | field :amount_cents, :integer |
"SELECT * WHERE name = '#{name}'" | from(u in User, where: u.name == ^name) |
Repo.all(User) |> Enum.filter(& &1.active) | from(u in User, where: u.active) |
| Preloading in loops | Repo.preload(posts, :comments) |
Repo.get!(User, user_id) with user input | Repo.get(User, id) + handle nil |
{:ok, _} = Repo.update(cs) inside Repo.transaction | case/with + Repo.rollback(cs) (see transactions.md) |
References
For detailed patterns, see:
references/changesets.md- cast vs put_change, custom validations, prepare_changesreferences/queries.md- Composable queries, dynamic, subqueries, preloadingreferences/migrations.md- Safe migrations, concurrent indexes, NOT NULLreferences/transactions.md- Repo.transact, Ecto.Multi, upserts
Signals
- GitHub stars
- 551
- Forks
- 39
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
ecto-patterns- Source
- github.com/oliver-kriska/claude-elixir-phoenix