woohaaha

woohaaha

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 <-?

Showing Posts 1 to 10

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),

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).

Sebb

Sebb

In this case, with being the only block in the function it does not matter, but still its a good thing to keep the scope of a variable as small as possible.

focused

focused

In this case error’s message will change also (pattern match error). This way phoenix cannot handle this error as “not found” (404) in production if debug_errors: false, it will be 500 error.

bennelsonweiss

bennelsonweiss

Generally my expectation when I see get_… is that it will return the thing or nil or a provided default value. If you want to return {:ok, thing} or an error fetch_… would be a more consistent naming.

In regards to the original question, I use = in with clauses to indicate that I’m not matching on that result. Sometimes you may need to do something between matches in a with that you don’t need to match on (like creating a changeset) and = signals intent better.

woohaaha

woohaaha OP

Sorry for asking, would you mind showing me an example?

woohaaha

woohaaha OP

I keep re-reading your answer and it’s not sticking.

By raising (assuming that ! does as intended) the error will fall through the with clause, and does not appear to be handled. I’m going to assume that’s a mistype :thinking:

What would be the difference if we use page <- CMS.get_page!/1? It would be the same wouldn’t it? I can’t think of anything subtle being said by using the =. If I were to see page <- CMS.get_page/1 (notice no !) I would also assume “This always matches”.

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.

woohaaha

woohaaha OP

Thank you! I think I got the intent :clap: Your example is more clear to me than the blog post.

Things that got me confused:

  1. Errors falling through with. Then why use with?
  2. The first condition in the with is page = CMS.get_page!(id), but if we don’t care about matching, why even put it in the with, it could well be outside, especially if it raises, which would make it even more clear. The with (to me) for get_page!/1 is a bit of indirection.
  3. The scope of page is then used in the redirect, how? (is that a mistype?)

In any case, your example of the changeset in the middle of the with is more understandable to me. I wouldn’t want to execute the create_changeset unless the :ok prior succeeded. Also, changeset is likely not fail, and I don’t want to use it for matching anyway (since it would be used in the else clause.
(I don’t think I’ve explained it well)

Thank you again.

focused

focused

@bennelsonweiss explained it perfectly.

  1. They use with because they want to handle those errors by matching on them in action_fallback like:
def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
    conn
    |> put_status(:unprocessable_entity)
    |> put_view(MyAppWeb.ChangesetView)
    |> render("error.json", changeset: changeset)
end

def call(conn, {:error, something}), do: ...

But Ecto.NoResultsError that can be raised by get_page!, handled by Plug.Exception implicitly and then may be directed to action_fallback, but not in dev environment, because in dev we may want to debug those errors earlier.

  1. You are right! It should be placed outside in this example, if you don’t care about binding scope; as @Sebb said he would limit the scope of page binding, but in this example it does not matter.

  2. cms_page_path(conn, :show, page) just works because it sees page binding, there is no inner scope between with ... do, only between with ... end, if I understood your question.

everywhere = 1

with only_inside_a = 2,
     only_inside_b = 3 do
  IO.puts(everywhere) # 1
  IO.puts(only_inside_a) # 2
  IO.puts(only_inside_b) # 3
end

IO.puts(everywhere) # 1
IO.puts(only_inside_a) # ERROR!
IO.puts(only_inside_b) # ERROR!

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
bradley
I really like the adapter patterns that ecto, nebulex, waffle, etc. use and would love find something similar for a key management servic...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
Alvinkariuki
How Can I Optimise Compile Time Dependencies I have been building an elixir application for about 2 years now. Many modules and files ha...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews