-
|
Our root query has many fields, nearly all backed by Rails models. Many models implement common functionality by way of traits or what Rails calls "concerns". For example, models may implement the Currently our fields and resolvers are not very DRY since we're repeating argument definitions and resolver logic for each field. As a simple example: field :posts, Post.connection_type, null: true do
argument :is_published, Boolean, required: false
end
def posts(is_published: nil)
scope = Post.all
unless is_published.nil?
scope = scope.where(is_published:)
end
scope
endWe have sort of tidied it up by moving the logic of each trait's scopes somewhere else. The result then looks like: def posts(**args)
scope = Post.all
scope = Publishable::QueryFilter.apply(scope, **args)
# ... other traits
scope
endBut this still isn't very DRY since you have to add the field extension for the argument and add a line to the resolver body. Ideally each field would reference each trait just once. I took a shot at implementing this as a field extension: module Publishable
class FieldExtension < GraphQL::Schema::FieldExtension
ARG_NAME = :is_published
def apply
field.argument(ARG_NAME, Boolean, required: false)
end
def after_resolve(value:, arguments:, **rest)
case arguments[ARG_NAME]
in nil
value
in true | false => is_published
value.where(is_published:)
end
end
end
endAnd then in the root query type: field :posts, Post.connection_type, null: true, extensions: [Publishable::FieldExtension]
def posts(**args)
Post.all
endBut this fails with an error: Is using |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 6 replies
-
|
Yes, that's right -- graphql-ruby/lib/graphql/schema/field/connection_extension.rb Lines 24 to 29 in 4c32e90 What they return is connection instance, not the list object (eg ActiveRecord scope). Hence the error message here: your code expected a scope, but received a connection object. So, I'd suggest a different integration points. Some options are:
I'd be happy to help explore one of those options if one of them stands out to you. Let me know what you think! |
Beta Was this translation helpful? Give feedback.
-
Always your best bet, IMO -- something that you can test in isolation (without touching any GraphQL surfaces), then call from GraphQL. Promises can make that tough, since usually it's handled inside GraphQL. But you can call GraphQL::Batch directly in your tests to simulate that: https://github.com/Shopify/graphql-batch#unit-testing I'd be happy to suggest a kind of Ruby data structure that might work for this if you could you share an example of a resolver that uses GraphQL-Batch as part of this flow. That's the trickiest one, since it requires Personally, I'm not a huge fan of field extensions. GraphQL-Ruby uses them for some magic but they just have never worked really smoothly. In |
Beta Was this translation helpful? Give feedback.
Ok, cool to know you're using Dataloader, not GraphQL-Batch. That makes it simpler because we don't have to worry about whether these methods return Promises or not. For unit testing, you can wrap the code with
GraphQL::Dataloader.with_dataloading { ... }(doc) to create a batch-loading context.I think the first thing I would try is to generate Resolver classes that could be tested apart from GraphQL queries. Here's a take on that:
Generate Resolver classes based on scope, resolves_to, and filters