/ graphql

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 PostOrderEnum and manually sync it with PostSearch enum
  • reusing PostSearch in 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.