jonnifyelixir
F4 / F4.10 / patterns

F4.10 · part 1 of 3 · the request lifecycle

Idiomatic patterns

A request to view a lesson has to clear four gates before it renders: the id must be well-formed, the caller authenticated, the lesson present, and the caller allowed to see it. Written as nested case expressions that is a pyramid. Written as a with chain it is four lines that read top to bottom, each binding the happy path and every failure falling through to one else that maps a tagged error to a status.

The same chain, five outcomes. Choose which gate fails and watch where the with stops and what it returns.

Where the chain stops

Each step returns a tagged tuple. The with proceeds while every step matches its {:ok, _} pattern and bails to the else on the first that does not — so the steps after it never run. Select the gate that fails.

Outcome · select one

200 OK · lesson rendered

      

stops at: nothing — all pass

status: 200

The happy path is the body; everything that can fail is named once and handled once. The reader sees the four preconditions in order, not a staircase of nested branches.

Advanced: one else, tagged errors

Two habits make the chain work. Every step returns a tagged result — {:ok, value} or {:error, reason} — so the else can pattern-match the reason and map it to a status without guessing. And the chain binds only what the body needs; intermediate values that are merely checked, like :ok from authorize/2, are matched but not named. A with without an else returns the first non-matching value as-is, which is why tagging matters: an untagged nil would leak out of the function instead of becoming a 404.

The same shape recurs all over the Portal — create a session, enrol in a course, submit an answer — because almost every action is a short sequence of fallible steps with one happy path. It also keeps the validation of F4.08 honest: the first step is the edge check, so a malformed id never reaches the store lookups below it.

def show(conn, %{"id" => raw, "sid" => sid}) do
  with {:ok, id}     <- Portal.Id.validate(raw, :LSN),
       {:ok, user}   <- Auth.current_user(sid),
       {:ok, lesson} <- Catalog.fetch_lesson(id),
       :ok            <- Catalog.authorize(user, lesson) do
    render(conn, "lesson.json", lesson: lesson)        # 200 — the happy path
  else
    {:error, :bad_id}          -> send_resp(conn, 400, "")
    {:error, :unauthenticated} -> send_resp(conn, 401, "")
    {:error, :not_found}       -> send_resp(conn, 404, "")
    {:error, :forbidden}       -> send_resp(conn, 403, "")
  end
end

nested cases

A pyramid that drifts right with every precondition.

a with chain

A flat happy path; one else maps each tagged error to a status.

Next: streams & pipelines — once the request is admitted, how the report it asks for is built without materialising every step.

References

Primary sources for this lesson, and where it connects in the course.

Sources

Related in this course