GraphQL with Ruby On Rails: Queries in Multiple Files

GraphQL with Ruby On Rails: Queries in Multiple Files

<strong>In the last episode, we added RSpecs tests for GraphQL mutations. If you need some more insight about the topic you can check the article here. Today we are going to add specs (again, we will focus only on the happy paths) for GraphQL queries. But there is one thing about the current implementation of the queries that I don’t like. We have everything defined in one file: app/graphql/types/query_type.rb</strong>

In the last episode, we added RSpecs tests for GraphQL mutations. If you need some more insight about the topic you can check the article here. Today we are going to add specs (again, we will focus only on the happy paths) for GraphQL queries. But there is one thing about the current implementation of the queries that I don’t like. We have everything defined in one file: app/graphql/types/query_type.rb

module Types
  class QueryType < Types::BaseObject
    field :authors, [Types::AuthorType], null: false do
      description 'Find all authors'
    end

field :author, Types::AuthorType, null: false do
  description 'Find an author by ID'
  argument :id, ID, required: true
end

field :books, [Types::BookType], null: false do
  description 'Find all books'
end

field :book, Types::BookType, null: false do
  description 'Find a book by ID'
  argument :id, ID, required: true
end

def authors
  Author.all
end

def author(id:)
  Author.find(id)
end

def books
  Book.all
end

def book(id:)
  Book.find(id)
end

end end

You can imagine that this file will grow over time. I want to show you how to divide the logic into many smaller pieces.

The first step of every refactoring

It is hard to do refactoring without proper specs. So we are going to add specs first. But to make the article a little shorter and easier to comprehend, we will embrace the approach we put a great emphasis on in our company. We will do a test-driven development. That means that we will write the final specs. So instead of adding specs to our previous implementation, we will add specs for the future implementation where every query is defined in a separate file.

We would like to have a structure where all queries are inside app/graphql/queries/ folder. Therefore our specs will end up in specs/graphql/queries/ directory.

specs/graphql/queries/author_spec.rb

require 'rails_helper'

module Queries RSpec.describe Author, type: :request do describe '.resolve' do it 'returns author for provided id' do author = create(:author, first_name: 'Lee', last_name: 'Child', date_of_birth: Date.parse('1954-10-29')) book = create(:book, author: author)

    post '/graphql', params: { query: query(id: author.id) }

    json = JSON.parse(response.body)
    data = json['data']['author']

    expect(data).to include(
      'id'          =&gt; be_present,
      'firstName'   =&gt; 'Lee',
      'lastName'    =&gt; 'Child',
      'dateOfBirth' =&gt; '1954-10-29',
      'books'       =&gt; [{ 'id' =&gt; book.id.to_s }]
    )
  end
end

def query(id:)
  &lt;&lt;~GQL
    query {
      author(id: #{id}) {
        id
        firstName
        lastName
        dateOfBirth
        books {
          id
        }
      }
    }
  GQL
end

end end

specs/graphql/queries/book_spec.rb

require 'rails_helper'

module Queries RSpec.describe Authors, type: :request do describe '.resolve' do it 'returns all authors' do author = create(:author, first_name: 'Lee', last_name: 'Child', date_of_birth: Date.parse('1954-10-29')) create(:author, first_name: 'Stephen', last_name: 'King', date_of_birth: Date.parse('1947-09-21')) book = create(:book, author: author)

    post '/graphql', params: { query: query }

    json = JSON.parse(response.body)
    data = json['data']['authors']

    expect(data).to match_array [
      hash_including(
        'id'          =&gt; be_present,
        'firstName'   =&gt; 'Lee',
        'lastName'    =&gt; 'Child',
        'dateOfBirth' =&gt; '1954-10-29',
        'books'       =&gt; [{ 'id' =&gt; book.id.to_s }]
      ),
      hash_including(
        'id'          =&gt; be_present,
        'firstName'   =&gt; 'Stephen',
        'lastName'    =&gt; 'King',
        'dateOfBirth' =&gt; '1947-09-21',
        'books'       =&gt; []
      )
    ]
  end
end

def query
  &lt;&lt;~GQL
    query {
      authors {
        id
        firstName
        lastName
        dateOfBirth
        books {
          id
        }
      }
    }
  GQL
end

end end

specs/graphql/queries/book_spec.rb and

specs/graphql/queries/books_spec.rb are almost the same so I’am not going to paste the code in here. Instead, you can check the files on gist if you like: book_spec.rb and books_spec.rb

As you can see our new specs look really similar to those we wrote for mutations. We have a query method that we use for building GraphQL query and we check if the result of the query meets our expectations.

Dividing queries into separate files

Now when all needed specs are in place we can finally do the refactoring. I am not going to show a proper TDD in this article because it will require those actions over and over again: run spec, see the error, fix the error, run spec again to see another error, fix that error, etc. It will be hard to follow so I’m going to present the final solution and explain the idea behind it.

We want to have a similar structure to what we have in mutations but it is not supported and we need to look for alternatives. The one we usually use for splitting queries is Resolvers.

Disclaimer: it is not always recommended. Please check the documentation for more information.

As I mentioned many times, we want our query_type.rb to work similar to mutation_type.rb it means that a new version of app/graphql/types/query_type.rb should look like this:

module Types
  class QueryType < Types::BaseObject
    field :author, resolver: Queries::Author
    field :authors, resolver: Queries::Authors
    field :book, resolver: Queries::Book
    field :books, resolver: Queries::Books
  end
end

We removed all the logic we had. Instead, we define what query is available and the resolver for that query (a place when we can find it).

If we are going to follow the mutations’ way of doing things we need an additional file: app/graphql/queries/base_query.rb

module Queries
  class BaseQuery < GraphQL::Schema::Resolver
  end
end

It is empty for now, but in the future episodes, we will use it for some cool things.

We can finally see the implementation of our new queries:

app/graphql/queries/author.rb

module Queries
  class Author < Queries::BaseQuery
    description 'Find an author by ID'

argument :id, ID, required: true

type Types::AuthorType, null: false

def resolve(id:)
  ::Author.find(id)
end

end end

app/graphql/queries/authors.rb

module Queries
  class Authors < Queries::BaseQuery
    description 'Find all authors'

type [Types::AuthorType], null: false

def resolve
  ::Author.all
end

end end

app/graphql/queries/book.rb

module Queries
  class Book < Queries::BaseQuery
    description 'Find a book by ID'

argument :id, ID, required: true

type Types::BookType, null: false

def resolve(id:)
  ::Book.find(id)
end

end end

app/graphql/queries/books.rb

module Queries
  class Books < Queries::BaseQuery
    description 'Find all books'

type [Types::BookType], null: false

def resolve
  ::Book.all
end

end end

The code of each query is moved from the query_type.rb without any changes. The magic that makes it possible is the inheritance from Queries::BaseQuery which inherits from GraphQL::Schema::Resolver. That is it.

Summary

At this point we have all the endpoints tested out and we (almost) have a structure that is easy to build on. In the future episodes, we will discuss things like pagination, authentication, authorization and many more.

Originally published by Ireneusz Skrobiś at https://itnext.io

Learn More

☞ The Complete Ruby on Rails Developer Course

☞ Learn to Code with Ruby

☞ Comprehensive Ruby Programming

☞ Dissecting Ruby on Rails 5 - Become a Professional Developer

ruby-on-rails graphql

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Hire Ruby on Rails Developer | Hire RoR Developer

#1 Ruby on Rails development company. Hire Ruby on rails developer or a team to build secure, scalable and complex web solutions with a quick turnaround time.

Explain Ruby on rails MVC

Our Ruby on Rails Training will provide you to learn about Rails and web applications development with realty. Our Ruby on Rails Course also includes live sessions.

"Rewrite your Software," says Creator of Ruby on Rails

David Heinemeier Hansson is the creator of Ruby on Rails, co-founder & CTO of Basecamp, best-selling author, Le Mans class-winning race car driver, family man, frequent podcast guest, and inspirational conference speaker.

How to Set Up a Ruby on Rails GraphQL API

In this tutorial, you will build a GraphQL-powered Ruby on Rails API for taking notes. When you are finished, you will be able to create and view notes from the API using GraphQL.

Pros & Cons you must know before using Ruby on Rails for your startup

Click here, free ruby on rails course videos for you. It shows you best way to complete Ruby certification. OnlineITGuru guides you towards easy Web script