Hello @rmosolgo
Thank you for the Gem :)
Just been trying out GraphQL with rails and wondering, if I can eager load nested associations in parent type. For ex: If I have a model Post, which has many comments and then comment belongs to a user.
If I try to fetch this query:
query_string = "query getPostById { post(id: 1) {id, body, comments{body, user{id, email}}}}"
PostType = GraphQL::ObjectType.define do
name "Post"
description "A post entry"
interfaces [NodeIdentification.interface]
field :id, field: GraphQL::Relay::GlobalIdField.new('Post')
field :title, types.String, "The title of this post"
field :body, types.String, "The body of this post"
field :comments, -> { types[CommentType] }, "All comments of this post"
field :user, UserType, "User associated with this post"
field :comments_count do
type types.Int
description "Get comments count for this post"
resolve -> (post, arguments, context) {
post.comments.count
}
end
end
The above query runs 1 additional query per comment loading comment user. Normally, we would do like: post.comments.includes(:user), which would basically run two queries and get all comments plus its users.
Is I am missing anything?
Shopify has published a gem for this purpose! Check this one out: https://github.com/Shopify/graphql-batch (I haven't tried it yet)
thanks @rmosolgo will check this one and report usage.
@gauravtiwari To solve this, I access the child nodes within my resolve block to check if I will need to eager load that association:
if ctx.ast_node.children.map(&:name).include?('comments')
thanks @davidnorth for the tip :+1:
i wrote a really naive approach to this earlier today.
it could use a lot of work/refactoring, but maybe this will help someone/maybe people can provide feedback
also i can't imagine this works with fragments
class ActiveRecordField < GraphQL::Field
[...]
def resolve(object, arguments, ctx)
includes = map_includes(@model, ctx.ast_node.selections)
@model.includes(*includes).find(arguments["id"])
end
def map_includes(model, selections)
selections.map do |selection|
if selection.selections.present?
singular = selection.name.singularize.to_sym
plural = selection.name.pluralize.to_sym
nested = map_includes(singular.to_s.classify.constantize, selection.selections)
final_type = if model.reflections[singular].present?
# this is for has_one relationships
singular
elsif model.reflections[plural].present?
# this is for has_many relationships
plural
end
if nested.present?
{ final_type => nested }
else
final_type
end
else
nil
end
end.compact
end
end
:+1: glad to see some good options! I don't want to build this feature into graphql but I definitely want to support it, please open another issue if there's something that would make it easier.
0.16.0 introduces ctx.irep_node. You can inspect the node's children to see _all_ fields which were queried and what types they'll apply to:
ctx.irep_node.children.each do |irep_child|
irep_child.name # alias or field name
irep_child.definition # => <#GraphQL::Field ...> Field definition
irep_child.on_types # => Set<types...> Type definitions which this field will apply to
irep_child.ast_node # => <#GraphQL::Language::Nodes::Field ... >
end
I haven't had a ton of time to play around with it, but this is the data structure used for query execution now, I think it will provide better support for look-ahead too!
Here's another option https://github.com/hoverinc/graphql-eager_load
Most helpful comment
@gauravtiwari To solve this, I access the child nodes within my resolve block to check if I will need to eager load that association: