Declarative controllers with Phoenix
Tags: elixir, phoenix
Published: 2020-11-24 04:09:33

---
title: Declarative controllers with Phoenix
published: true
description: Simplifying controller code
tags: elixir, phoenix
//cover_image: https://direct_url_to_image.jpg
---

The other day I found myself needing to write a bunch of controllers. Maybe I was feeling lazy, but I really wanted to finish the work quickly. I thought about how I could wrap it up in one afternoon.

Maybe I could write a code generator? or some kind of library?

I decided to go the library route.

In the end, it allowed me to write about 10 controllers in a couple of hours, so I'm sharing it.

# Anatomy of a controller  

A controller action does 3 things:

1. **Input**: Passing `params` and data from `conn` to a context function.
2. **Algorithm**: the context function.
3. **Output**: Rendering a response based on the context function's result.

Let's use this `create` action as an example:

```elixir
def create(conn, params) do
  # call context function
  case Catalog.create_product(conn.assigns.current_account, params) do
    # pattern match result
    {:ok, product} ->
      # rendering success
      conn
      |> put_status(:created)
      |> render("show.json", product)

    {:error, changeset} ->
      # rendering error
      conn
      |> put_status(:unprocessable_entity)
      |> render("error.json", changeset)
  end
end
```

In my case, all my controller actions would follow this structure.

# Reusable logic

Each controller action is calling a context function and then pattern matching against the result. The result is always a tagged tuple like `{:ok, data}` or `{:error, changeset}`. So it seems the response handling could be shared between controllers. 

We can extract the shared logic to a module:

```elixir
defmodule Responder do
  # handle success
  def respond({:ok, record}, conn) do
    conn
    |> put_status(:created)
    |> render("show.json", record: record)
  end
 
  # handle error
  def respond({:error, changeset}, conn) do
    conn
    |> put_status(:unprocessable_entity)
    |> render("error.json", changeset: changeset)
  end 
end
```

Then our controller code can be simplified:

```elixir
# import shared response logic
import Responder

def create(conn, params) do
  # call context
  Catalog.create_product(conn.assigns.current_account, params)
  |> respond(conn) # re-use response logic
end

def show(conn, params) do
  # call context
  Catalog.get_product(conn.assigns.current_account, params["id"])
  |> respond(conn) # re-use response logic
end
``` 

Way less repetition, it's starting to shape up.

# A pattern emerges

All the actions now have a similar look:

```elixir
def <action-name>(conn, params) do
  respond(conn, <context-function>.())
end
``` 

So why not distill it further with a macro:

```elixir
defmacro defaction(name, fun) do
  quote do
    def unquote(name)(conn, params) do
      data = %{conn: conn, params: params, assigns: conn.assigns}
      respond(conn, unquote(fun).(data))
    end
  end
end
```

Then, replace `def action_name(...)` with `defaction :action_name, ...`:

```elixir
defaction :index,  &Catalog.list_products(&1.params)
defaction :show,   &Catalog.get_product(&1.params)
defaction :create, &Catalog.create_product(&1.params["product"])
defaction :update, &Catalog.update_product(&1.params["id"], &1.params["product"])
defaction :delete, &Catalog.delete_product(&1.params["id"])
```

Now our controllers are completely declarative. Easier to read and less typing :)

# Hex package

Since it worked out well for me, I open sourced it: 

- Repo: https://github.com/joshnuss/transponder
- Hex package: https://hex.pm/packages/transponder

To set it up, install the hex package:

```elixir
# in mix.exs, add to `deps`:
{:transponder, "~> 0.2"}
```

Then in any controller, import `Transponder`, passing the required format (JSON or HTML).

```elixir
defmodule MyAppWeb.Admin.ProductsController do
  use MyAppWeb, :controller
  use Transponder, format: Transponder.JSON

  defaction :name, &Context.function(...)
end
```

# Conclusion

The idea isn't a new one, it exists in Rails with [`make_resourceful`](https://github.com/hcatlin/make_resourceful) and [`resource_controller`](https://github.com/jamesgolick/resource_controller).

The approach works well for controllers that follow the same  pattern. It keeps controller code declarative, increases readability, and eliminates the need for testing controllers. 

Of course for more intricate applications, hand-tuned response logic may work better. Nothing wrong with that.

P.S. It's still alpha software, but feel free to give it a try and open PRs

Happy hacking,