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

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews