DEV Community

Lee Hambley
Lee Hambley

Posted on

3 1

Union and interface types in GraphQL Ruby

This is a quick post of a self-contained MiniTest example of how to implement resolve_type on a GraphQL::Schema if you are using graphql-ruby without using the Ruby DSL to define your schema.

graphql-ruby provides two ways to create a schema, one from the .graphql file directly (from_definition) and by using the "class based definition"

Taking a Schema Definition Language (SDL) such as GraphQL and hiding it behind a Domain Specific Language (DSL) in a higher level turing-complete language strikes me as utterly irresponsible, the value of an SDL is in the interchange format, and for us that was an absolute necessity.

Many features of graphql-ruby behave differently, or simply don't work at all, or are undocumented if you use the SDL approach rather than their preferred DSL, we run into issues with it from time to time.

In this case, without the DSL the Gem cannot infer what type a resolver result has, and how to resolve that using the variant types from the union. It is required to implement a resolve_type function in your resolver tree, but where and how to do that isn't obvious, I hope this post helps:

# frozen_string_literal: true

require 'test_helper'

module Schema
  class UnionsFromDefinitionTest < Minitest::Test
    def definition
      <<~EOSCHEMA
        type Wimpel {
          id: ID!
        }
        type Doodad {
          code: ID!
        }
        union Widget = Doodad | Wimpel
        type Query {
          widget: [Widget]!
        }
      EOSCHEMA
    end

    def resolvers
      Sch3ma.default_resolvers.merge(
        {
          'Query' => {
            'widget' => lambda { |_obj, _args, _ctx|
              [OpenStruct.new(id: 'i am wimpel'), OpenStruct.new(code: 'i am doodad')]
            }
          },
          'resolve_type' => lambda { |_type, obj, ctx|
            type_name = if obj.respond_to? :id
                          'Wimpel'
                        elsif obj.respond_to? :code
                          'Doodad'
                        end
            ctx.schema.types[type_name] || raise('boom')
          }
        }
      )
    end

    def schema
      GraphQL::Schema.from_definition(
        definition,
        default_resolve: resolvers
      )
    end

    def test_querying_union_types
      res = schema.execute(<<~EOQUERY)
        query {
          widget {
            ... on Wimpel {
              id
            }
            ... on Doodad {
              code
            }
          }
        }
      EOQUERY
      assert_equal({ 'widget' => [{ 'id' => 'i am wimpel' }, { 'code' => 'i am doodad' }] }, res['data'])
    end
  end
end

In reality our schema lives in a standlone package (Gem) and we have an implementation of the resolver map in our actual application, the schema is used by other teams too. This test-case in the schema repo helps serve as documentation on how this feature is supposed to be used with the SDL rather than the DSL.

AWS Q Developer image

Your AI Code Assistant

Generate and update README files, create data-flow diagrams, and keep your project fully documented. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

Rather than just generating snippets, our agents understand your entire project context, can make decisions, use tools, and carry out tasks autonomously.

Read full post

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay