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
Use this skill before modifying ANY schema, query, or migration.
^defmodule MyApp.Media.Image do
use Ecto.Schema
import Ecto.Changeset
schema "images" do
field :title, :string
field :filename, :string
field :content_type, :string
belongs_to :folder, MyApp.Media.Folder # parent uses has_many :images, MyApp.Media.Image
timestamps()
end
def changeset(image, attrs) do
image
|> cast(attrs, [:title, :filename, :content_type, :folder_id])
|> validate_required([:title, :filename, :content_type])
|> validate_length(:title, min: 1, max: 255)
|> validate_inclusion(:content_type, ["image/jpeg", "image/png", "image/gif"])
|> foreign_key_constraint(:folder_id)
end
endSee assets/changeset_snippets.ex for copy-paste changeset templates (validations, associations, and error formatting).
import Ecto.Query
def list_images_by_folder(folder_id) do
Image
|> where([i], i.folder_id == ^folder_id)
|> order_by([i], desc: i.inserted_at)
|> Repo.all()
end
def search_images(query_string) do
search = "%#{query_string}%"
Image
|> where([i], ilike(i.title, ^search))
|> Repo.all()
end❌ N+1 — avoid:
images = Repo.all(Image)
Enum.each(images, fn image -> image.folder.name end)✅ Single query with preload:
images =
Image
|> preload(:folder)
|> Repo.all()
Enum.each(images, fn image -> image.folder.name end)def transfer_images(image_ids, from_folder_id, to_folder_id) do
Repo.transaction(fn ->
with {:ok, from_folder} <- get_folder(from_folder_id),
{:ok, to_folder} <- get_folder(to_folder_id),
{count, nil} <- update_images(image_ids, to_folder_id) do
{:ok, count}
else
{:error, reason} -> Repo.rollback(reason)
_ -> Repo.rollback(:unknown_error)
end
end)
enddef create_user_with_profile(user_attrs, profile_attrs) do
Ecto.Multi.new()
|> Ecto.Multi.insert(:user, User.changeset(%User{}, user_attrs))
|> Ecto.Multi.insert(:profile, fn %{user: user} ->
Profile.changeset(%Profile{}, Map.put(profile_attrs, :user_id, user.id))
end)
|> Repo.transaction()
endOn failure, the error tuple identifies the named step: {:error, :user, changeset, _changes} or {:error, :profile, changeset, _changes}.
def add_image_to_folder(folder, image_attrs) do
folder
|> Ecto.build_assoc(:images)
|> Image.changeset(image_attrs)
|> Repo.insert()
enddef create_or_update_folder(attrs) do
%Folder{}
|> Folder.changeset(attrs)
|> Repo.insert(
on_conflict: {:replace, [:name, :updated_at]},
conflict_target: :name
)
enddef list_images(filters) do
Enum.reduce(filters, Image, fn
{:folder_id, id}, q -> where(q, [i], i.folder_id == ^id)
{:search, term}, q -> where(q, [i], ilike(i.title, ^"%#{term}%"))
{:content_type, ct}, q -> where(q, [i], i.content_type == ^ct)
_, q -> q
end)
|> Repo.all()
enddefmodule MyApp.Repo.Migrations.CreateImages do
use Ecto.Migration
def change do
create table(:images) do
add :title, :string, null: false
add :filename, :string, null: false
add :content_type, :string, null: false
add :folder_id, references(:folders, on_delete: :nilify_all)
timestamps()
end
create index(:images, [:folder_id])
create index(:images, [:inserted_at])
end
endMigration validation workflow:
mix ecto.migrate — confirm it applies without errorsmix ecto.rollback — confirm it reverses cleanlymix ecto.migrate again — confirm re-applying succeedsSee assets/migration_checklist.md for the full safe-migration and expand-contract checklist.
Add unique constraints in migration AND schema changeset.
# Migration
create unique_index(:folders, [:name])Never call Repo from the web layer (LiveViews, controllers) — all database operations belong in context modules.
defmodule MyApp.Media do
alias MyApp.Media.{Image, Folder}
alias MyApp.Repo
def create_image(attrs) do
%Image{}
|> Image.changeset(attrs)
|> Repo.insert()
end
endAll standard CRUD functions (list_*, get_*!, update_*, delete_*) follow the same pattern.
mix ecto.schema directlyecto-changeset-patterns insteadecto-migration persona instead| ❌ Don't | ✅ Do |
|---|---|
| Interpolate user input into a query fragment | Bind values with ^ so Ecto parameterizes them |
| Add a changeset validation but skip the DB constraint | Pair validate_* / unique_constraint with unique_index / references |
| Leave foreign keys unindexed | create index(:images, [:folder_id]) in the migration |
Access image.folder inside Enum.each (N+1) | preload(:folder) before Repo.all/1 |
Call Repo directly from a LiveView or controller | Route all queries through a context module |
| Combine schema changes and data backfill in one migration | Split into separate migrations (expand → backfill → contract) |
| Predecessor | This Skill | Successor |
|---|---|---|
| elixir-essentials | ecto-essentials | ecto-changeset-patterns |
| elixir-essentials | ecto-essentials | testing-essentials |
Companion skills: ecto-changeset-patterns, ecto-nested-associations, ecto-migration, testing-essentials
.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