Using old style definitions we have been splitting QueryType into multiple files using the following solution:
QueryType = GraphQL::ObjectType.new.tap do |query|
query.name = 'Query'
query.description = 'The query root of this schema. See available queries.'
query.interfaces = []
query_types = [
ClientQueryType,
AccountQueryType,
InstitutionQueryType,
]
query.fields = FieldCombiner.combine(query_types)
end
Example of *QueryType:
ClientQueryType = GraphQL::ObjectType.define do
field :clients, types[ClientType] do
description 'Find clients by ID'
argument :ids, types[!types.ID]
resolve ->(_, args, _) {
Client.where(id: args['ids'])
}
end
end
And the field combiner:
class FieldCombiner
def self.combine(query_types)
Array(query_types).inject({}) do |acc, query_type|
acc.merge!(query_type.fields)
end
end
end
I am having a hard time using this concept (or coming up with a similar one) for class based types, when I execute an empty query {} I get the following:
NoMethodError: undefined method `edges?' for #<GraphQL::Schema::Field:0x00007fbeb7f51900>
And it happens on the first field that gets added to the schema, I believe is something related to the fact that fields returning old style definition?
I will be investigating more, is there a blessed approach for breaking up a huge QueryType into multiple objects?
Not sure this is a good way of doing it, but I'm attempting to add a DSL that allows for something like
class Article
queryable_on :id, :title
end
queryable_on will automatically generate the following queries: article(id: ID), articleByTitle(id: ID) (breaking consistency for id cause articleByID seems redundant)
The way I'm doing it is by reopening QueryType using class_eval. Metaprogramming is evil though, so it's probably not the most reusable or readable solution.
This is what I have right now:
QueryType.class_eval do
field model, Module.const_get("#{model}Type"), null: true do
description "Query #{model} by ID"
argument :id, query_type, required: true
resolve -> (obj, args, ctx) { PostsResolver.show(id) }
end
end
I'm also really interested in this â should be possible to do with concerns, somehow? Perhaps some sort of GraphQL::Concern to extend would be really nice, the query_type file gets very large in big projects
I wouldn't necessarily agree that a concern would be the right approach here, IMHO mixins/concerns that one only mixes in in one place are a bit of an antipattern ;) But some sort of composition helper, so one could say
class QueryType < ...
exposes ThisLogicalUnitQueries
exposes ThatLogicalUnitQueries
# ...
end
Would be very nice indeed. As a matter of fact, this helper could actually run class_eval under the hood as shown in @NicholasLYang's example above :)
Same goes for MutationType too, the one we have isn't very large _yet_, but it is definitely going to amass quite a bit of stuff soon.
My interpretation of this issue is the desire to break up a graphql type across multiple files, rather than across multiple objects (which is the way the issue is phrased). So maybe I'm misunderstanding something, but I think you're overthinking this problem (or maybe I'm underthinking it)?
I've found it very easy to define a graphql type across multiple files (both in the current class-based API, and the old .define API).
In my project I have a MutationRoot class with the base definition in app/graphql/root/mutation_root.rb
module Types
class MutationRoot < BaseObject
graphql_name "Mutation"
end
end
And I have a folder app/graphql/mutations with individual mutation files such as app/graphql/mutations/person.rb
require_relative '../root/mutation_root'
module Types
class MutationRoot
field :personUpdate, PersonType, null: true do
description "Update a person's properties"
argument :person_id, ID, required: true
argument :property, Inputs::Person::PropertyUpdate, required: true
end
end
end
This works fine for splitting up files. I just make sure the mutation_root.rb file is loaded before the individual mutation files.
For this matter, in the pre-class version of graphql-ruby, I similarly split up my mutation type by calling .define on the MutationRoot class multiple times, which works fine. I.e.
In app/graphql/mutations/person_mutations.rb
require_relative '../root/mutation_root'
GraphTypes::MutationRoot.define do
field :personUpdate, !GraphTypes::Person do
description "Update a person's properties"
argument :personId, !types.ID, as: :person_id
argument :property, !GraphInputs::PersonPropertyUpdate
resolve ->(obj,args,ctx) {
result = Person::Update.(args.to_h, 'current_user' => ctx[:current_user])
if result.success?
::PersonInstance.new(result[:person], ctx[:current_perspective])
else
GraphQL::ExecutionError.new(result[:errors].join(' '))
end
}
end
end
In app/graphql/mutations/contact_list_mutations.rb
require_relative '../root/mutation_root'
GraphTypes::MutationRoot.define do
field :contactListCreate, !GraphTypes::ContactList do
description "Create a contactcontactLlist"
argument :property, !GraphInputs::ContactListPropertyCreate
resolve ->(obj,args,ctx) {
params = args.to_h
params[:owner_ids] = [ctx[:current_perspective].id]
result = ContactList::Create.(params, 'current_user' => ctx[:current_user])
if result.success?
result[:contact_list]
else
GraphQL::ExecutionError.new(result[:errors].join(' '))
end
}
end
end
This worked because .define mutates the caller.
I don't understand the desire to create additional DSL to accomplish this. Seems unnecessary?
At least for me, I'm attempting to do what Rails did for resource routes: make a concise, declarative DSL for explaining the queries in types. I agree that if you're simply trying to lower the amount of code in query_type.rb, then reopening the class is perfectly fine. But in my case, I'm trying to make touching QueryType a rare occurrence (much like manually defining resources in config/routes.rb versus just using resources :articles)
@NicholasLYang gotcha. That makes sense. Personally, I don't think that approach makes sense for my own app, and I don't immediately like the idea of searching through every object type definition file to see how the query object is defined, but maybe I would have designed my graphql API differently had DSL like this been around (and hence it would make more sense).
Yeah, my eventual goal is to make something that's like Rails + GraphQL Ruby, but minus all the stuff I don't use anymore (views, asset pipeline, etc.)
@thefliik
This works fine for splitting up files. I just make sure the
mutation_root.rbfile is loaded before the individual mutation files.
Please excuse my ignorance. How does one ensure the load order of these files?
I'm testing things around with your gem for a little while now and there's something I really don't get, and it's about what everyone's talking about in this issue, and code scalability.
For mutations you have this sort of things: http://graphql-ruby.org/mutations/mutation_classes.html
This is perfect, it splits up things into different files and you limit mutation_type.rb to the bare minimum. You can add something like /mutations/create_something.rb
module Mutations
class CreateSomething < Mutations::BaseMutation
argument :something
field :result_field, String, null: true
def resolve(something:)
end
end
end
Then you just have to add this into the mutation_type.rb
module Types
class MutationType < Types::BaseObject
field :createSomething, mutation: Mutations::CreateSomething
end
end
Why isn't there any /queries/ folder doing the exact same thing for queries ? I'm new in the GraphQL world so I tried random things like
module Queries
class GetCurrentUser < GraphQL::Schema::Queries
# some logic abstracted
end
end
Then you could write something like
module Types
class QueryType < Types::BaseObject
extend ActiveSupport::Concern
field :currentUser, Types::User, null: true, query: Queries::GetCurrentUser
end
end
Obviously, it did not work because it's not implemented, or maybe I missed something in the documentation.
Anyway, aren't we suppose to have some kind of cohesion between mutations and queries in the structure ? Why can't i abstract the logic of my queries into multiple files ? Is it under progress ?
I'm trying to integrate your gem to a fairly small project and my query_type.rb file is over 100 lines and start to be unreadable and full of logic, not at all like a route file because it contains several methods. We are talking about 3 models integrated top right now, I can't imagine when it grows.
If I lead myself into the wrong direction, don't hesitate to correct me đ
It works for me:
module Queries
module Companies
extend ActiveSupport::Concern
included do
field :companies, Types::CompanyType.connection_type, 'ć·„ćć
ŹćžćèĄš', connection: true, null: false do
argument :name_contains, String, 'æłšćć·„ććć
ć«', required: false
end
end
def companies(**args)
end
end
end
then
class QueryType < Types::BaseObject
include Queries::Companies
end
@Loschcode
I was facing the same problem. rmosolgo pointed out the usefulness of resolvers for queries and they feel almost exactly like the Mutation interface, which is what I was looking for. Let me know if you have anymore questions.
Here's an example:
Folder structure:
app /
|__ graphql /
|__ mutations /
|__ queries /
| |- base_query.rb
| |- some_resource.rb
|__ types /
| |- query_type.rb
| |- some_resource_type.rb
|- schema.rb
Resolvers:
module Queries
class BaseQuery < GraphQL::Schema::Resolver
# methods that should be inherited can go here.
# like a `current_tenant` method, or methods related
# to the `context` object
end
end
module Queries
class SomeResource < BaseQuery
type Types::SomeResourceType, null: <false|true>
description "Query all SomeResources"
argument :name, String, required: false, default_value: nil
def resolve(name:)
return SomeResource.all if name.nil?
SomeResource.where('name like ?', "%#{name}%")
end
end
end
md5-4e57c8d7541b8df5d3b411f8e9a5ebae
module Types
class QueryType < GraphQL::Schema::Object
field :some_resource, resolver: Queries::SomeResource
end
end
Thanks for your input @hooopo but that's not a parallel solution, just a workaround and I wish to avoid tricks as much as I can
@bryantbj this actually gets really close from a more solid solution, I made it work within my project pretty easily, it's nice enough for now đ
Would be really best to have a similar structure to Mutations though, but I guess i'll go for this now đ
@bryantbj when implementing your solution I get the following error:
uninitialized constant Types::QueryType::Queries
/app/graphql/types/query_type.rb:5:in `<class:QueryType>'
/app/graphql/types/query_type.rb:4:in `<module:Types>'
/app/graphql/types/query_type.rb:3:in `<top (required)>'
And then
A copy of Types has been removed from the module tree but is still active!
I am using Rails 5.2.1 with Ruby 2.5.1 and graphql-ruby 1.8.11
Could it be related to this?
@Loschcode Happy to help! After rmosolgo pointed me in that direction, I noticed a reference to that pattern in the docs. I don't know how I missed it!
@egonm12
_Regarding the load error:_
It seems like Rails is looking for Queries in the local namespace. You could try preceding your reference to Queries with ::, making it ::Queries::YourResolverNameHere to reset the namespace. If that doesn't work, would you mind pasting the block of code when it's convenient for you so we have some context?
_Regarding the Types error:_
And then
A copy of Types has been removed from the module tree but is still active!
I get this regularly. It's usually a typo somewhere. Most recently, it was me typing resovler: instead of resolver:. It's a frustrating error (like that linked issue describes) because it doesn't really indicate where the problem is.
@bryantbj @rmosolgo This is exactly what I've been looking for! I've been using the mixin approach for a while now but my app is getting complicated enough that I was starting to worry about method name collisions and all the other limitations you run into with module mixins. This resolver approach is perfect, thank you!
When refactoring from the .define to the class variant I also hit this problem. The QueryType becomes too big and unmaintainable. @hooopo's solution is just a workaround - but that was the "old" field-combiner method, too. It would be nice to create a real solution here, which even with the resolver method of @bryantbj the QueryType becomes huge in larger projects - just think on 200-300 queries.
Also the definition of a resolver which only does the following at the end:
::User.find(user_id)
seems to me to generate a lot of code and makes the whole thing rather confusing than writing one query file per module and putting them together at the end.
@rmosolgo is this really the best way to handle the Query and MutationType in larger projects? After all, any lint-test etc. will alarm at these file lengths. If I need the entire QueryType for an overview, I export the schema and use an appropriate viewer program, which is clearer anyway.
Because query ultimately is a type in my GraphQL schema it makes sense to me to have all the field definitions in one place. Maybe add the same exception for file length on your linter that you would for something like schema.rb or routes.rb?
@SeanRoberts ok, if you see it as a comparison with the routes.rb it makes sense. I was just astonished, because you generally try not to let Rails classes get too big.
Thanks for answering
I think one solution to this, would be to allow defining Queries the same way mutations can be defined:
class Types::MutationType < Types::BaseObject
field :createThing, mutation: Mutations::Thing::CreateThign
end
Similarly, field definitions - that used to use resolve: keyword argument, but now uses methods, could use a method reference, e.g:
module Types::Queries::Things
def get_things
# This is the resolver for a list of things
end
end
class Types::QueryType < Types::BaseObject
include Types::Queries::Things
field :things, [Types::Thing], null: false, query: :get_things
end
or even just structure them almost identically:
class Types::Queries::ListThings < Types::BaseQuery
description "Fetch a list of things"
argument :arg1, String, required: false
def resolve(arg1: nil)
# This is the resolver for a list of things
end
end
class Types::QueryType < Types::BaseObject
include Types::Queries::Things
field :things, [Types::Thing], null: false, query: Types::Queries::ListThings
end
This would allow you to either use a very big QueryType class, with all the fields + resolver methods in the same file (which I think works very well for smaller projects), but also allow you to split up the code base and avoid having a very big bloated QueryType which has often been a problem for me personally
@Amnesthesia you might like GraphQL::Schema::Resolver! It looks just like what you posted, and it's the base class of mutations: http://graphql-ruby.org/fields/resolvers.html
I think the analogy to routes.rb is quite good. Types::Query isn't a normal Rails model or controller, it's more like a router that takes API requests and passes them to business logic. Hopefully the adapter code between GraphQL API inputs and the corresponding business logic is pretty minimal!
Thank you @rmosolgo! I didn't see @bryantbj had commented about that further up too â I just rewrote everything using resolvers and cleaned up my code a lot
I believe the pattern of breaking up your graphql schema/resolvers into verticals and merging them together in a single place is easier in other graphql implementations. I don't think graphql-ruby should impose restrictions that prevents the same pattern. I think a vertical module should be able to define its entire graphql structure, like a rails engine. we've learned that the rails's horizontal separations leads to difficult to maintain apps. I would love to see first class support for merging of schemas that doesn't require a large root query.rb with all of the definitions in it.
Yeah, I guess I'm not _opposed_ to something like that in GraphQL-Ruby, but it's not a priority for me.
Maybe there could be an abstraction _like_ a GraphQL Interface, but without a name. (When you implement interfaces, the interface becomes part of your schema, but maybe we could have an even _more_ abstract "container of fields" which doesn't even _exist_ in the GraphQL side of things, only in Ruby!)
@rmosolgo I like this Resolver pattern as a solution to the problem described here, but I also see in the docs that using Resolvers in general is discouraged. Can you give your thoughts on that? Is it simply discouraged because it's easy to end up with fat resolvers? If that's the case, I don't think it's any worse than doing it with methods, right?
Assuming I'm not missing something, I would love to see the docs updated to:
I'd even go so far as having this type of set up be the generated example, because it's more consistent with Mutations, and it's cleaner/more maintainable
In agreement with @dkniffin, it wasn't clear to me why the resolver pattern is discouraged in the docs, as it would probably lead to a more modular code structure.
I'd happily accept an improvement to the docs as mentioned above. You'll find an "Edit" button on the bottom of the page which will start a web-based PR workflow. Please give it a try!
Thanks for all the discussion here. I don't have any other work planned for this, so I'm closing it.
For those who are looking for how to implement the feature using Resolver, try this:
types/query_type.rb
class Types::QueryType < Types::BaseObject
field :searchBooks, resolver: Queries::SearchBooks
end
queries/search_books.rb
class Queries::SearchBooks < Queries::BaseQuery
type [Types::BookType]
argument :q, String
def resolve(q:)
context[:viewer].books.search(q)
end
end
queries/base_query.rb
class Queries::BaseQuery < GraphQL::Schema::Resolver
extend GraphQL::Schema::Member::HasFields
extend GraphQL::Schema::Resolver::HasPayloadType
class << self
# Override this method to handle legacy-style usages of `MyMutation.field`
def field(*args, &block)
if args.empty?
raise ArgumentError, "#{name}.field is used for adding fields to this mutation. Use `mutation: #{name}` to attach this mutation instead."
else
super
end
end
def visible?(context)
true
end
end
end
keep in mind that the error RuntimeError: Unexpected parent_type: will occur if there are fields with the same model name, like class User < ActiveRecord::Base and field :user, resolver: Queries::User.
Queries
@acro5piano I tried this pattern but am getting argument errors wrong number of arguments (given 2, expected 0..1). This is my code:
class Queries::SearchResources < Queries::BaseQuery
type [Types::ProfileType], null: true
description 'Find Profiles by Name'
argument :search_value, String, required: true
argument :limit, Int, required: false
def resolve(search_value:, limit: 10)
sanitized_sql = Profile.sanitize_sql_for_assignment(<redacted>).concat(" limit #{limit}")
Profile.find_by_sql(sanitized_sql)
end
end
``ruby
class Queries::BaseQuery < GraphQL::Schema::Resolver
# methods that should be inherited can go here.
# like acurrent_tenantmethod, or methods related
# to thecontext` object
extend GraphQL::Schema::Member::HasFields
extend GraphQL::Schema::Resolver::HasPayloadType
class << self
# Override this method to handle legacy-style usages of `MyMutation.field`
def field(*args, &block)
if args.empty?
raise ArgumentError, "#{name}.field is used for adding fields to this mutation. Use `mutation: #{name}` to attach this mutation instead."
else
super
end
end
def visible?(context)
true
end
end
end
```ruby
# QueryType primary file
class Types::QueryType < Types::BaseObject
# Add root-level fields here.
# They will be entry points for queries on your schema.
add_field(GraphQL::Types::Relay::NodeField)
add_field(GraphQL::Types::Relay::NodesField)
field :search_resources, resolver: Queries::SearchResources
#... queries not migrated below ...
end
Edit:
It's not clear to me why my resolves are having issues.
[1] pry(Types::QueryType)> field :search_resources, resolver: Queries::SearchResources
ArgumentError: wrong number of arguments (given 2, expected 0..1)
from /Users/lassitergregg/.rvm/gems/ruby-2.6.5/gems/graphql-1.11.1/lib/graphql/schema/resolver/has_payload_type.rb:16:in `payload_type'
but looking at this guide, it seems like i'm doing it correctly? https://graphql-ruby.org/fields/resolvers.html
using graphql 1.11.1
@lassiter
[1] pry(Types::QueryType)> field :search_resources, resolver: Queries::SearchResources ArgumentError: wrong number of arguments (given 2, expected 0..1) from /Users/lassitergregg/.rvm/gems/ruby-2.6.5/gems/graphql-1.11.1/lib/graphql/schema/resolver/has_payload_type.rb:16:in `payload_type'
It looks like the error is occurring in the GraphQL::Schema::Resolver::HasPayloadType extension. I also use resolvers in my project, but I don't use this extension and things work fine. Is it necessary to include it? Can you comment it out and see if it works?
@lassiter
I use 1.9.2 and it's working fine. You should try change:
type [Types::ProfileType], null: true
to
type [Types::ProfileType]
null: true
Also remove add_field from QueryType and try again.
@bryantbj
I also use HasPayloadType and working fine. However trying to comment out could help to solve the error.
keep in mind that the error RuntimeError: Unexpected parent_type: will occur if there are fields with the same model name, like > class User < ActiveRecord::Base and field :user, resolver: Queries::User.
@acro5piano - how do you avoid this? besides the obvious and difficult answer (Don't name your fields the same as your class?) I have a similar issue and need to return the resource after an update mutation so the cache will update it's value, but the ActiveRecord model and the field name are the same, and must be because the application requires it on both front and back ends!
@rcaf-anthony
I avoid it by using other name, and always add ...Type suffix for type classes. In that example,
class User < ActiveRecord::Base
end
class Types::QueryType < Types::BaseObject
field :user, resolver: Queries::GetUser
end
class Queries::GetUser < Queries::BaseQuery
type Types::UserType
# ...
end
class Types::UserType < Types::BaseObject
field :id, ID, null: false
end
I might be misunderstanding the question but if you're inside Types::User and you need to refer to your model user User the syntax is ::User
What about in the context of a mutation? I'm encountering the error you describe and the circumstances are similar. I didn't create the codebase, but it's similar to the following;
class User < ActiveRecord::Base
has_many :groups
end
class Types::UserType < Types::BaseObject
field :id, ID, null: false
field :user, String, null: false
field :user_order, Integer, null: false
field :groups, [Types::UserGroupType], null: false
end
class Mutations::UpdateUser < Types::BaseMutation
argument :id, ID, required: true
argument :user, String, required: true
argument :user_order, Integer, required: true
field :user, Types::UserType, null: true
field :redirectLink, String, null: true
field :errors, [String], null: false
def resolve(id:, user:, user_order:)
userToEdit = User.find(id)
userToEdit.user = user
userToEdit.user_order = user_order
if userToEdit.save!
{
user: userToEdit,
redirectLink: "/admin/users/#{userToEdit.id}",
errors: []
}
else
{
user: null,
redirectLink: null,
errors: userToEdit.errors.full_messages
}
end
end
end
Bear in mind this is only an illustration, but I get the "Unexpected Parent Type:" Error (with no explanation of what parent type was unexpected) when I start my application and access the main page. the UserGroupType also has a field of another type, which also has a field of another type. I didn't think that this would be a problem.
I only wanted to return the "UserType" type, so that ApolloClient will update the cache automatically. Instead, I'm hacking a workaround of returning the id, user_name, and user_order in the mutation and finding and removing the item in the cache that was updated, and then adding it back with its changed values with a writeFragment gql query.
Not sure if you have this problem in your exact code but in the example you've got there the method is def user but I think it should be def resolve. Additionally the user field is expecting a string but the method is returning a User object.
Edit: For your purposes you'd want
field :user, Types::UserType, null: false
I'll correct the example, and when I have Types::UserType, is when I get the error.
if I return this.
field :id, ID, null: true
field :user, String, null: true
field :user_order, Integer, null:true
field :rediectLink, String, null: true
field :errors, [String], null: false
I get no error, this plus the cache manipulation is my workaround. My if/else changes appropriately as well. The example in the upper comment is what I'd like, but I get the error for. and the above is how I've worked around it. I'd like to use ApolloClient's native cache update ability when I update an item, but this one class seems to shoot me in the foot for that!
I also noticed that the user_order argument has Int not Integer, but it's currently coded with the workaround and working, so I'm guessing that Int and Integer are interchangeable in Ruby (I'm a .net guy)...
Hmm... it looks like there are a number of little mistakes and typos that can lead to this error: https://github.com/rmosolgo/graphql-ruby/issues/2303
Unhelpful errors have always been an issue for me with this gem but you might be able to get something more useful if you downgrade to 1.8.x from 1.9. I've been running that for a couple of years with limited problems. Personally I also just use the same classnames for my models, so I don't even have Types::UserType, I just have Types::User
I'll try first to fix the Int => Integer and see if that works, if that doesn't then I'll try the downgrade. The original coders used the Type suffix for all the Graphql types, so I'll continue that practice, but I really thought that I could just return the Types::UserType object and the cache would be updated!
I've spent nearly a full day on this already though and I have a workaround that works, I just dislike having to hack together stuff that should work OOTB. I even tried returning ONLY the UserType object, and skipping the errors and redirect link, since the redirect should be front end anyway, and the errors won't be used at this point. Even that failed with the same error.
I just wanted to avoid having to have another round trip to the database to update the UI.
Yeah I don't quite have the full picture of what you're doing but what you're describing is how I do all my mutations pretty much. Return the object so that Apollo-Client can read the updates instead of having to do refetchQueries. You probably have some little error or typo somewhere along the way and GraphQL-rb isn't able to give you a more helpful error message.
Do you return anything other than the object?
Up the chain of types I have an XML file being called from another website and opened with Nokogiri. I actually get two errors, the first is that there's no such file or directory (the XML file that's accessed over the web, and the second is the Unexpected Parent Type So I'm wondering now if it isn't the XML files that's causing the error, but if I follow the stack trace back, it starts at the top the file opening the XML file and goes backwards to the class discussed above.
Sometimes yeah. I return all kinds of stuff depending on what the mutation calls for. Try downgrading to 1.8 and see what you get.
@rmosolgo @SeanRoberts I renamed the field to payload and had it try to return a Types::UserType, and I still get this error, so it's not because the name of the field and the name of the model are the same. I even tried with another class but the stack trace shows that it follows the same path back to the class that uses the online XML file, both classes I'm trying this with are used in another class that then goes back to the XML accessing class.
I downgraded to 1.8 and 1.8.18 and got errors about ISO8601Date/ISO8601DateTime, and this is still a problem. After fixing the issue with the XML file I was able to return the type as long as the type doesn't contain a field with the same name....So it's still an issue.
I fixed it by renaming the return type to payload, and including the fields in the return message in the mutation gql query. The cache is still updated, and I can return other fields as well.
here's an example
Fields in class rb file
field :payload, Types::UserType, null: true
field :redirectLink, String, null: true
field :errors, [String], null: false
GQL Query in REACT front end
mutation UpdateUser($user: String, $userName: String) {
updateUser(user: $user, userName: $userName) {
payload {
id
user
userName
}
redirectLink
errors
}
}
the object's values in the cache are updated with the values in the payload item, and the screen is rendered with the updated values, so I'm good to go. I thought that I would have to name the payload item the same as the object type, but I'm OK with calling it payload
Thanks for your help @rmosolgo and @SeanRoberts
Thanks for your input @hooopo but that's not a parallel solution, just a workaround and I wish to avoid tricks as much as I can
@bryantbj this actually gets really close from a more solid solution, I made it work within my project pretty easily, it's nice enough for now đ
Would be really best to have a similar structure to
Mutationsthough, but I guess i'll go for this now đ
It's work for me.
# app/graphql/concerns/resolver_loader.rb
# frozen_string_literal: true
module ResolverLoader
extend ActiveSupport::Concern
module ClassMethods
def load_resolvers_for(domain_module)
resolvers_module = domain_module.const_get('Queries')
resolvers_module.constants
.map(&resolvers_module.method(:const_get))
.each { |resolver| field resolver.graphql_name, resolver: resolver }
end
end
end
# app/graphql/root_query.rb
class RootQuery < Types::BaseObject
include ResolverLoader
add_field GraphQL::Types::Relay::NodeField
# Core
load_resolvers_for Core::Metadata
# Accounts
load_resolvers_for Accounts
# CRM
load_resolvers_for Crm::Leads
load_resolvers_for Crm::Billing
end
and
# app/graphql/core/metadata/queries/lookup_environment.rb
module Core::Metadata
module Queries
class LookupEnvironment < Common::BaseResolver
type Core::Environment::Type, null: false
def resolve
{
environment: Rails.env,
}
end
end
end
end
Most helpful comment
@Loschcode
I was facing the same problem. rmosolgo pointed out the usefulness of resolvers for queries and they feel almost exactly like the Mutation interface, which is what I was looking for. Let me know if you have anymore questions.
Here's an example:
Folder structure:
Resolvers: