plugins/elixir-phoenix/skills/ecto-patterns/SKILL.md
Ecto patterns — schemas, changesets, queries, migrations, Multi, associations, preloads, upserts. Use when editing Repo calls, Ecto.Query, or schema fields. Skip for Ash.
npx skillsauth add oliver-kriska/claude-elixir-phoenix ecto-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference for working with Ecto schemas, queries, and migrations.
cast/4 for user/API input, change/2 or put_change/3 for internal trusted data:float FOR MONEY — Always use :decimal or :integer (cents)u.name == ^user_input is safe, string interpolation causes SQL injectionhas_many, JOIN FOR belongs_to — Avoids row multiplicationfrom(a in A, b in B) without on: creates Cartesian productcast_assoc WITH SHARED DATA — When multiple parents share child data, deduplicate child records BEFORE building changesets. Dedup only works within a single changesetdefmodule 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
| 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 |
| Relationship | Strategy |
|--------------|----------|
| belongs_to | JOIN (single query) |
| has_many | Separate queries (avoid row multiplication) |
| 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 |
For detailed patterns, see:
${CLAUDE_SKILL_DIR}/references/changesets.md - cast vs put_change, custom validations, prepare_changes${CLAUDE_SKILL_DIR}/references/queries.md - Composable queries, dynamic, subqueries, preloading${CLAUDE_SKILL_DIR}/references/migrations.md - Safe migrations, concurrent indexes, NOT NULL${CLAUDE_SKILL_DIR}/references/transactions.md - Repo.transact, Ecto.Multi, upsertstools
Scope or freeze which files Claude can edit during debugging, a refactor, or review. Use when edits should stay in specific dirs, or for a read-only investigate lock. Backed by a sentinel + PreToolUse hook.
development
Ash Framework — resources, actions, policies, aggregates, calculations, AshPhoenix.Form, LiveView, migrations. Use when generating resources via mix ash.codegen, editing changes, checks, types, validations, or domain code interfaces.
development
Reduce mix output noise (5-15% token savings) by installing rtk filters that compress mix test/credo/dialyzer/compile output before it reaches Claude. Use when long mix output floods context.
development
Narrow bare rescue in Elixir so real errors like KeyError and typos propagate instead of being swallowed. Use to audit rescues and refactor error handling.