Using Ash Resources as input types
I've occasionally encountered issues using Ash resources as input types to actions of other resources. The most common patterns I've seen to resolve this are to simply turn the inputs into a map and let
manage_relationship
figure it out, or to take as a parameter only the :uuid
and similarly construct a map with just %{id: uuid}
for manage_relationship
.
The culprit in some cases seems to be AshGraphql, which errors following this pattern:
Internally, there is not a lot of danger to using the :map
approach, since manage_relationship
still knows what kind of resource it's supposed to be creating and will error accordingly, but it would be quite nice to be able to declare the input type as the resource itself and get it reported in the GQL schema, generated docs, etc.61 Replies
So when you are setting up your managed relationships in the way you described are you getting json inputs for relationships?
Sort of, if it's coming through the GraphQL layer they are usually parameterized queries with the arguments provided as JSON. Is that what you mean?
Well, I guess a better question is wether or not you are using the
managed_relationships
config in graphqlNope
That will get you better/more specified types for those arguments.
Looks like the link should be taking me to a section heading, but it's not.
The references to keyword "managed" are only for
writable?
Does that mean I can just set the relationship to be writable?
and I don't need to specify args or do manage_relationship
at all and it will be handled by the default write actions?No you still have to have the managed relationship, but you’re configuring it’s representation per write action
Gotcha. It kept clearing my selection of AshGraphql each time I refreshed the page, so the section wasn't coming up.
So is it preferred then to use
{:array, :map}
and :map
for argument types rather than resources directly?
I guess what struck me as odd was that sometimes it seems to be happy with the Ash resource as an argument type specifier.It probably works in cases where the resource in question uses the ash_graphql extension and this defines a graphql type for it
And breaks in cases where it doesn’t
But even then, it doesn’t really make sense to put an action with arguments like
argument :foo, Resource
Sorry, doesn’t make sense to use that with ash_graphql
Because the graphql client can’t possibly provide that typeThat would make sense to me, but in this case both resources are in the registry/api/schema.
Someone can’t send
%Resource{}
over graphql
So using that type would always fail to cast inputYeah, maybe this is simply a case of what exactly
argument
means. If it means (perhaps not only this) that the argument is a tagged struct, then for sure.argument :foo, Resource
means I accept an instance of the Resource
structHm. I guess, is there a way to derive an Ash.Type (which is what it wants) from an Ash.Resource?
Not all types can be provided by all api layers
That is exactly what
is meant to do
Right on, makes sense. Is there an equivalent for JSON:API?
Its not really necessary for JSON:API, or is less necessary
because we can just say
"object"
in the json schema
but in graphql you have to be explicit (otherwise it expects a string as JSON which is ugly)Definitely.
Yeah I was thinking JSON:API allows you to specify a nested schema for object.
Yeah, I forget wether or not we've set that up
but if not its just a matter of making it derive that
TBH
ash_graphql
should just do that by defaultI was sort of thinking the same, for
code_interface
it's fine to require the tagged struct, and Ash{Graphql,JsonApi} could use the existing machinery to derive the type/schema.Honestly, you shouldn't use
attribute :type, Resource
It causes compile time issues
and is not a portable typeAh, I'm not, though.
It's a
belongs_to
relationship in this case.oh
Sorry
I meant
argument :type, Resource
Like
You should always use :map
or {:array, :map}
or :uuid
(or w/e the primary key is)
The reason is that manage_relationship
can call multiple actions, and those actions might take arguments. If you say its a struct, for example, then you'd never be able to include those arguments.Right on, that works for me. Through
manage_relationship
the same checks will be performed when it tries to turn that map into an instance of the relationship, presumably by passing it to the primary create
action.Yeah, exactly.
But it can include more than just the create action input as well
i.e
on_match: :update, on_no_match: :create
that would update all matches (i.e if the thing you specified was already related) and create any non-matchesSure, yep.
Does it handle nested relationships by default or is it dependant on the
create
action being called?If the nested actions also manage relationships, then it will manage those too (by virtue of calling the relevant actions)
Great, that's what I was hoping.
So you still retain control over the chain.
Yep! Although keep in mind if you're doing like...large lists of related data doing large lists of related data that it can call many resource actions.
Once we introduce bulk actions, the idea is that we will have
manage_relationship
use those bulk actions to do its work, to remove the N+1 issue there
Which will basically make massive data imports with lots of related data actually a reasonable thing to do.Sounds great! I have some stuff that will probably be quite pleased about that :thinkies:
I actually was wondering, is there an equivalent to
read
vs. read_one
when defining the actions themselves?For updates maybe less relevant since it can tell from the input type.
Perfect
If you use
Api.read(...)
on that it will still return a list
but the code interface will switch to automatically return a single thingBut then
code_interface
will do the right thing
Coolyep
Thanks mate!
One thing that would definitely be nice is if I could omit the action name, like
managed_relationship :rel
, and have it apply to all actions with the argument
and attribute
of :rel
. Seems like this is already the most common pattern. Lmk if you see any downsides.🤔 Potentially...I'd rather just add a configuration that makes it automatically assume you want to derive those types, and then you only override per action/argument that you want to change the config for
Would it then infer the type from the use of the argument in
manage_relationship
? Otherwise the action body doesn't have the info, since there it's just map
.Yeah, thats what it already does
And the above seems shaky, what with manual actions etc.
managed_relationship :action, :argument
figures it out from the call to manage_relationship
Ah, I would've thought it looks at the relationship through
Ash.Resource.Info
.Nope, it can't because the shape of the input is determined by the options given to
manage_relationship
Sure, right on. I was thinking it just needs to differentiate between singular and plural, which it can get from the argument type.
Given that, this sounds great.
Well imagine if you had on one resource actions like this:
And then you managed it with
manage_relationship
options like on_match: :update, on_no_match: :create
You need to get a type like this:
And you could customize the actions i.e on_match: {:update, :something_else}
so we have to synthesize the various actions that could be calledYep, that makes sense, tbh I figured that it was matching the argument and relationship name there, but when we start talking about actions with
argument
s and accept
s down the manage_relationship
chain, the derived type needs to be a union of those, and wouldn't necessarily match a type derived from the resource itself at all 😂
Since that machinery is already there, seems like most of the work has been done towards making it automatically derive all argument types unless told otherwise, though, no?Alright, try
main
and do this:
All arguments with an attached change manage_relationship
should have their types automatically figured outSec
Alright, had to weed out all the places where I was using resources as types and it was working (;
I'm getting a lot of this, and then it errors out on a non-unique name deal.
Yeah, just put that config in
Is there a flag to set that for all actions?
Aha, ok
oh, yeah its saying literally
:action_name
😄
not like a generic
some_action_name
😆Ok, I'm getting only a non-unique now, but a different one.
Hmm, that is one of the resources that still has the default create action.
can you show me the full error?
🤔
can you delete your
_build
directory and recompile?Yep, just a minute
Same
well
yeah
damn
one sec
can you try main again?
Sure
Yep, looks good.
Awesome mate, thanks!