woohaaha

woohaaha

Why was equal sign used in with statement?

I read an article on the Dockyard blog and I’m wondering why they used an equal sign with a with statement.

The code example is:

defmodule CMS.PageController do
  
  def update(conn, %{"id" => id, "page" => page_params}, current_user) do
    with page = CMS.get_page!(id),
         :ok <- Authorizer.authorize(:update, page, current_user),
         {:ok, page} <- CMS.update_page(page, page_params) do
    
      conn
      |> put_flash(:info, "Page Updated")
      |> redirect(to: cms_page_path(conn, :show, page)
    end
  end
end

Why was page = CMS.get_page!(id) used instead of <-?

Most Liked

felix-starman

felix-starman

with is a macro that evaluates a set of expressions and the <- symbol is a hint to that macro that the right-hand side will either match the left, bind any unbound variables, and then continue to the next expression, or it won’t match, and it will follow the else clause matching if present, or just return the unmatched clause. But if you don’t use the <- then it’s just a typical expression, and it will run it, and do the next thing.

Using a = inside a with is usually a hint that either the expression will always pass (e.g. string = "asdf:" <> some_known_value) or that you want it to raise an error if it something is wrong. In this case because the right side of the = is CMS.get_page!/1 and ! is typically used when you want to raise if something isn’t found, I’d expect that get_page!/1 will raise an error if it doesn’t find the page, so we can assume that page = ... will always pass (if it doesn’t raise an error). If it didn’t raise an error and returned {:ok, page} but the author wanted it to raise in that scenario they could change it to {:ok, page} = CMS.get_page(id),

woohaaha

woohaaha

Thank you everyone!!! :clap: @bennelsonweiss @focused @Sebb @felix-starman

Such a nice implementation in the original example :sweat_smile: :blush:

focused

focused

I think it should be:

with {:ok, page} <- CMS.get_page(id), 
     :ok <- Authorizer.authorize(:update, page, current_user),
     {:ok, page} <- CMS.update_page(page, page_params) do
...

or

page = CMS.get_page!(id)

with :ok <- Authorizer.authorize(:update, page, current_user),
     {:ok, page} <- CMS.update_page(page, page_params) do 
...

The code with page = CMS.get_page!(id) raises in case of error even if it would be outside of with, so it has no reason to be placed inside that with clause (it does not need to be inside that with).

bennelsonweiss

bennelsonweiss

It’s probably not a mistype at all. You don’t need to handle everything in a with, and in their case they’re not handling anything in with.

Since they’re not handling anything in a with everything is falling through, whether it falls through as a throw or as a returned error, and my educated guess is that since we’re looking at a controller they’re:

  1. Relying on the fallback controller they would no doubt register with action_fallback to handle the return value of failing to match authorize or update_page
  2. Relying on Plug.Exception’s coercion of Ecto.NoResultsError (as one would expect get_page! to raise) to turn that error into a 404 response

So they are using = because they aren’t trying to match on the value, they just expect it to work or raise.

It’s contrived, but

with :ok <- validate(params),
     changeset = Thing.create_changeset(%Thing{}, params),
     {:ok, thing} <- Repo.insert(changeset) do
  Logger.info("I created thing #{thing.id}")
  {:ok, thing}
end

When I created the changeset, it’s gonna work no matter what, so I use = instead of <- because there’s nothing to match on.

In your original question they did they same because they don’t care what get! returns because either it’ll return what they want or it’ll raise.

bennelsonweiss

bennelsonweiss

The simplest reason is that it lets you define a clear sequence of execution for functions that may fail without needing to nest conditional checks.

In the example you’re fetching a page (page may not exists), then authorizing the current user to interact with that page (user may not be authorized), then updating the page (update may be invalid).

You could nest three ifs, “if page is not found fail, else if user is not authorized fail, else if page cannot be updated fail, else put flash and redirect”, but then all the error handling obscures what you’re actually trying to achieve.

with allows you to structure it as “successfully get the page, then ensure the user is authorized, then update the page, then put flash and redirect”. That way the happy path is clear to see, but you haven’t ignored the fact that things can go wrong because you’re still checking for that, just not handling it right there.

You could just as easily not put it in the with, but there’s a non-technical reason you might want to put it in there.

Since the with is defining the logic for some process, in this case how you update a page, and getting a page is a fundamental part of the process as they’re defining it, then including it in the with puts all the steps of the process at the same level so that it’s clear to understand each stage of the process.

It’s a decision entirely aimed towards trying to present something clearly for the developer, and it might not always be the clearest choice given a team’s conventions.

Everything defined between with and do is available in the do block (but not in the else block, if there is one), and if you reach the do block it means there were no errors before.

Where Next?

Popular in Questions Top

yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

Other popular topics Top

TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement