Happy teams create great products

Elixir "with" syntax and guard clauses

In this blog post we will take a look at a simple example of refactoring Elixir
code using with syntax (called a “special form” in the documentation), and guard
clauses.

The code that I started with:

12345678910111213141516

defcall(conn,_options)douser_id=get_session(conn,:user_id)ifuser_iddouser=Repo.get(User,user_id)endifuserdoassign(conn,:current_user,user)elseconn|>Controller.put_flash(:error,"You have to sign in to access this page.")|>Controller.redirect(to:"/sign_in_links/new")|>haltendend

The algorithm can be described as:

look up user_id in the session

if user_id is set find the user in the database

if the given user exists assign it as current_user

if user_id is not set or the user doesn’t exist in the database display a
flash message and make a redirect

The code looks quite simple, but in fact it’s not very explicit. While reading it
we have to remember that when user_id is nil then also user will be nil,
because the variable is not being assigned at all. That’s the implicit part and
I don’t like it.

We can follow these instructions directly, but we can also take a look at
with special form:

12345678910111213

defcall(conn,_options)dowith{:ok,user_id}<-get_session(conn,:user_id),{:ok,user}<-Repo.get(User,user_id)doassign(conn,:current_user,user)else{:error,_}->conn|>Controller.put_flash(:error,"You have to sign in to access this page.")|>Controller.redirect(to:"/sign_in_links/new")|>haltendend

That’s the most common example of the with syntax shown in the blog posts that
I found. When you have functions that return {:ok, _} or {:error, _} it’s
really straightforward to apply.

However, my problem is slightly different. Plug.Conn.get_session/2 returns
a value or nil, and Ecto.Repo.get/3 returns a database record or nil.

Now I was a bit puzzled here – how can I adjust the above example to solve my
problem?

defcall(conn,_options)dowithuser_idwhen!is_nil(user_id)<-get_session(conn,:user_id),userwhen!is_nil(user)<-Repo.get(User,user_id)doassign(conn,:current_user,user)else_->conn|>Controller.put_flash(:error,"You have to sign in to access this page.")|>Controller.redirect(to:"/sign_in_links/new")|>haltendend

When I re-read the documentation for guards I realized that I have to replace
! with not:

12345678910111213

defcall(conn,_options)dowithuser_idwhennotis_nil(user_id)<-get_session(conn,:user_id),userwhennotis_nil(user)<-Repo.get(User,user_id)doassign(conn,:current_user,user)else_->conn|>Controller.put_flash(:error,"You have to sign in to access this page.")|>Controller.redirect(to:"/sign_in_links/new")|>haltendend

This worked perfectly, hooray! What I really like about this approach is that
the happy path and the error handling path are separated.

Did you enjoy this post? Sign up to my newsletter to get the next bits!