How To: Atomic update with a where clause

I'm trying to implement this workflow: Update a timestamp atomically on an existing record if the new timestamp is greater than the old one, and the old one hasn't changed while im updating it(optimistic update). I know atomics just dropped on Ash core so wondering if this could be achieved with it. This is the ash action in question:
update :mark_as_paid do
argument :invoice_event_ts, :utc_datetime_usec, allow_nil?: false

change set_attribute(:invoice_status, :paid)
change atomic_update(:last_invoice_event_ts, arg(:invoice_event_ts))
end
update :mark_as_paid do
argument :invoice_event_ts, :utc_datetime_usec, allow_nil?: false

change set_attribute(:invoice_status, :paid)
change atomic_update(:last_invoice_event_ts, arg(:invoice_event_ts))
end
5 Replies
gordoneliel
gordonelielOP•2y ago
Basically want to do something like this:
update :mark_as_unpaid do
argument :invoice_event_ts, :utc_datetime_usec, allow_nil?: false

change set_attribute(:invoice_status, :unpaid)
change atomic_update(:last_invoice_event_ts, arg(:invoice_event_ts)), where: expr(last_invoice_event_ts < ^arg(:invoice_event_ts)
end
update :mark_as_unpaid do
argument :invoice_event_ts, :utc_datetime_usec, allow_nil?: false

change set_attribute(:invoice_status, :unpaid)
change atomic_update(:last_invoice_event_ts, arg(:invoice_event_ts)), where: expr(last_invoice_event_ts < ^arg(:invoice_event_ts)
end
ZachDaniel
ZachDaniel•2y ago
change atomic_update(:last_invoice_event_ts, expr(
if last_invoice_event_ts < ^arg(:invoice_event_ds) do
^arg(:invoice_event_ds)
else
invoice_event_ds
end
))
change atomic_update(:last_invoice_event_ts, expr(
if last_invoice_event_ts < ^arg(:invoice_event_ds) do
^arg(:invoice_event_ds)
else
invoice_event_ds
end
))
That should do it 🙂
gordoneliel
gordonelielOP•2y ago
That works @Zach Daniel You're the best! How would you combine this with another attribute? Like say I want the invoice_status attribute to only update if the atomic last_invoice_event_ts is updated? Can that even work? Do I have to dip into a custom action / change for that? Something like so:
change atomic_update(
:invoice_status,
expr(
cond do
# Not sure if you can get another field here
is_nil(last_invoice_event_ts) ->
^arg(:invoice_event_ts)

last_invoice_event_ts < ^arg(:invoice_event_ts) ->
:unpaid

true ->
invoice_status
end
)
)

change atomic_update(
:last_invoice_event_ts,
expr(
cond do
is_nil(last_invoice_event_ts) ->
^arg(:invoice_event_ts)

last_invoice_event_ts < ^arg(:invoice_event_ts) ->
^arg(:invoice_event_ts)

true ->
last_invoice_event_ts
end
)
)
change atomic_update(
:invoice_status,
expr(
cond do
# Not sure if you can get another field here
is_nil(last_invoice_event_ts) ->
^arg(:invoice_event_ts)

last_invoice_event_ts < ^arg(:invoice_event_ts) ->
:unpaid

true ->
invoice_status
end
)
)

change atomic_update(
:last_invoice_event_ts,
expr(
cond do
is_nil(last_invoice_event_ts) ->
^arg(:invoice_event_ts)

last_invoice_event_ts < ^arg(:invoice_event_ts) ->
^arg(:invoice_event_ts)

true ->
last_invoice_event_ts
end
)
)
ZachDaniel
ZachDaniel•2y ago
You can get another field 🙂 You can't get the "new value", but you can get the old value and the argument
gordoneliel
gordonelielOP•2y ago
Thanks that works! For posterity, This is my change module updating two values atomically:
def change(changeset, [next_status: next_status], _context) do
changeset
|> Changeset.atomic_update(
:invoice_status,
expr(
cond do
is_nil(last_invoice_event_ts) ->
^next_status

last_invoice_event_ts < ^arg(:invoice_event_ts) ->
^next_status

true ->
invoice_status
end
)
)
|> Changeset.atomic_update(
:last_invoice_event_ts,
expr(
cond do
is_nil(last_invoice_event_ts) ->
^arg(:invoice_event_ts)

last_invoice_event_ts < ^arg(:invoice_event_ts) ->
^arg(:invoice_event_ts)

true ->
last_invoice_event_ts
end
)
)
end
def change(changeset, [next_status: next_status], _context) do
changeset
|> Changeset.atomic_update(
:invoice_status,
expr(
cond do
is_nil(last_invoice_event_ts) ->
^next_status

last_invoice_event_ts < ^arg(:invoice_event_ts) ->
^next_status

true ->
invoice_status
end
)
)
|> Changeset.atomic_update(
:last_invoice_event_ts,
expr(
cond do
is_nil(last_invoice_event_ts) ->
^arg(:invoice_event_ts)

last_invoice_event_ts < ^arg(:invoice_event_ts) ->
^arg(:invoice_event_ts)

true ->
last_invoice_event_ts
end
)
)
end

Did you find this page helpful?