Curated library of 38 atomic skills, 7 personas, and 1 orchestrator for Elixir and Phoenix development. Organized by category: fundamentals, phoenix, database, testing, auth, infrastructure, quality, security, integrations, tooling, frameworks, personas, and orchestration. Covers core Elixir patterns, Phoenix LiveView, Ecto, OTP, Oban, testing, security, deployment, real-time, and modern tooling (Req, Swoosh, Cachex, Broadway, Ash).
91
91%
Does it follow best practices?
Impact
91%
1.37xAverage score across 56 eval scenarios
Advisory
Suggest reviewing before use
registration_changeset, email_changeset, password_changeset, etc.cast_assoc child changesets — cast_assoc sets them automaticallyunsafe_validate_unique with unique_constraint — fast UI feedback plus race-safe enforcementupdate_change/3 for field transformations — trim, downcase, and slugify inside the changesetopts \\ [] for conditional validation — toggle hashing or uniqueness checks per call siteWhen adding changesets to a new schema, apply patterns in this order:
unsafe_validate_unique with unique_constraintupdate_change/3 in the changeset, not in controllersiex -S mix or running your changeset tests. A quick iex smoke test:# In iex -S mix
MyApp.Accounts.User.registration_changeset(%MyApp.Accounts.User{}, %{email: "bad", username: ""})
# => Inspect .valid? and .errors to confirm validations fire as expected
# Or a minimal ExUnit test
test "registration_changeset requires email and username" do
changeset = User.registration_changeset(%User{}, %{})
assert %{email: ["can't be blank"], username: ["can't be blank"]} = errors_on(changeset)
enddefmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :email, :string
field :username, :string
field :password, :string, virtual: true, redact: true
field :hashed_password, :string, redact: true
field :bio, :string
timestamps()
end
# Registration — all fields, password hashing
def registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email, :username, :password])
|> validate_email(opts)
|> validate_username()
|> validate_password(opts)
end
# Email change — only email
def email_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email])
|> validate_email(opts)
end
# Password change — only password
def password_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:password])
|> validate_password(opts)
|> put_password_hash()
end
# Profile update — non-sensitive fields only
def profile_changeset(user, attrs) do
user
|> cast(attrs, [:username, :bio])
|> validate_username()
end
end| ❌ Don't | ✅ Do |
|---|---|
Require :post_id in a cast_assoc child changeset | Cast only user fields; cast_assoc sets the FK |
| Reuse one changeset for every operation | Define separate named changesets per operation |
Use unsafe_validate_unique alone | Pair it with unique_constraint for race safety |
| Trim/downcase fields in the controller | Transform in the changeset with update_change/3 |
| Hardcode hashing/uniqueness behavior | Accept opts \\ [] for conditional validation |
| Validate inside context functions | Validate at the changeset level, next to the schema |
Cast the virtual :password and forget to hash | Hash via put_password_hash/1 in the password changeset |
❌ Bad — :post_id is required but set automatically by cast_assoc:
def changeset(ingredient, attrs) do
ingredient
|> cast(attrs, [:name, :quantity, :post_id])
|> validate_required([:name, :post_id]) # Fails!
end✅ Good — only require user-provided fields:
def changeset(ingredient, attrs) do
ingredient
|> cast(attrs, [:name, :quantity])
|> validate_required([:name])
enddefp validate_email(changeset, opts) do
changeset
|> validate_required([:email])
|> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "must have the @ sign and no spaces")
|> validate_length(:email, max: 160)
|> maybe_validate_unique_email(opts)
end
defp validate_username(changeset) do
changeset
|> validate_required([:username])
|> validate_format(:username, ~r/^[a-zA-Z0-9_]+$/, message: "only letters, numbers, and underscores")
|> validate_length(:username, min: 3, max: 30)
|> unsafe_validate_unique(:username, MyApp.Repo)
|> unique_constraint(:username)
end# Normal registration
def register_user(attrs) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
end
# In tests — skip hashing for speed
def register_user_for_test(attrs) do
%User{}
|> User.registration_changeset(attrs, hash_password: false, validate_email: false)
|> Repo.insert()
enddef changeset(user, attrs) do
user
|> cast(attrs, [:email, :username])
|> update_change(:email, &String.downcase/1)
|> update_change(:username, &String.trim/1)
|> update_change(:username, &String.downcase/1)
end
# For slugs
defp generate_slug(changeset) do
case get_change(changeset, :title) do
nil -> changeset
title ->
slug = title |> String.downcase() |> String.replace(~r/[^a-z0-9]+/, "-") |> String.trim("-")
put_change(changeset, :slug, slug)
end
endAlways pair unsafe_validate_unique with unique_constraint:
def changeset(user, attrs) do
user
|> cast(attrs, [:email, :username])
# Fast check — queries DB, gives immediate UI feedback
|> unsafe_validate_unique(:email, MyApp.Repo)
|> unsafe_validate_unique(:username, MyApp.Repo)
# Constraint check — catches race conditions at insert time
|> unique_constraint(:email)
|> unique_constraint(:username)
end| Predecessor | This Skill | Successor |
|---|---|---|
| ecto-essentials | ecto-changeset-patterns | ecto-nested-associations |
| ecto-essentials | ecto-changeset-patterns | testing-essentials |
Companion skills:
ecto-essentials — schema, query, and migration foundationsecto-nested-associations — cast_assoc for deeply nested data structurestesting-essentials — changeset tests with errors_on/1 helpersapply-ecto-conventions — enforce changeset conventions on existing code| Skill | When to Use |
|---|---|
ecto-essentials | Start here for schema definitions and migration patterns before writing changesets |
ecto-nested-associations | Use when cast_assoc involves deeply nested data structures |
testing-essentials | Use after this skill to write changeset tests with errors_on/1 helpers |
Each skill can be used independently if companion files are not present.
.tessl-plugin
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
scenario-45
scenario-46
scenario-47
scenario-48
scenario-49
scenario-50
scenario-51
scenario-52
scenario-53
scenario-54
scenario-55
scenario-56
skills
frameworks
ash-framework
infrastructure
orchestration
elixir-skill-router
personas
phoenix
quality
security
security-essentials
tooling
mix-tasks-generators