Phoenix Conventions

Phoenix framework development guidelines within the Bliss Framework methodology.

Project Structure

Standard Phoenix Layout

lib/
├── my_app/
│   ├── application.ex          # OTP Application
│   ├── repo.ex                # Ecto Repository
│   └── accounts/              # Domain Context
│       ├── user.ex           # Schema
│       └── user_token.ex     # Schema
├── my_app_web/
│   ├── endpoint.ex           # HTTP Endpoint
│   ├── router.ex             # URL Routing
│   ├── telemetry.ex          # Metrics
│   ├── controllers/          # HTTP Controllers
│   ├── views/                # View Logic
│   ├── templates/            # HTML Templates
│   └── live/                 # LiveView Components

Context Organization

Domain Contexts

  • Group related functionality into contexts
  • Use clear, domain-specific names
  • Keep contexts focused and cohesive
defmodule MyApp.Accounts do
  @moduledoc """
  The Accounts context manages user authentication and profiles.
  """

  alias MyApp.Accounts.User
  alias MyApp.Repo

  def get_user!(id), do: Repo.get!(User, id)

  def create_user(attrs \\ %{}) do
    %User{}
    |> User.changeset(attrs)
    |> Repo.insert()
  end
end

Context Boundaries

  • Avoid circular dependencies between contexts
  • Use well-defined APIs between contexts
  • Keep database access within context modules

Controller Patterns

Action Structure

defmodule MyAppWeb.UserController do
  use MyAppWeb, :controller

  alias MyApp.Accounts
  alias MyApp.Accounts.User

  def index(conn, _params) do
    users = Accounts.list_users()
    render(conn, "index.html", 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: Routes.user_path(conn, :show, user))

      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end
end

Error Handling

  • Use pattern matching for success/error cases
  • Provide meaningful flash messages
  • Handle edge cases gracefully

Schema and Changeset Patterns

Schema Definition

defmodule MyApp.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :email, :string
    field :name, :string
    field :age, :integer

    timestamps()
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:email, :name, :age])
    |> validate_required([:email, :name])
    |> validate_format(:email, ~r/@/)
    |> unique_constraint(:email)
  end
end

Changeset Conventions

  • Use descriptive changeset function names
  • Validate data thoroughly
  • Use constraints for database-level validations

LiveView Best Practices

Component Structure

defmodule MyAppWeb.UserLive.Index do
  use MyAppWeb, :live_view

  alias MyApp.Accounts

  @impl true
  def mount(_params, _session, socket) do
    users = Accounts.list_users()
    {:ok, assign(socket, :users, users)}
  end

  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    user = Accounts.get_user!(id)
    {:ok, _} = Accounts.delete_user(user)

    users = Accounts.list_users()
    {:noreply, assign(socket, :users, users)}
  end
end

State Management

  • Keep socket assigns minimal and focused
  • Use temporary assigns for large datasets
  • Handle loading states appropriately

Testing Patterns

Context Testing

defmodule MyApp.AccountsTest do
  use MyApp.DataCase

  alias MyApp.Accounts

  describe "users" do
    test "create_user/1 with valid data creates a user" do
      valid_attrs = %{email: "test@example.com", name: "Test User"}

      assert {:ok, %User{} = user} = Accounts.create_user(valid_attrs)
      assert user.email == "test@example.com"
      assert user.name == "Test User"
    end
  end
end

Controller Testing

defmodule MyAppWeb.UserControllerTest do
  use MyAppWeb.ConnCase

  describe "GET /users" do
    test "lists all users", %{conn: conn} do
      conn = get(conn, Routes.user_path(conn, :index))
      assert html_response(conn, 200) =~ "Users"
    end
  end
end

These conventions ensure consistent Phoenix application structure and promote maintainable, testable code across all projects.