Reputation: 1275
I'm trying to query a transactions list for the total amount and then add that to another amount. However, an error occurs if the user has no transactions. So I put a conditional to assign 0 if the result is nil, but it doesn't carry over
query = from t in Transactions, where: (t.user_id == ^user), select: sum(t.amount)
balance = Repo.one(query) # get value
if is_nil(balance) do
balancevalue = 0
else
balancevalue = Decimal.to_float(balance) # convert value to decimal
end
total = balancevalue + ^anotherbalance # add with other value
IO.puts "total: #{total}"
How do I rewrite the condtional so that the query will return zero if the result is nil?
Upvotes: 2
Views: 1426
Reputation: 373
You want to use coalesce/2
:
https://hexdocs.pm/ecto/Ecto.Query.API.html#coalesce/2
balance =
from(t in Transactions,
where: t.user_id == ^user,
select: coalesce(sum(t.amount), 0)
)
|> Repo.one()
Upvotes: 4
Reputation: 6882
nil
is a falsey value in Elixir, so you can simply use the or operator (||
).
balance = Repo.one(query) || 0
total = Decimal.to_float(balance) + another_balance
or if you want to pipe it down
query
|> Repo.one()
|> Kernel.||(0)
|> Decimal.to_float()
|> Kernel.+(another_balance)
Upvotes: 2
Reputation: 9628
Remember that everything in Elixir is an assignment. That means that you have to divorce yourself of many patterns which are quite common in other languages because an Elixir variable does not share any scope beyond its block. For example, in many OO languages, you might do a conditional count of list items doing something like this:
# pseudo code... This pattern will not work in Elixir!
cnt = 0
for i = 0; i < length(list); i++ {
if list[i] == 'something'
cnt++
}
}
In Elixir, you would need to do an "assignment", which in this case means you'd have to fall back to the tried and true Enum
module, e.g. using Enum.reduce/3
:
cnt = Enum.reduce(list, 0, fn
"something", acc -> acc + 1 # function clause 1 (match)
_, acc -> acc # function clause 2 (no match)
end)
Above we have employed 2 function clauses to match on a specific conditional value ("something") instead of using an if-statement -- if-statements end up being somewhat rare in Elixir because it turns out you don't often need them.
In your example, you can leverage assignment via a simple case
statement:
def user_balance(user) do
query = from t in Transactions, where: (t.user_id == ^user), select: sum(t.amount)
case Repo.one(query) do
nil -> 0
balance -> Decimal.to_float(balance)
end
end
If you follow the execution flow, you see that the result of the Repo.one(query)
is evaluated immediately inside the case
statement. Again, we use pattern-matching instead of an if
statement (remember, the most specific matches must appear first). So if the result is nil
, 0
gets returned; if it is anything else, it is handed off to the Decimal.to_float/1
. Because Elixir functions rely on implicit returns, the output of this case
statement is the return value of the user_balance/1
function.
Breaking things down into separate functions not only makes your code more readable, it helps you achieve an assignment so your code actually works.
Upvotes: 1