Protecting against infinite loops on loading relationships
In my business domain I need to richly relate (i.e. with data on the relationship a resource with any number of resources of the same kind. I do this using a 'connector' resource, where the resource has_many connector forward_relationships and reverse_relationships, and the connector belongs to each of a source and target resource.
So the source resource forward_relationship[]'s run through the connector resource an appear as reverse_relationship on the target. This enables me to model forward and reverse relationships independently, which is useful since they have independent lifecycles.
The problem I'm having is that when I establish both forward and reverse relationships, if I have a load statement that loads relationships, then Ash will loop infinitely, exercising the datalayer over and over as it recursively loads resources it has recently loaded. As I mentioned the source and target resource are the same resource kind, so are constrained to have the same load statement.
I'm open to suggestions about best practice, but I feel I should be able to model my domain as it really is.
Have people encountered this/how do they avoid/deal with it?
Should Ash protect against infinite looping by some mechanism, since this is effectively worse than an application crash since harder to detect and consumes resources.
8 Replies
The trick is not to load data inside of actions
I assume you have something like
prepare build(load: ...)
etc in your actions or something like that
You should instead pass down load
statements from the callersYes, prepare (all reads)and also in creates and update actions where these relationships are either injected or derived and established. I’m needing high level actions that abstract the mechanics via code interface/etc.
These high level actions will need to load relationship data on the related resources, including supervision of their relationships. I thought ash actions were the point here but they can’t safely load relationships while avoiding infinite loops.
Does this mean I’m into reactor or Oban territory?
Similarly I find loads at the call site unattractive: https://elixirforum.com/t/ash-action-needs-calculation-to-be-loaded-how/67436
Elixir Programming Language Forum
Ash action needs calculation to be loaded, how?
Hi, I have an action called ‘start’: update :start do change transition_state(:active) require_atomic? false validate attribute_equals(:enough_parties_defined?, true) validate attribute_equals(:has_parties_with_no_members?, false) validate Validations.HasDocuments, before_action?: true ch...
I also have calculations and validations that require relationship data so should be handled in the action .
How does pushing this up to the call site protect from infinite load loops? Maybe we need an Ash.load option safe?: true, perhaps should be the default.
what would
safe?: true
do?
We don't know what woudl and wouldn't be an infinite loop because relationships are unidirectional
its not a possible problem to solve
You also don't need to load the relationship data required by calculations, Ash handles that
you define your dependencies of the calculation and Ash ensures its loaded while running the calculation.
You can define code interfaces with default options that do loading
define :list_things, ...., default_options: [load: [:foo, :bar]]
I think safe loading could be done without relationship introspection (which is limited as at resource compile we don’t know about other resources) by having ash cache datalayer query results by identities rather than request them again from the datalayer.
Thanks I’ll try the load options on the code interface for the general case where I want to return a fully populated resource.
I’ll also play with not loading the target when loading the connector resource, and use a calculation on it to retrieve the href of the target so it just renders as a reference unless you specifically load its target.
You can look at the query context to see where you were loaded from
Thanks I’ll experiment and then update this thread
Yes loading via the code interface rather than the action appears to be the solution, since the load isn't applied recursively.