用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/UitbreidenOS/UitKit --skill phoenix命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
Guidelines and instructions for Agent execution state rollback rules
Guidelines and instructions for Agent execution step counters limits
Guidelines and instructions for Agent execution timeout limits setups
基于 SOC 职业分类
| name | phoenix |
| description | Build Elixir Phoenix web applications with LiveView, channels, and Ecto |
A GenServer is a stateful process. Implement all standard callbacks explicitly:
defmodule MyApp.Cache do
use GenServer
require Logger
# --- Client API ---
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def get(key), do: GenServer.call(__MODULE__, {:get, key})
def put(key, value), do: GenServer.cast(__MODULE__, {:put, key, value})
def delete(key), do: GenServer.cast(__MODULE__, {:delete, key})
# --- Server Callbacks ---
@impl true
def init(_opts) do
{:ok, %{}} # initial state is an empty map
end
@impl true
def handle_call({:get, key}, _from, state) do
{:reply, Map.get(state, key), state}
end
@impl true
def handle_cast({:put, key, value}, state) do
{:noreply, Map.put(state, key, value)}
end
@impl true
def handle_cast({:delete, key}, state) do
{:noreply, Map.delete(state, key)}
end
@impl true
def handle_info(:cleanup, state) do
Logger.info("Running cache cleanup")
{:noreply, %{}}
end
@impl true
def terminate(reason, _state) do
Logger.warning("Cache terminating: #{inspect(reason)}")
:ok
end
end
Use handle_call for synchronous operations that return a value. Use handle_cast for fire-and-forget mutations. Use handle_info for messages not sent via call/cast (timers, process monitor messages).
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
MyApp.Repo,
MyAppWeb.Endpoint,
{MyApp.Cache, []},
{Task.Supervisor, name: MyApp.TaskSupervisor},
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
Strategy guide:
:one_for_one — restart only the crashed child. Use when children are fully independent.:one_for_all — restart all children when one crashes. Use when all children share state or must start together.:rest_for_one — restart the crashed child and every child started after it. Use for pipeline-style dependency chains.Use DynamicSupervisor for unbounded, runtime-created processes (e.g., per-user sessions, per-connection workers):
{:ok, pid} = DynamicSupervisor.start_child(MyApp.SessionSupervisor, {MyApp.Session, user_id})
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 handle_event("reset", _params, socket) do
{:noreply, assign(socket, count: 0)}
end
@impl true
def render(assigns) do
~H"""
<div>
<p>Count: <%= @count %></p>
<button phx-click="increment">+1</button>
<button phx-click="reset">Reset</button>
</div>
"""
end
end
LiveView process lifecycle:
mount/3 — called on static render (no socket) and on live socket connecthandle_params/3 — called after mount when using live navigationhandle_event/3 — handles phx-click, phx-change, phx-submithandle_info/2 — handles messages from other processes (PubSub broadcasts)For complex components, extract to Phoenix.LiveComponent to scope state and reduce parent re-renders.
Use Ecto.Multi to group multiple database operations into an atomic transaction:
defmodule MyApp.Accounts do
alias MyApp.Repo
alias MyApp.Accounts.{User, Wallet}
import Ecto.Multi
def register_user(attrs) do
Ecto.Multi.new()
|> Ecto.Multi.insert(:user, User.changeset(%User{}, attrs))
|> Ecto.Multi.insert(:wallet, fn %{user: user} ->
Wallet.changeset(%Wallet{}, %{user_id: user.id, balance: 0})
end)
|> Ecto.Multi.run(:welcome_email, fn _repo, %{user: user} ->
case Mailer.send_welcome(user) do
:ok -> {:ok, user}
{:error, reason} -> {:error, reason}
end
end)
|> Repo.transaction()
end
end
Repo.transaction/1 on a Multi returns:
{:ok, %{user: user, wallet: wallet, welcome_email: user}} on success{:error, failed_operation_name, changeset_or_value, changes_so_far} on failureEach step receives results from prior steps in the map argument. Rollback is automatic on any {:error, ...} return.
The pipe operator |> passes the result of the left expression as the first argument to the right function:
# Data transformation pipeline
def process_orders(user_id) do
user_id
|> fetch_orders()
|> Enum.filter(&(&1.status == :pending))
|> Enum.map(&calculate_total/1)
|> Enum.sort_by(& &1.amount, :desc)
|> Enum.take(10)
end
# String processing
" Hello, World! "
|> String.trim()
|> String.downcase()
|> String.split(", ")
# => ["hello", "world!"]
# Pattern matching in pipelines with with
def create_and_notify(attrs) do
with {:ok, user} <- create_user(attrs),
{:ok, _token} <- generate_token(user),
:ok <- send_notification(user) do
{:ok, user}
else
{:error, %Ecto.Changeset{} = cs} -> {:error, format_errors(cs)}
{:error, reason} -> {:error, reason}
end
end
Use with instead of nested case expressions for sequences of operations that can each fail independently.
Phoenix 7 uses the three-file config pattern:
# config/config.exs — compile-time, all environments
config :my_app, MyApp.Repo,
pool_size: 10
config :my_app, MyAppWeb.Endpoint,
url: [host: "localhost"],
render_errors: [formats: [html: MyAppWeb.ErrorHTML, json: MyAppWeb.ErrorJSON]]
# config/runtime.exs — evaluated at runtime, reads System.get_env
import Config
if config_env() == :prod do
database_url = System.fetch_env!("DATABASE_URL")
config :my_app, MyApp.Repo,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
config :my_app, MyAppWeb.Endpoint,
secret_key_base: System.fetch_env!("SECRET_KEY_BASE"),
server: true
end
# config/dev.exs — development overrides only
config :my_app, MyApp.Repo,
username: "postgres",
password: "postgres",
database: "my_app_dev"
Build a mix release:
MIX_ENV=prod mix release
_build/prod/rel/my_app/bin/my_app start
A PubSub-backed LiveView that shows real-time order updates:
defmodule MyAppWeb.OrdersLive do
use MyAppWeb, :live_view
alias MyApp.Orders
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(MyApp.PubSub, "orders")
end
{:ok, assign(socket, orders: Orders.list_recent())}
end
@impl true
def handle_info({:order_updated, order}, socket) do
updated = Enum.map(socket.assigns.orders, fn
%{id: ^(order.id)} -> order
existing -> existing
end)
{:noreply, assign(socket, orders: updated)}
end
@impl true
def render(assigns) do
~H"""
<ul>
<li :for={order <- @orders}>
<%= order.id %> — <%= order.status %>
</li>
</ul>
"""
end
end
# Broadcast from the context after an update
def update_order(id, attrs) do
with {:ok, order} <- do_update(id, attrs) do
Phoenix.PubSub.broadcast(MyApp.PubSub, "orders", {:order_updated, order})
{:ok, order}
end
end