In a case
statement in Elixir, is it possible to do nothing if a particular condition is met in a case
statement? Or must something always be returned?
To illustrate, here's a snippet from a Phoenix app that I'm working on:
Enum.map(record_params, fn(record_id) ->
record = Repo.get!(Record, record_id)
case Repo.update(record) do
{:ok, struct} ->
# I DON'T REALLY NEED ANYTHING TO HAPPEN HERE... BUT I HAVE TO HAVE A CLAUSE TO MATCH WHEN THE UPDATE RETURNS {:ok, struct}
IO.inspect struct
{:error, changeset} ->
errors = parse_errors(changeset)
IO.inspect errors
json(conn |> put_status(400), %{status: "error", message: "There was a problem updating this record.", errors: errors})
end
end)
If the record
is updated, I need to know if there's an error, get information about it, and return that to the client, hence the need for the case
statement... but I don't really need to do anything if the record has been updated successfully - {:ok, struct}
. Since these updates are taking place inside an Enum.map()
, if the update is successful, I just want the map to continue to loop through the record_ids
.
For the time being, I've just been putting IO.inspect struct
in the success condition - this is harmless, but not really necessary. I'd prefer to clean up my code, if possible. I can't remove the {:ok, struct}
condition due to Elixir's pattern matching, and if I put nothing at all under that condition, I get the error syntax error before: '->'
.
Now I am totally new to Elixir (and the functional programming paradigm, in general), so if there is a more 'Elixirish' way to handle this sort of scenario, I would love to hear about it.
Despite there is already a bunch of answers, I would post another one. The idiomatic Elixir way of ignoring all but one return would be to use Kernel.SpecialForms.with/1
:
with {:error, changeset} <- Repo.update(record) do
# Do whatever you want here
end
The do
block would be executed if and only the match happened, otherwise the non-matched RHO value ({:ok, _}
would be directly returned.)
For the correct code performing what you wanted, please refer to @Dogbert’s answer.
Your code is wrong because you're potentially calling json
multiple times on the same conn
in case there are multiple Repo.update
errors. You need to stop processing the rest of the list in that case. Also, since you're not actually detecting when there are any errors, you're probably unconditionally calling json
again later on the same conn
. If you look at your logs, you should see that Phoenix is writing a response to the same conn
multiple times, e.g.
[debug] Processing by MyApp.PageController.index/2
Parameters: %{}
Pipelines: [:browser]
[info] Sent 200 in 130µs
[info] Sent 200 in 232µs
[info] Sent 200 in 498µs
[info] Sent 200 in 390µs
[info] Sent 200 in 182µs
Here's how I'd do this with Enum.reduce_while/3
:
errors = Enum.reduce_while(record_params, nil, fn(record_id, nil) ->
record = Repo.get!(Record, record_id)
case Repo.update(record) do
{:ok, struct} ->
{:cont, nil}
{:error, changeset} ->
errors = parse_errors(changeset)
{:halt, errors}
end
end)
if errors do
json(conn |> put_status(400), %{status: "error", message: "There was a problem updating this record.", errors: errors})
else
json(conn, "no errors!")
end
The Erlang convention inherited by Elixir is to just use :ok
as with the output of IO.puts
There is no allocation overhead returning an atom, since they are all interned by the runtime.
In this particular case, because you are only wanting to do something for one branch, you could just use if/2
.
{status, changeset} = Repo.update(record)
if status == :error do
# Do whatever you want here
end
If you need to match on multiple things, you would want to use the case and just return something for that branch. In your case above, I would return :ok
, because the action was successful.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With