Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elixir - do nothing under particular case condition

Tags:

elixir

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.

like image 966
skwidbreth Avatar asked Apr 27 '17 01:04

skwidbreth


4 Answers

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.

like image 105
Aleksei Matiushkin Avatar answered Nov 18 '22 19:11

Aleksei Matiushkin


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
like image 37
Dogbert Avatar answered Nov 18 '22 17:11

Dogbert


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.

like image 3
Mike Buhot Avatar answered Nov 18 '22 17:11

Mike Buhot


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.

like image 3
Justin Wood Avatar answered Nov 18 '22 19:11

Justin Wood