• Home

  • Custom Ecommerce
  • Application Development
  • Database Consulting
  • Cloud Hosting
  • Systems Integration
  • Legacy Business Systems
  • Security & Compliance
  • GIS

  • Expertise

  • About Us
  • Our Team
  • Clients
  • Blog
  • Careers

  • CasePointer

  • VisionPort

  • Contact
  • Our Blog

    Ongoing observations by End Point Dev people

    Converting GraphQL Ruby Resolvers to the Class-​based API

    Patrick Lewis

    By Patrick Lewis
    February 28, 2019

    GraphQL Ruby code

    The GraphQL gem is a great tool for any Rails developer who wants to add a full-​featured GraphQL API to their Rails applications.

    I have been using GraphQL to serve an API in one of my Rails applications since late 2017 and have been very happy with the features and performance provided by the gem, but some of the domain-​specific syntax for building out my API schema never felt quite right when compared to the other Ruby code I was writing in my projects. Fortunately, the 1.8.0 release of the GraphQL Ruby gem brought with it a new default class-​based syntax while remaining compatible with existing code that predated the change.

    The Class-based API guide that accompanied the changes does a good job of describing the upgrade path for developers who need to convert their existing schemas. The old .define syntax is eventually going to be removed with version 2.0 of the gem, so I was interested in converting my existing API over to the new style, both to see what benefits the newer syntax provides and to ensure that the API schema remains compatible with future releases of the gem.

    The GraphQL gem provides some rake tasks like graphql:upgrade:schema and graphql:upgrade:member for automatic conversion of the older-​style .define files to the newer class-​based syntax. It worked quite well for updating my type definitions, but I also make heavy use of resolvers for containing the logic needed to return values in my GraphQL fields, and there was no way to automatically convert those files.

    I found that the process of manually converting my resolvers was pretty straightforward and provided some benefits by cleaning up my QueryType file that was starting to look a little unwieldy.

    Here is a before and after for comparison:

    Old pre-1.8 ‘.define’ syntax for types:

    # app/graphql/types/query_type.rb
    Types::QueryType = GraphQL::ObjectType.define do
      description 'Queries'
    
      field :instructor_names, types[types.String] do
        description 'Returns a collection of instructor names for a given range of years'
    
        argument :semester, !Inputs::SemesterInput
        argument :past_years, types.Int, 'Include instructors for this number of past years'
    
        resolve Resolvers::InstructorNamesResolver.new
      end
    end
    

    New class-based syntax for types:

    # app/graphql/types/query_type.rb
    class Types::QueryType < Types::BaseObject
      description 'Queries'
    
      field :instructor_names,
          description: 'Returns a collection of instructor names for a given range of years',
          resolver: Resolvers::InstructorNamesResolver
    end
    

    Old pre-1.8 ‘.define’ syntax for resolvers:

    # app/graphql/resolvers/instructor_names_resolver.rb
    module Resolvers
      # Return collections of instructor names based on query arguments
      class InstructorNamesResolver
        def call(_obj, args, _ctx)
          semester = args[:semester]
          past_years = args[:past_years] || 0
          term_year_range = determine_term_year_range(semester, past_years)
    
          CourseInstructor
            .where(term_year: term_year_range)
            .group(:first_name, :last_name)
            .pluck(:first_name, :last_name)
            .map { |name| name.join(' ') }
        end
    
        private
    
        def determine_term_year_range(semester, past_years)
          term_year_max = semester[:term_year]
          term_year_min = term_year_max - past_years
    
          term_year_min..term_year_max
        end
      end
    end
    

    New class-based syntax for resolvers:

    # app/graphql/resolvers/instructor_names_resolver.rb
    module Resolvers
      # Return collections of instructor names based on query arguments
      class InstructorNamesResolver < Resolvers::Base
        type [String], null: false
    
        argument :semester, Inputs::SemesterInput, required: true
        argument :past_years, Integer, 'Include instructors for this number of past years', required: false
    
        def resolve(semester:, past_years: 0)
          term_year_range = determine_term_year_range(semester, past_years)
    
          CourseInstructor
            .where(term_year: term_year_range)
            .group(:first_name, :last_name)
            .pluck(:first_name, :last_name)
            .map { |name| name.join(' ') }
        end
    
        private
    
        def determine_term_year_range(semester, past_years)
          term_year_max = semester[:term_year]
          term_year_min = term_year_max - past_years
    
          term_year_min..term_year_max
        end
      end
    end
    

    The main things to note here are:

    • The class definitions for Types::QueryType and Resolvers::InstructorNamesResolver, which now inherit from base GraphQL classes.
    • The type and argument definitions are moved out of the query_type file, cleaning it up greatly, especially for larger schemas with many fields.
    • The call method in InstructorNamesResolver is now resolve, and accepts arguments using named parameters (or **args if you want to retain the previous syntax).

    Overall, I’m really happy with the newer GraphQL API syntax and think that its more idiomatic Ruby is easier to work with and feels more familiar than the older style. I look forward to building out my API schema further and attempting things like replacing my use of Resolvers with plain old Ruby objects in order to make it even easier to test the logic they contain.

    ruby graphql api


    Comments