What is the "Ash way" to auto update related resources ?
Let's say that I have an invoice (postgres) resource and that it has_many lines. Let's say that I want the total_amount field of my invoice resource to be updated with the sum(amount) of all lines every time that a line is created, updated or deleted. What would be the idiomatic way to do that ? I understand that I should probably use aggregates and changes with an after_action, but I have a bit of a hard time to see exactly how to articulates all the pieces together.
9 Replies
There are a few ways to do it. Are you looking to do this as an optimization? i.e to avoid counting the
total_amount
each time?
Or is there some other reason to persist the value?I need to have it recorded. It is definitely an optimization. I tried it only with aggregates and calculations and it does work very well (I love them!), but if I continue that project, in the longer run, I will have fields that require complex calculations dependent on business rules and also need to be queryable fast. So I'd be very happy to understand how it could be done "the Ash way".
Another example could be a tree of related resources where the lowest ones need to "touch" their parent recursively to mark them as "updated"
I tried something like that : I made an
update_total
action in the invoice resource that does the job of updating the total_amount
field (using an aggregate) and in the lines resource I made a change
in a changes
block that calls the update_caches
action on the parent invoice in an after_action
. It seems ok, but I've a feeling that something might be done better.When you say using an aggregate what do you mean? You mean that you are loading the aggregate and then persisting its value?
My first stab at it would likely be something like this (because Ash doesn't have updating with a query/atomic changes yet) using ecto directly
In my previous trials I did approximately that:
In parent :
In child:
It works but feels a bit clunky. What problems do you see with it ?
By the way, what result an after_action is supposed to return ? (when returning `{:ok, resource} ) the doc is not clear about how and where that result may be used.
You should return
{:ok, the_thing_we_gave_you}
it’s an opportunity to modify the return value in some way. So the main issue I see with that approach is that it isn’t atomic. If you had two child records created in quick succession, you could potentially end up with an incorrect total.Aren't Ash update actions transactional ? If I'd lock the parent record in a before_action (or with an around_action) shouldn't that take care of the race condition ?
The problem I have with your suggestion is that it works only if the total can be easily incremented by the amount. If it would require a complex calculation, it might not be so easy.
Yes, that is correct, if you lock the records in question then that is a viable strategy
there is no interface in Ash to lock records though, you'll need to issue locks in ecto directly.
Understood. Thank you very much for your help.
You answered me in the past that a MySQL connector is planned and I saw in a recent video that it is on your near future roadmap . Do you have an estimation as to when may do it ? Even a rough one... (it may be a deal breaker for me)
I don't unfortunately. Its behind bulk actions and atomics
Its probably longer than a month
but if you wanted to take a stab, we've decided to take a bit of a "brute force" approach to these by copying the
ash_postgres
data layer
we can abstract out similarities later
but you could probably get basic support working just changing some postgres specific stuff.