Jun Dalisay
Jun Dalisay

Reputation: 1275

How to assign zero to a nil result of Ecto query?

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

Answers (4)

Mark McElroy
Mark McElroy

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

justapilgrim
justapilgrim

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

Everett
Everett

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

copser
copser

Reputation: 2641

If you want to use elixir is_nil, guard, because you are not sure that balance is valid integer and not nil, you will:

balancevalue = if is_nil(balance), do: 0, else: Decimal.to_float(balance)

Upvotes: 2

Related Questions