# Ecto Patterns

Use when a task touches Ecto, even a how-to question: schemas, changesets, validations...

- Repository: https://github.com/oliver-kriska/claude-elixir-phoenix (572 stars)
- Category: Writing
- License: MIT
- Last updated: 2026-10-30
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add oliver-kriska/claude-elixir-phoenix --skill ecto-patterns -g -a claude-code -y`
Codex: `npx skills add oliver-kriska/claude-elixir-phoenix --skill ecto-patterns -g -a codex -y`
Cursor: `npx skills add oliver-kriska/claude-elixir-phoenix --skill ecto-patterns -g -a cursor -y`
Gemini CLI: `npx skills add oliver-kriska/claude-elixir-phoenix --skill ecto-patterns -g -a gemini-cli -y`
Claude Code plugin marketplace: `/plugin marketplace add oliver-kriska/claude-elixir-phoenix`

## Description

Use when a task touches Ecto, even a how-to question: schemas, changesets, validations, queries, preloads, Multi, migrations, constraints, money fields. Load it before writing Ecto code. Skip for Ash.

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# Ecto Patterns Reference

Reference for working with Ecto schemas, queries, and migrations.

## Iron Laws, Never Violate These

1. **CHANGESETS ARE FOR EXTERNAL DATA**, Use `cast/4` for user/API input, `change/2` or `put_change/3` for internal trusted data
2. **NEVER USE `:float` FOR MONEY**, Always use `:decimal` or `:integer` (cents)
3. **NO RAILS-STYLE POLYMORPHIC ASSOCIATIONS**, They break foreign key constraints; use multiple nullable FKs or separate join tables
4. **ALWAYS PIN VALUES IN QUERIES**, `u.name == ^user_input` is safe, string interpolation causes SQL injection
5. **PRELOAD COLLECTIONS, NOT INDIVIDUALS**, Preloading in loops = N+1 queries
6. **CONSTRAINTS BEAT VALIDATIONS FOR RACE CONDITIONS**, Validations provide quick feedback, constraints provide DB-level safety
7. **SEPARATE QUERIES FOR `has_many`, JOIN FOR `belongs_to`**, Avoids row multiplication
8. **NO IMPLICIT CROSS JOINS**, `from(a in A, b in B)` without `on:` creates Cartesian product
9. **DEDUP BEFORE `cast_assoc` WITH SHARED DATA**, When multiple parents share child data, deduplicate child records BEFORE building changesets. Dedup only works within a single changeset

## Quick Schema Template

```elixir
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

The rest: https://github.com/oliver-kriska/claude-elixir-phoenix/blob/HEAD/plugins/elixir-phoenix/skills/ecto-patterns/SKILL.md
