Introducing SearchObject GraphQL Plugin
When I started using GraphQL, I immediately saw, that SearchObject would be a perfect fit for search resolvers.
Having a GraphQL query to fetch the first 10 most recent published news posts would look something like this:
query {
  posts(first: 10, categoryName: "News", order: "RECENT", published: true) {    
    id  
    title  
    body  
    author {      
       id
       name
    }
  }
}
And it would have a corresponding SearchObject:
class Resolvers::PostSearch  
  include SearchObject.module  
  
  scope { Post.all }    
  
  option :categoryName, with: :apply_category_name_filter
  option :published, with: :apply_published_filter
  option :order, enum: %i(RECENT VIEWS LIKES)  
  
  # ... definitions of the option methods
end
So clean. ☀️
But then, PostSearch have to be connected with GraphQL Ruby gem:
PostOrderEnum = GraphQL::EnumType.define do
  name 'PostOrder'
  value 'RECENT'
  value 'VIEWS'
  value 'LIKES'
end  
Types::QueryType = GraphQL::ObjectType.define do
  name 'Query'
  field :posts, types[Types::PostType] do
    argument :categoryName, types.String  
    argument :published, types.Boolean  
    argument :order, PostOrderEnum
    resolve ->(_obj, args, _ctx) { Resolvers::PostSearch.results(filters: args.to_h) } 
  end
end
That isn't so bad. ?
But then, thinking about how can this code can change in the future:
- adding/removing options would involve going to both files
- adding a new order option would mean searching for the PostOrderEnumand manually sync it withPostSearchenum
- reusing PostSearchin other types, for queries like:query { user(id: 1) { posts(published: true) } }- requires copy and paste argument/type definitions
- which makes updating the resolver even harder
 
Yikes! ? ?
This is where SearchObject::Plugin::GraphQL comes in. It puts type definitions and the resolver itself:
class Resolvers::PostSearch  
  # include the the plugin
  include SearchObject.module(:graphql)
    
  # documentation and type about this resolver
  # can be provided into the resolver itself
  type types[Types::PostType]
  description 'Lists posts'
  # enums or other types can also be nested
  OrderEnum = GraphQL::EnumType.define do
    name 'PostOrder'
    value 'RECENT'
    value 'VIEWS'
    value 'LIKES'
  end
    
  scope { Post.all }    
  
  # options just need to have a their type specifed.
  option :categoryName, type: types.String, with: :apply_category_name_filter
  option :published, type: types.Boolean, with: :apply_published_filter  
  # enums are automatically handled
  option :order, type: OrderEnum
  
  # ... definitions of the option methods
end
Then PostSearch can be used just as GraphQL::Function:
Types::QueryType = GraphQL::ObjectType.define do
  name 'Query'
  field :posts, function: Resolvers::PostSearch
end
Now, changing filter options requires changing only a single file. PostSearch can be reused in other types, by just adding function: Resolvers::PostSearch.
For more information check SearchObject::Plugin::GraphQL example.