Reputation: 8247
In many situations I find myself needing to pattern match on both a struct itself and its fields, and proceed to perform some further actions when some intermediate results are not empty.
However, the struct itself might be nil
in the first place. This resulted in me writing multiple nested matches, e.g.
experiment = Repo.get(Experiment, experiment_id)
case experiment do
nil ->
# Error 1
_ ->
case experiment.active do
false -> # Error 2
true ->
case Repo.all(assoc(experiment, :experiment_results)) do
[] -> # Error 3
results -> # Do stuffs
end
end
Ideally I would want to write code without so much nesting.
How can I refactor the code?
(Note that my original question was about pattern matching on a struct when it could be nil
. My actual use case is broader than my original question suggested, thus I updated the question.)
The original code, to which AlekseiMatiushkin and Sheharyar's answers apply:
experiment = Repo.get(Experiment, experiment_id)
case experiment do
nil ->
:error
_ ->
case experiment.active do
false -> :error
true -> # Do stuffs
end
end
Upvotes: 0
Views: 311
Reputation: 8247
Another way is to use the with
syntax. It essentially stipulates what the "happy path" should be, even when the path entails multiple relatively complex checks:
with experiment <- Repo.get(Experiment, id),
{:nil_experiment, false} <- {:nil_experiment, is_nil(experiment)},
experiment_results <- Repo.all(assoc(experiment, :experiment_results)),
{:empty_results, false} <- {:empty_results, Enum.empty?(experiment_results)} do
do
# Do stuffs with `experiment_results`
else
{:nil_experiment, true} ->
# Error message 1
{:empty_results, true} ->
# Error message 2
_ ->
# Unknown error
end
Upvotes: 0
Reputation: 121000
I would go with a direct pattern match.
Experiment
|> Repo.get(experiment_id)
|> case do
%Experiment{active: true, other_attribute: :value} ->
# do stuff
_ ->
:error
end
Upvotes: 5
Reputation: 75740
How about the &&
short-circuit operator?
if experiment && experiment.active && experiment.other do
# do something
else
:error
end
You can also use cond
to add more cases:
cond do
experiment && experiment.active && experiment.other ->
# do something
!experiment.active ->
{:error, :inactive}
is_nil(experiment) ->
{:error, :experiment_is_nil}
true ->
{:error, :unknown}
end
Upvotes: 1