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:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def call ( conn , _options ) do user_id = get_session ( conn , :user_id ) if user_id do user = Repo . get ( User , user_id ) end if user do assign ( 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" ) |> halt end end

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.

The Elixir compiler even emits a warning here:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 warning: the variable "user" is unsafe as it has been set inside one of: case , cond , receive , if , and , or , && , ||. Please explicitly return the variable value instead . For example: case integer do 1 -> atom = :one 2 -> atom = :two end should be written as atom = case integer do 1 -> :one 2 -> :two end

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

1 2 3 4 5 6 7 8 9 10 11 12 13 def call ( conn , _options ) do with { :ok , user_id } <- get_session ( conn , :user_id ), { :ok , user } <- Repo . get ( User , user_id ) do assign ( 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" ) |> halt end end

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?

Fortunately I found the answer in the official documentation for with . You can combine with with guards.

My first attempt produced:

1 2 3 4 5 6 7 8 9 10 11 12 13 def call ( conn , _options ) do with user_id when ! is_nil ( user_id ) <- get_session ( conn , :user_id ), user when ! is_nil ( user ) <- Repo . get ( User , user_id ) do assign ( 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" ) |> halt end end

This resulted in a cryptic error from the compiler:

1 2 ** ( CompileError ) lib / multipster_web / sign_in / plug . ex: 29 : invalid expression in guard , case is not allowed in guards ( elixir ) expanding macro: Kernel . ! / 1

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

1 2 3 4 5 6 7 8 9 10 11 12 13 def call ( conn , _options ) do with user_id when not is_nil ( user_id ) <- get_session ( conn , :user_id ), user when not is_nil ( user ) <- Repo . get ( User , user_id ) do assign ( 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" ) |> halt end end

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