.claude/skills/elixir-expert/SKILL.md
Elixir and Phoenix expert including OTP, Ecto, and functional programming
npx skillsauth add oimiragieo/agent-studio elixir-expertInstall 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.
When reviewing or writing code, apply these guidelines:
Pattern matching is fundamental to Elixir. Use it for:
Function clauses:
def greet(%User{name: name, role: :admin}), do: "Hello Admin #{name}"
def greet(%User{name: name}), do: "Hello #{name}"
def greet(_), do: "Hello stranger"
Case statements:
case {status, data} do
{:ok, %{id: id} } when id > 0 -> process(id)
{:error, reason} -> handle_error(reason)
_ -> :unknown
end
With statements for chaining:
with {:ok, user} <- fetch_user(id),
{:ok, profile} <- fetch_profile(user),
{:ok, settings} <- fetch_settings(profile) do
{:ok, %{user: user, profile: profile, settings: settings} }
end
Use guards to add constraints to pattern matching:
def categorize(n) when is_integer(n) and n > 0, do: :positive
def categorize(n) when is_integer(n) and n < 0, do: :negative
def categorize(n) when is_integer(n), do: :zero
def process_map(map) when map_size(map) == 0, do: :empty
def process_map(map) when is_map(map), do: :has_data
The pipe operator |> improves readability:
# Instead of nested calls
result = String.trim(String.downcase(String.reverse(input)))
# Use pipes
result =
input
|> String.reverse()
|> String.downcase()
|> String.trim()
Pipe into case for handling results:
user_id
|> fetch_user()
|> case do
{:ok, user} -> process_user(user)
{:error, :not_found} -> create_user()
{:error, reason} -> {:error, reason}
end
GenServer is the foundation for stateful processes:
defmodule Counter do
use GenServer
# Client API
def start_link(initial_value) do
GenServer.start_link(__MODULE__, initial_value, name: __MODULE__)
end
def increment do
GenServer.cast(__MODULE__, :increment)
end
def get do
GenServer.call(__MODULE__, :get)
end
# Server Callbacks
@impl true
def init(initial_value) do
{:ok, initial_value}
end
@impl true
def handle_cast(:increment, state) do
{:noreply, state + 1}
end
@impl true
def handle_call(:get, _from, state) do
{:reply, state, state}
end
end
Supervisors manage process lifecycles:
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children = [
# Database
MyApp.Repo,
# PubSub
{Phoenix.PubSub, name: MyApp.PubSub},
# GenServers
{MyApp.Cache, []},
{MyApp.Worker, []},
# Endpoint (starts web server)
MyAppWeb.Endpoint
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
Supervisor strategies:
:one_for_one - restart only failed child:one_for_all - restart all children if one fails:rest_for_one - restart failed child and those started after itFor simple state management:
{:ok, agent} = Agent.start_link(fn -> %{} end)
Agent.update(agent, fn state -> Map.put(state, :key, "value") end)
Agent.get(agent, fn state -> Map.get(state, :key) end)
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
def index(conn, _params) do
users = Accounts.list_users()
render(conn, :index, users: users)
end
def create(conn, %{"user" => user_params}) do
case Accounts.create_user(user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "User created successfully")
|> redirect(to: ~p"/users/#{user}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset)
end
end
end
For real-time interactive UIs:
defmodule MyAppWeb.CounterLive do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0)}
end
@impl true
def handle_event("increment", _params, socket) do
{:noreply, update(socket, :count, &(&1 + 1))}
end
@impl true
def render(assigns) do
~H"""
<div>
<h1>Count: <%= @count %></h1>
<button phx-click="increment">+</button>
</div>
"""
end
end
PubSub for broadcasting:
# Subscribe
Phoenix.PubSub.subscribe(MyApp.PubSub, "updates")
# Broadcast
Phoenix.PubSub.broadcast(MyApp.PubSub, "updates", {:new_data, data})
# Handle in LiveView
@impl true
def handle_info({:new_data, data}, socket) do
{:noreply, assign(socket, :data, data)}
end
For WebSocket communication:
defmodule MyAppWeb.RoomChannel do
use MyAppWeb, :channel
@impl true
def join("room:" <> room_id, _payload, socket) do
{:ok, assign(socket, :room_id, room_id)}
end
@impl true
def handle_in("new_message", %{"body" => body}, socket) do
broadcast!(socket, "new_message", %{body: body})
{:reply, :ok, socket}
end
end
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :email, :string
field :age, :integer
has_many :posts, MyApp.Content.Post
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :email, :age])
|> validate_required([:name, :email])
|> validate_format(:email, ~r/@/)
|> validate_number(:age, greater_than: 0)
|> unique_constraint(:email)
end
end
import Ecto.Query
# Basic queries
query = from u in User, where: u.age > 18, select: u
# Composable queries
def for_age(query, age) do
from u in query, where: u.age > ^age
end
def ordered(query) do
from u in query, order_by: [desc: u.inserted_at]
end
# Chain them
User
|> for_age(18)
|> ordered()
|> Repo.all()
# Joins and preloads
from u in User,
join: p in assoc(u, :posts),
where: p.published == true,
preload: [posts: p]
Repo.transaction(fn ->
with {:ok, user} <- create_user(params),
{:ok, profile} <- create_profile(user),
{:ok, _settings} <- create_settings(user) do
user
else
{:error, reason} -> Repo.rollback(reason)
end
end)
defmodule MyApp.AccountsTest do
use MyApp.DataCase, async: true
describe "create_user/1" do
test "creates user with valid attributes" do
attrs = %{name: "John", email: "[email protected]"}
assert {:ok, user} = Accounts.create_user(attrs)
assert user.name == "John"
end
test "returns error with invalid email" do
attrs = %{name: "John", email: "invalid"}
assert {:error, changeset} = Accounts.create_user(attrs)
assert %{email: ["has invalid format"]} = errors_on(changeset)
end
end
end
# Testing LiveView
defmodule MyAppWeb.CounterLiveTest do
use MyAppWeb.ConnCase
import Phoenix.LiveViewTest
test "increments counter", %{conn: conn} do
{:ok, view, _html} = live(conn, "/counter")
assert view |> element("button") |> render_click() =~ "Count: 1"
end
end
Use Elixir releases for production:
# mix.exs
def project do
[
releases: [
myapp: [
include_executables_for: [:unix],
steps: [:assemble, :tar]
]
]
]
end
Build and deploy:
MIX_ENV=prod mix release
_build/prod/rel/myapp/bin/myapp start
# config/runtime.exs
import Config
if config_env() == :prod do
database_url = System.get_env("DATABASE_URL") ||
raise "DATABASE_URL not available"
config :myapp, MyApp.Repo,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
end
# In your router
get "/health", HealthController, :check
# Controller
def check(conn, _params) do
case Repo.query("SELECT 1") do
{:ok, _} -> send_resp(conn, 200, "ok")
_ -> send_resp(conn, 503, "database unavailable")
end
end
This expert skill consolidates 1 individual skills:
Enum functions on potentially large streams — use Stream for lazy evaluation to avoid loading entire collections into memory.iex> examples in @doc) for public functions — doctests are runnable specifications; they document behavior and serve as regression tests.| Anti-Pattern | Why It Fails | Correct Approach |
| ------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Nested if/cond chains instead of pattern matching | Harder to read; misses the power of Elixir's pattern matching; doesn't scale | Use function clauses with pattern matching heads and guard clauses |
| Bare spawn without supervision | Crashed processes disappear silently; no restart, no visibility | Always use Supervisor trees; use Task.Supervisor for dynamic tasks |
| Enum.map/filter on large streams | Loads entire collection into memory; causes OOM on large datasets | Use Stream.map/filter for lazy, memory-efficient pipeline processing |
| Global mutable state via Process dictionary | Process dictionary is implicit state; makes code unpredictable and untestable | Use Agent, GenServer, or ETS explicitly when shared state is required |
| No doctests for public functions | Public API has no runnable specification; behavior drifts from documentation | Always add iex> examples in @doc; run with mix test |
Before starting:
cat .claude/context/memory/learnings.md
After completing: Record any new patterns or exceptions discovered.
ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.
tools
Comprehensive biosignal processing toolkit for analyzing physiological data including ECG, EEG, EDA, RSP, PPG, EMG, and EOG signals. Use this skill when processing cardiovascular signals, brain activity, electrodermal responses, respiratory patterns, muscle activity, or eye movements. Applicable for heart rate variability analysis, event-related potentials, complexity measures, autonomic nervous system assessment, psychophysiology research, and multi-modal physiological signal integration.
tools
Comprehensive toolkit for creating, analyzing, and visualizing complex networks and graphs in Python. Use when working with network/graph data structures, analyzing relationships between entities, computing graph algorithms (shortest paths, centrality, clustering), detecting communities, generating synthetic networks, or visualizing network topologies. Applicable to social networks, biological networks, transportation systems, citation networks, and any domain involving pairwise relationships.
data-ai
Molecular featurization for ML (100+ featurizers). ECFP, MACCS, descriptors, pretrained models (ChemBERTa), convert SMILES to features, for QSAR and molecular ML.
development
Run Python code in the cloud with serverless containers, GPUs, and autoscaling. Use when deploying ML models, running batch processing jobs, scheduling compute-intensive tasks, or serving APIs that require GPU acceleration or dynamic scaling.