| name | moai-lang-elixir |
| description | Elixir 1.17+ development specialist covering Phoenix 1.7, LiveView, Ecto, and OTP patterns. Use when developing real-time applications, distributed systems, or Phoenix projects. |
| version | 1.0.0 |
| category | language |
| modularized | true |
| updated | Sun Dec 07 2025 00:00:00 GMT+0000 (Coordinated Universal Time) |
| status | active |
| allowed-tools | Read, Grep, Glob, Bash, mcp__context7__resolve-library-id, mcp__context7__get-library-docs |
Quick Reference (30 seconds)
Elixir 1.17+ Development Specialist - Phoenix 1.7, LiveView, Ecto, OTP patterns, and functional programming.
Auto-Triggers: .ex, .exs files, mix.exs, config/, Phoenix/LiveView discussions
Core Capabilities:
- Elixir 1.17: Pattern matching, pipes, protocols, behaviours, macros
- Phoenix 1.7: Controllers, LiveView, Channels, PubSub, Verified Routes
- Ecto: Schemas, Changesets, Queries, Migrations, Multi
- OTP: GenServer, Supervisor, Agent, Task, Registry
- ExUnit: Testing with setup, describe, async
- Mix: Build tool, tasks, releases
- Oban: Background job processing
Quick Patterns
Phoenix Controller:
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
alias MyApp.Accounts
def show(conn, %{"id" => id}) do
user = Accounts.get_user!(id)
render(conn, :show, user: user)
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
Ecto Schema with Changeset:
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :email, :string
field :password_hash, :string
field :password, :string, virtual: true
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :email, :password])
|> validate_required([:name, :email, :password])
|> validate_format(:email, ~r/@/)
|> validate_length(:password, min: 8)
|> unique_constraint(:email)
end
end
GenServer Pattern:
defmodule MyApp.Counter do
use GenServer
def start_link(initial_value) do
GenServer.start_link(__MODULE__, initial_value, name: __MODULE__)
end
def increment, do: GenServer.call(__MODULE__, :increment)
def get_count, do: GenServer.call(__MODULE__, :get)
@impl true
def init(initial_value), do: {:ok, initial_value}
@impl true
def handle_call(:increment, _from, count), do: {:reply, count + 1, count + 1}
def handle_call(:get, _from, count), do: {:reply, count, count}
end
Implementation Guide (5 minutes)
Elixir 1.17 Features
Pattern Matching Advanced:
def process_message(%{type: :email, to: to} = message) when is_binary(to) do
send_email(message)
end
def process_message(%{type: :sms, phone: phone}) when byte_size(phone) == 10 do
send_sms(phone)
end
def process_message(_), do: {:error, :invalid_message}
Pipe Operator with Error Handling:
def process_order_safe(params) do
with {:ok, validated} <- validate_order(params),
{:ok, total} <- calculate_total(validated),
{:ok, discounted} <- apply_discounts(total),
{:ok, order} <- create_order(discounted) do
{:ok, order}
else
{:error, reason} -> {:error, reason}
end
end
Protocols for Polymorphism:
defprotocol Stringify do
@doc "Converts a data structure to string"
def to_string(data)
end
defimpl Stringify, for: Map do
def to_string(map), do: Jason.encode!(map)
end
defimpl Stringify, for: List do
def to_string(list), do: Enum.join(list, ", ")
end
Phoenix 1.7 Patterns
LiveView Component:
defmodule MyAppWeb.CounterLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0)}
end
def handle_event("increment", _, socket) do
{:noreply, update(socket, :count, &(&1 + 1))}
end
def render(assigns) do
~H"""
<div class="counter">
<h1>Count: <%= @count %></h1>
<button phx-click="increment">Increment</button>
</div>
"""
end
end
LiveView Form with Changesets:
defmodule MyAppWeb.UserFormLive do
use MyAppWeb, :live_view
alias MyApp.Accounts
alias MyApp.Accounts.User
def mount(_params, _session, socket) do
changeset = Accounts.change_user(%User{})
{:ok, assign(socket, form: to_form(changeset))}
end
def handle_event("validate", %{"user" => user_params}, socket) do
changeset =
%User{}
|> Accounts.change_user(user_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, form: to_form(changeset))}
end
def handle_event("save", %{"user" => user_params}, socket) do
case Accounts.create_user(user_params) do
{:ok, user} ->
{:noreply,
socket
|> put_flash(:info, "User created!")
|> push_navigate(to: ~p"/users/#{user}")}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
def render(assigns) do
~H"""
<.form for={@form} phx-change="validate" phx-submit="save">
<.input field={@form[:name]} label="Name" />
<.input field={@form[:email]} type="email" label="Email" />
<.button>Save</.button>
</.form>
"""
end
end
Phoenix Channels:
defmodule MyAppWeb.RoomChannel do
use MyAppWeb, :channel
@impl true
def join("room:" <> room_id, _params, socket) do
send(self(), :after_join)
{:ok, assign(socket, :room_id, room_id)}
end
@impl true
def handle_info(:after_join, socket) do
push(socket, "presence_state", MyAppWeb.Presence.list(socket))
{:noreply, socket}
end
@impl true
def handle_in("new_message", %{"body" => body}, socket) do
broadcast!(socket, "new_message", %{
user_id: socket.assigns.user_id,
body: body
})
{:noreply, socket}
end
end
Verified Routes:
# In router.ex
scope "/", MyAppWeb do
pipe_through :browser
live "/users", UserLive.Index, :index
live "/users/:id", UserLive.Show, :show
end
# Usage with ~p sigil
~p"/users" # "/users"
~p"/users/#{user}" # "/users/123"
Ecto Patterns
Multi for Transactions:
def transfer_funds(from_account, to_account, amount) do
Ecto.Multi.new()
|> Ecto.Multi.update(:withdraw, withdraw_changeset(from_account, amount))
|> Ecto.Multi.update(:deposit, deposit_changeset(to_account, amount))
|> Ecto.Multi.insert(:transaction, fn %{withdraw: from, deposit: to} ->
Transaction.changeset(%Transaction{}, %{
from_account_id: from.id,
to_account_id: to.id,
amount: amount
})
end)
|> Repo.transaction()
end
Query Composition:
defmodule MyApp.Accounts.UserQuery do
import Ecto.Query
def base, do: from(u in User)
def active(query \\ base()) do
from u in query, where: u.active == true
end
def with_posts(query \\ base()) do
from u in query, preload: [:posts]
end
end
# Usage
User
|> UserQuery.active()
|> UserQuery.with_posts()
|> Repo.all()
Advanced Implementation (10+ minutes)
For comprehensive coverage including:
- Production deployment with releases
- Distributed systems with libcluster
- Advanced LiveView patterns (streams, components)
- OTP supervision trees and dynamic supervisors
- Telemetry and observability
- Security best practices
- CI/CD integration patterns
See:
- Advanced Patterns - Complete advanced patterns guide
Context7 Library Mappings
/elixir-lang/elixir - Elixir language documentation
/phoenixframework/phoenix - Phoenix web framework
/phoenixframework/phoenix_live_view - LiveView real-time UI
/elixir-ecto/ecto - Database wrapper and query language
/sorentwo/oban - Background job processing
Works Well With
moai-domain-backend- REST API and microservices architecturemoai-domain-database- SQL patterns and query optimizationmoai-workflow-testing- TDD and testing strategiesmoai-essentials-debug- AI-powered debuggingmoai-platform-deploy- Deployment and infrastructure
Troubleshooting
Common Issues:
Elixir Version Check:
elixir --version # Should be 1.17+
mix --version # Mix build tool version
Dependency Issues:
mix deps.get # Fetch dependencies
mix deps.compile # Compile dependencies
mix clean # Clean build artifacts
Database Migrations:
mix ecto.create # Create database
mix ecto.migrate # Run migrations
mix ecto.rollback # Rollback last migration
Phoenix Server:
mix phx.server # Start server
iex -S mix phx.server # Start with IEx
MIX_ENV=prod mix release # Build release
LiveView Not Loading:
- Check websocket connection in browser console
- Verify endpoint configuration for websocket
- Ensure Phoenix.LiveView is in mix.exs dependencies
Last Updated: 2025-12-07 Status: Active (v1.0.0)