Hey guys,
I was looking for a way to get the parent objects from the ctx variable but could not find anything. Here is an example:
query {
user(id: 1) {
abilities(first: 10) {
edges {
node {
name
pricing {
cost
}
}
}
}
}
}
The `pricing resolver look like that:
field :pricing, Types::Ability::PricingType do
resolve -> ability, args, ctx {
user = ???
ability.pricing_for(user)
}
end
How can I get the user from the user(id: 1) root query?
Thank you!
There's not a way to get parent objects from the resolve function :S
Options I can think of are:
userId as an argument to pricing :S abilities so that you can access the user later on I can't think of any more :S
@gottfrois I found that you can get an Array of single Argument's by doing ctx.irep_node.parent.ast_node.arguments, but I agree that there _has_ to be a simpler way.
(I find the lack of getters here pretty discouraging...)
There's no way to get a true Arguments object, like the args one, w/ a to_h method, etc...
If ctx is my "context", why aren't there easy ways to access its actual data? 馃槶
def self.get_parent_argument_from_ctx(ctx, name)
arg = ctx.irep_node.parent.ast_node.arguments.find { |a| a.name == name }
arg ? arg.value : nil
end
I don't recommend going through the AST -- for example, if a client uses a variable, you'll get the name of the variable instead of a value.
You can backtrack irep_nodes using parent:
def self.get_parent_argument_from_ctx(ctx, name)
parent_node = ctx.irep_node.parent
parent_args = parent_node.arguments
# ^ Arguments
parent_args[name]
end
The above also does not solve the original issue since I'm looking for the resolved value for the user(id: 1) and not a way to get the arguments.
Is there a way to get the resolved value from the ast?
get the resolved value
I can't think of any way to backtrack and get that value :(
Thanks, @rmosolgo!
Sorry @gottfrois, I misread your question... Makes sense now!
context.parent.value
# #<User ...>
@gottfrois until #923 fixes this issue, what I've done is create a simple wrapper object that allows me to easily pass parents down like so:
class GraphqlWrapper
attr_reader :gwobject, :gwdata
def initialize(gwobject:, gwdata:)
@gwobject = gwobject
@gwdata = gwdata
end
def method_missing(name, *args)
if args.any?
@gwobject.public_send(name, args)
else
@gwobject.public_send(name)
end
end
end
In your case, instead of grabbing an abilities object with something like user.abilities.first, you would have:
ability = GraphqlWrapper.new(
gwobject: user.abilities.first,
gwdata: user
)
The resulting ability object would behave like a normal ability object, except you can call ability.gwdata on it to get the user.
Obviously the additional objects add some overhead, but depending on your use case, this could be an easy solution.
Thanks @thefliik for the tip!
This will be supported in 1.7.0, where you can access ctx.parent, for example:
ctx # => GraphQL::Query::Context::FieldResolutionContext
ctx.parent # => another GraphQL::Query::Context::FieldResolutionContext
ctx.parent.irep_node # => the query node parent object
ctx.parent.object # => the object used for resolving the parent field
ctx.parent.value # => the in-progress result for the parent field
Is it still possible to access FieldResolutionContext in the new class-based API? The instance method context returns a GraphQL::Query::Context instead of a GraphQL::Query::Context::FieldResolutionContext
No, those objects aren't passed in anymore. Since we're working on an object-by-object basis, there's no good way to inject those into each method without adding a lot of boilerplate.
In the meantime, attributes of ctx may be injected using extras:: http://graphql-ruby.org/type_definitions/objects.html#extra-field-metadata
I'm not sure about the long-term future here, so if you want to share something about your use case, please do, and I'll keep it in mind!
I think that will probably solve the issue, I'm also just trying to climb back up the chain to access my root object from a deeply nested one. The nested object has a computed value that's created from a combination of itself and the parent object, something kind of like this:
user(id: 1) {
favoriteBooks {
title
isbn
isAuthor
}
}
Where isAuthor would be computed by checking if book.author === user. If there's a better way to pass the user into the BookType or to do those kinds of calculations I'm definitely open to alternatives. I guess the correct thing would be to pull something like authoredBooks and favoriteBooks on the user and then let the client determine authorship, but that feels inconvenient and would be a downgrade from the existing REST API.
Thanks for responding so quickly and thanks for this great library!
Also, based on this comment it sounds like maybe what I want to do is actually discouraged in GraphQL, but that I could also probably just use the current_user from context because it's okay to treat that as implicit in the query.
Yeah, I agree here:
on the wrong object
Another design would allow a query like this:
user(id: 1) {
favoriteBooks {
isAuthor
book {
title
isbn
}
}
}
where favoriteBooks returns some _intermediary_ object, which has both user and book (something like a Favorite object?), then the Favorite.isAuthor field can naturally compare @user == @book.author, since it has both of those objects already.
But of course, sometimes we _really_ need parent, so it continues! 馃槅
That makes sense. I was hoping to do a drop-in replacement for our existing REST API, but in a lot of places it's really tailored to the client in ways that GraphQL probably can't or shouldn't be. The whole point of this exercise is to create something that works across multiple clients and multiple use-contexts though so it probably makes sense to revisit how some of the data is presented.
Thanks again for the help 馃槃
Hey @rmosolgo, regarding
if you want to share something about your use case, please do, and I'll keep it in mind!
I'm experimenting with schema stitching in graphql-ruby using graphql-remote_loader (which you helpfully linked to in #1812 馃憤) and prisma. The second graphql API I'm sending queries to is a largely identical to my graphql-ruby schema, so for the fields I want to stitch together, I simply "foward" the query on to the second API. This is accomplished by building up the second query piece by piece, merging all the pieces together, and sending it off.
i.e.
query { person { id } } + query { person { firstName } } = query { person { id\n firstName } }
Or, the more complex scenerio
query { person(where: {id: "1"}) { firstName } }
+
query { person(where: {id: "1"}) { friends(limit: 2) { id } } }
+
query { person(where: {id: "1"}) { friends(limit: 2) { firstName } } }
=
query { person(where: {id: "1"}) { firstName\n friends(limit: 2) { id\n firstName } } }
And this works! But....
And maybe you can see where I'm going with this, but in order to accomplish this from a deeply nested field, I need to crawl up the query tree and build the query string. (i.e. for the friends firstName field, I need to prepend its query string with query { person(where: {id: "1"}) { friends(limit: 2)
At the moment, the context object gives me access to the top level query, so I'm crawling from the top down and, since the entire query representation is in the context, I need to figure out what's applicable to whatever deeply nested field I'm currently looking at.
If the context let me enter the query tree at the current, deeply nested location however, then I could crawl up it nice and easy. Hopefully this makes sense.
Any suggestions? Definitely understand if this is just outside the scope of graphql-ruby at this point.
At the moment, I think the best way to handle this situation is to
field method which tags the field as one that should be stichedHaving tested this, it works.
It's been a while, but #2634 adds "scoped" context, a hash of values which are only available to _child selections_ of the field where they were added. For example, if you could set ctx.set_scoped(:author, author), and then for _child fields_ (and grandchild fields), ctx[:author] would return that value.
Most helpful comment
923 will add support for backtracking via