Reputation: 4050
I have a model that I would like to execute in Phoenix/Elixir. The model basically computes a rolling sum of the amount spent by a user. The model (Receipts
) looks like the following:
---------------
| ID | Amount |
---------------
| 1 | 0 |
| ...| ... |
| 5 | 4 |
---------------
There is a unique index on ID
. I want to insert a user into the table (I define the ID) and then set the amount if the user does not exists, otherwise add the new amount to the existing amount. For example, executing:
Repo.insert(Users.changeset(%Users{}, %{ID: 1, Amount: 10})
Would result in:
---------------
| ID | Amount |
---------------
| 1 | 11 |
| ...| ... |
| 5 | 4 |
---------------
and executing Repo.insert(%Users{}, Users.changeset(%{ID: 6, Amount: 5}) would result in:
---------------
| ID | Amount |
---------------
| 1 | 11 |
| ...| ... |
| 5 | 4 |
| 6 | 5 |
---------------
I know that I should be doing something with :on_conflict
but I'm a bit lost how to get it right. Can anyone point me in the right way?
Upvotes: 4
Views: 4474
Reputation: 65
Adding to the first answer return the variable after the update will prevent parallel problems.
iex> Repo.insert(%User{id: 1, amount: amount_to_add}, returning: [:amount], conflict_target: :id, on_conflict: [inc: [amount: amount_to_add]])
Upvotes: 1
Reputation: 15736
Repo.transaction fn ->
user =
User
|> where(id: ^id)
|> lock("FOR UPDATE")
|> Repo.one()
if user do
user
|> change(amount: user.amount + 5)
|> Repo.update!()
else
%User{}
|> change(amount: 5)
|> Repo.insert!()
end
end
That's one way to go which uses postgreSQL syntax for locking. I guess you will figure out the rest.
Upvotes: 3
Reputation: 1349
Sure, that's possible. It would look something like this:
iex> amount_to_add = 10
10
iex> Repo.get(User, 1)
nil
iex> Repo.insert(%User{id: 1, amount: amount_to_add}, conflict_target: :id, on_conflict: [inc: [amount: amount_to_add]])
...
iex> Repo.get(User, 1)
%User{id: 1, amount: 10, ...}
iex> Repo.insert(%User{id: 1, amount: amount_to_add}, conflict_target: :id, on_conflict: [inc: [amount: amount_to_add]])
...
iex> Repo.get(User, 1)
%User{id: 1, amount: 20, ...}
Upvotes: 13