Working with Assigns in Phoenix

Cleaning up messy controller actions

Photo by Marcel Massa on Unsplash

I build a lot of apps with Phoenix, and I sometimes run into a problem where I need to pass a lot of variables from a controller down to the view / template.

When you only have one or two variables, here’s what the controller action looks like:

def index(conn, _params) do
users = Accounts.list_users()
render(conn, "index.html", users: users)
end

The relevant part is users: users in the render/3 function, which is a keyword list of of key/value pairs that will be available in the template. For example, in the template you can access list of users passed here with @users.

The key / value pairs you pass in are also available in the %Plug.Conn{} struct under the :assigns key.

Here are a few more variables added to assigns:

def index(conn, _params) do
users = Accounts.list_users()
foo = get_foo()
bar = get_bar()
render(conn, "index.html", users: users, foo: foo, bar: bar)
end

In the majority of controller actions you write, you’ll only be passing a couple of variables down and this format works great! However, in those cases where you need to pass down a lot of variables it starts to get ugly:

def index(conn, _params) do
users = Accounts.list_users()
foo = get_foo()
bar = get_bar()
long_variable_name = get_long_variable_name()
other_var = get_other_var()
last_var = get_last_var()
render(conn, "index.html", users: users, foo: foo, bar: bar, long_variable_name: long_variable_name, other_var: get_other_var(), last_var: last_var)
end

And if you try to use the built-in Elixir formatter it breaks the render/3 function into a bunch of lines, which makes the assignments clearer but isn’t pretty as a function call:

def index(conn, _params) do
users = Accounts.list_users()
foo = get_foo()
bar = get_bar()
long_variable_name = get_long_variable_name()
other_var = get_other_var()
last_var = get_last_var()
render(conn, "index.html",
users: users,
foo: foo,
bar: bar,
long_variable_name: long_variable_name,
other_var: get_other_var(),
last_var: last_var
)
end

It’s even worse if you have to put a flash message or any other modification of conn before you hit render/3:

def index(conn, _params) do
users = Accounts.list_users()
foo = get_foo()
bar = get_bar()
long_variable_name = get_long_variable_name()
other_var = get_other_var()
last_var = get_last_var()
conn
|> put_flash(:info, "Hello")
|> render("index.html",
users: users,
foo: foo,
bar: bar,
long_variable_name: long_variable_name,
other_var: get_other_var(),
last_var: last_var
)
end

I recently came across a section in the Phoenix docs that gives an elegant solution to this problem: the assign/3 plug that can be used with the pipe operator to clean things up:

def index(conn, _params) do
users = Accounts.list_users()
foo = get_foo()
bar = get_bar()
long_variable_name = get_long_variable_name()
other_var = get_other_var()
last_var = get_last_var()
conn
|> put_flash(:info, "Hello")
|> assign(:users, users)
|> assign(:foo, foo)
|> assign(:bar, bar)
|> assign(:long_variable_name, long_variable_name)
|> assign(:other_var, other_var)
|> assign(:last_var, last_var)
|> render("index.html")
end

Much nicer! Now each assignment is clearly specified, everything is in a readable pipeline, and all render/2 has to deal with is the template name.

This format has helped me feel better about the readability of some of my most complex controller actions, where the pattern shines even more than in contrived examples like this. I hope you find it helpful!

Elixir dev building for the web with Phoenix

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store