Rails Kitchen

It's a place to write on stuff I learned recently.

Preload Has_many Associations in Graphql-ruby Using Graphql-preload

| Comments

In the previous post about solving N+1 query in graphQL using graphql-batch, we discussed batching queries using the graphql-batch gem. This works great for belongs_to relationships, but it is not useful for preloading has_many associations. For example, if you wanted the comments for each article, it would again produce N+1 queries. Luckily I found a gem graphql-preload that solves this problem. The graphql-preload gem is created based on a gist by @theorygeek. This gem depends on the graphql-batch gem. so we need to keep graphql-batch settings in the schema for graphql-preload work. detailed explanation about graphql batch is given here.

Installation

Add this line to your application’s Gemfile:
Gemfile
1
gem 'graphql-preload'
Add enable_preloading to the schema file to be able to use this functionality.
app/graphql/graphql_ruby_sample_schema.rb
1
2
3
4
5
6
7
GraphqlRubySampleSchema = GraphQL::Schema.define do
  query QueryType
  mutation MutationType

  use GraphQL::Batch
  enable_preloading
end
Now, for the comments field, for example, we could write it like this:
app/graphql/types/article_type.rb
1
2
3
4
5
6
7
ArticleType = GraphQL::ObjectType.define do
  name "Article"

  field :comments, types[CommentType] do
    preload :comments
  end
end
We can now throw a query like this at our API without the server with N+1 query problem for has_many association also.
1
2
3
4
5
6
7
8
9
10
11
12
13
query {
  articles{
    id
    title
    body
    comments{
      comment
      user{
        name
      }
    }
  }
}
Before preload After preload

GraphQL Ruby Mutation With Input Object Type

| Comments

In the previous blog post about GraphQl mutation, we specified each field in input as input_field. This method will do the job but it has some issues.

Consider an example mutation for saving an article. Here we used the input_field method without InputObjectType.
app/graphql/mutations/article_mutations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# encoding: utf-8
module ArticleMutations
  Create = GraphQL::Relay::Mutation.define do
    name 'AddArticle'

    input_field :title, !types.String
    input_field :body, !types.String

    # Define return parameters
    return_field :article, ArticleType
    return_field :errors, types.String

    resolve lambda { |object, inputs, ctx|
      article = Article.new(title: inputs[:title], body: inputs[:body])

      if article.save
        { article: article }
      else
        { errors: article.errors.to_a }
      end
    }
  end
end
1
2
3
4
5
6
7
8
9
10
mutation addArticle{
  addArticle(input: { title: "GraphQL mutation", body: " This article is about graphql mutation using  InputObjectType" })
  {
    article{
      id
      title
      body
    }
  }
}
For saving this we need to instantiate article object by assigning each and every input params like this.
app/graphql/mutations/article_mutations.rb
1
article = Article.new(title: inputs[:title], body: inputs[:body])
This method become ugly if our object contain lots fields. We can define and utilize mutation input params in the better way using InputObjectType. If we can specify article object as input_field instead of each field we can save object like below.
app/graphql/mutations/article_mutations.rb
1
article = Article.new(inputs[:article].to_h)
which is maintanable and easy to read. Lets see how we can achive this.
The first step is to define InputObjectType and declare that input object type as input_field. Here we are going to create InputObjectType in another folder inside our graphql folder for maintainability.
app/graphql/input_objects/article_input_object_type.rb
1
2
3
4
5
ArticleInputObjectType = GraphQL::InputObjectType.define do
  name 'ArticleInput'
  input_field :title, !types.String
  input_field :body, !types.String
end
Since we created new folder for input_objects, we have to tell Rails to autoload paths. place below code in application.rb to autoload it.
config/application.rb
1
config.autoload_paths << Rails.root.join('app/graphql/input_objects')
Now we can declare ArticleInputObjectType as input_field inside our mutation and use declared input_field inside our mutation resolver to save the article. So final mutation definition will look like this.
app/graphql/mutations/article_mutations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# encoding: utf-8
module ArticleMutations
  Create = GraphQL::Relay::Mutation.define do
    name 'AddArticle'

    # Define input parameters
    input_field :article, !ArticleInputObjectType

    # Define return parameters
    return_field :article, ArticleType
    return_field :errors, types.String

    resolve lambda { |object, inputs, ctx|
      article = Article.new(inputs[:article].to_h)

      if article.save
        { article: article }
      else
        { errors: article.errors.to_a }
      end
    }
  end
end
That’s it, we are done, now we can save new article using addArticle mutation.
1
2
3
4
5
6
7
8
9
10
mutation addArticle{
  addArticle(input: { article: { title: "GraphQL mutation", body: " This article is about graphql mutation using  InputObjectType" } })
  {
    article{
      id
      title
      body
    }
  }
}
If we want to map camel case arguments into DB fields which are in snake case, we can use as keyword.
app/graphql/input_objects/comment_input_object_type.rb
1
2
3
4
5
6
CommentInputObjectType = GraphQL::InputObjectType.define do
  name 'CommentInput'
  input_field :articleId, !types.ID, as: :article_id
  input_field :userId, !types.ID, as: :user_id
  input_field :comment, !types.String
end
This will convert our input params in snake case so that we can assign this to the object without manually converting into snake case.
1
2
inputs[:comment].to_h
=> {"article_id"=>"1", "user_id"=>"1", "comment"=>"New comment"}
You can see sample code here.

Deccan RubyConf 2017 - First Experience as a Speaker

| Comments

Deccan RubyConference is a Single-Track Conference happening at Pune every year. Deccan RubyConf 2017 took place 12th Aug. 2017 at Sheraton Grand Pune. The conference covered areas like Ruby Language, Framework, and Tools by well-known speakers and also few first-time speakers from Ruby community.

I got a chance to attend This year edition as speaker. My topic was Give REST a rest, use GraphQL for your next Ruby API. In my 25 minutes session, I talked about why should we use GraphQL and also about its implementation in Ruby. After conference session few people from different companies approached me and asked about GraphQL which was really a confidence booster for me as a speaker. Few people shared the issues they faced in GraphQL which given me new ideas and thought process.

The conference started with the keynote by Tim Riley. Tim is from Australian and partner at Australian design agency Icelab. He talked about next generation Ruby web apps with dry-rb. Next talk was about leveraging the hidden powers of a database to make Rails, the swiss army knife of web development, manipulate and present data faster by Harisankar, founder of Red Panthers. Aboobacker MK from Red Panthers talked about Garbage collector in Ruby and some practical tips to improve Ruby performance.

After my session, Shaunak Pagnis from Amura talked about the Active record and beyond. After Lunch Michael Kohl from Austria, currently, serves as CTO of Lockstap Labs talked about writing maintainable Rails applications. The sixth session was a panel discussion with the panels including Ajey Gore - Group CTO of Go-Jek, Vipul - Director BigBinary, Tim Riley, Michael Kohl, Gautam Rege -Director Josh Software discussed their experience of building a start-up and hiring strategies.

Followed by Panel Discussion, there were 9 flash talks which included 5 speakers from Kerala, talked about different ruby topics from beginner level to advanced level. After flash talks, Douglas Vaz talked about HTTP/2 World. Conference session ended with Keynote by Ajey Gore. He talked about testing principles and why testing is important.

After sessions, there were Open hours which gave opportunity meet and interact with different kind of people. For me, it was a great learning experience both listening to people share their contributions and ideas which really inspired me to code better and learn better.

All the photos from the conference is available here.

GraphQl Security Issues and Solutions

| Comments

One of the main highlights of GraphQl is declarative fetching, the client can define the shape of the data he needed and GraphQL server will provide data in the same shape. But an evil client can exploit this advantage to make deeper nesting and complex queries and can do DDoS attack.

Example for a deeper nesting query:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
query {
  articles{
    title
    body
    comments{
      comment
      user{
        comments{
          user{
            comments
            {
              comment
              user{
                comments{
                  comment
                  user{
                    comments{
                      comment
                      user{
                        name
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
GraphQL provide three confirations to avoid this issue.

Timeout: We can avoid queries utilizing server more than specified time by setting a timeout. If a query takes more time for execute, then after time out GraphQL will return the result of query up to that time and simply reject nonexcecuted part of the query.
app/graphql/graphql_ruby_sample_schema.rb
1
2
3
4
5
6
7
8
GraphqlRubySampleSchema = GraphQL::Schema.define do
  query QueryType
  mutation MutationType
end

GraphqlRubySampleSchema.middleware << GraphQL::Schema::TimeoutMiddleware.new(max_seconds: 10) do |err, query|
  Rails.logger.info("GraphQL Timeout: #{query.query_string}")
end
Maximum Depth: Another solution for prevent deeply-nested queries is setting max_deplth. If our query depth is greater than max_depth we set, the server will simply reject full query and return an error message.
app/graphql/graphql_ruby_sample_schema.rb
1
2
3
4
5
6
GraphqlRubySampleSchema = GraphQL::Schema.define do
  query QueryType
  mutation MutationType

  max_depth 12
end
Query Complexity: We can prevent complex queries by setting max_complexity in GraphQL schema. Each field in our type system has a complexity number so if our query complexity exceeds max_complexity, the server will reject query just like in the case of max_depth.
app/graphql/graphql_ruby_sample_schema.rb
1
2
3
4
5
6
7
GraphqlRubySampleSchema = GraphQL::Schema.define do
  query QueryType
  mutation MutationType

  max_depth 12
  max_complexity 100
end

By default complexity of field is 1, but we can configure constant complexity for fields in type system and also can set complexity dynamically by passing conditions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Constant complexity:
field :comments, types[CommentType], complexity: 10

# Dynamic complexity:
field :top_comments, types[CommentType] do
  argument :limit, types.Int, default_value: 5
  complexity ->(ctx, args, child_complexity) {
    if ctx[:current_user].admin?
      # no limit for admin users
      0
    else
      # `child_complexity` is the value for selections
      # which were made on the items of this list.
      #
      # We don't know how many items will be fetched because
      # we haven't run the query yet, but we can estimate by
      # using the `limit` argument which we defined above.
      args[:limit] * child_complexity
    end
  }
end
You can see sample code here.

DRY GraphQL Definitions Using Interfaces

| Comments

We can make GraphQL Types and mutations DRY using interfaces. An Interface is an abstract type that contains a collection of types which implement some of the same fields. We can avoid specifying the same set of fields in different GraphQL Types and mutations by defining an interface and using in sharing Types and mutations.

Interfaces can have fields, defined with a field, just like a GraphQL object type. Objects which implement this field inherit field definitions from the interface. An object type can override the inherited definition by redefining that field.

For example, active record time stamps are common fields in Rails models. So we can avoid declaring these fields in all object types by declaring an interface ActiveRecordTimestamp with these fields and using it our object types.

We can define ActiveRecordTimestamp like this.
app/graphql/interfaces/active_record_timestamp.rb
1
2
3
4
5
ActiveRecordTimestamp = GraphQL::InterfaceType.define do
  name 'ActiveRecordTimestamp'
  field :createdAt, types.String, property: :created_at
  field :updatedAt, types.String, property: :updated_at
end
Since we created a new folder for interfaces, we have to tell Rails to autoload paths. Place below code in application.rb to autoload it.
config/application.rb
1
config.autoload_paths << Rails.root.join('app/graphql/interfaces')
Now we can add defined interfaces in our object Type using interfaces keyword.
app/graphql/types/article_type.rb
1
2
3
4
5
6
7
8
9
ArticleType = GraphQL::ObjectType.define do
  name "Article"
  interfaces [ActiveRecordTimestamp]

  field :id, types.Int
  field :title, types.String
  field :body, types.String
  field :comments, types[CommentType]
end
Example for including multiple interfaces in Ruby object type.
app/graphql/types/comment_type.rb
1
2
3
4
5
6
7
8
9
CommentType = GraphQL::ObjectType.define do
  name "Comment"

  # multiple interfaces included using comma.
  interfaces [ActiveRecordTimestamp, GraphQL::Relay::Node.interface]
  field :id, types.Int
  field :comment, types.String
  field :user, UserType
end
Now, this active record time stamp will be available in both above-mentioned object types.

We can use return_interfaces to define and reuse return types in different mutation definitions. The result of the resolve block will be passed to the field definitions in the interfaces, and both interface-specific and mutation-specific fields will be available to clients.

For example, we can define a interface which will define notification of a mutation.
app/graphql/interfaces/mutation_result.rb
1
2
3
4
5
6
MutationResult = GraphQL::InterfaceType.define do
  name "MutationResult"
  field :success, !types.Boolean
  field :notice, types.String
  field :errors, types[ValidationError]
end
app/graphql/mutations/article_mutations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# encoding: utf-8
CreateArticle = GraphQL::Relay::Mutation.define do
  # ...
  return_field :title, types.String
  return_field :body, types.String
  return_interfaces [MutationResult],

  # clientMutationId will also be available automatically
  resolve ->(obj, input, ctx) {
    article, notice = Article.create_with_input(...)
    {
      success: article.persisted?
      notice: notice
      title: article.title
      errors: article.errors
    }
  }
end

GraphQL Ruby Error Handling

| Comments

GraphQL endpoints, we can expose errors as part of our schema. We should check errors fields to see if any errors in result data. For example if we query a field which is not existing in type system, we will get a error response. This type of errors is not supposed to be displayed to end users. It helps with debugging, error tracking etc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "errors": [
    {
      "message": "Field 'user' doesn't exist on type 'Article'",
      "locations": [
        {
          "line": 5,
          "column": 5
        }
      ],
      "fields": [
        "query",
        "article",
        "user"
      ]
    }
  ]
}
If a field’s resolve function returns an ExecutionError, the error will be inserted into the response’s errors key and the field will resolve to nil. It is often required to perform additional validation of the input parameters passed to GraphQL mutations, and provide user-friendly error messages in case validation fails or mutation cannot be completed successfully.
For example, we could add errors to ArticleType:
app/graphql/types/article_type.rb
1
2
3
4
5
6
7
8
ArticleType = GraphQL::ObjectType.define do
  name "Article"
  field :title, types.String
  # ...
  field :errors, types[types.String] do
    resolve -> (obj, args, ctx) { obj.errors.full_messages }
  end
end
Then, when clients create a article, they should check the errors field to see if it was successful:
1
2
3
4
5
6
7
mutation {
  createArticle(article: {title: "GraphQL is Nice"}) {
    id
    title
    errors # in case the save failed
  }
}
If errors are present (and id is null), the client knows that the operation was unsuccessful, and they can discover reason. If some part of our resolve function would raise an error, we can rescue it and add to the errors key by returning a GraphQL:: ExecutionError
app/graphql/mutations/article_mutations.rb
1
2
3
4
5
6
7
8
9
10
11
resolve -> (obj, args, ctx) {
  article_params = args["article"].to_h
  begin
    article = Article.create!(post_params)
    # on success, return the article:
    article
  rescue ActiveRecord::RecordInvalid => err
    # on error, return an error:
    GraphQL::ExecutionError.new("Invalid input for Article: #{article.errors.full_messages.join(", ")}")
  end
}
If we don’t want to begin … rescue … end in every field, we can wrap resolve functions in error handling.
For example, we could make an object that wraps another resolver:
app/graphql/resolvers/rescue_form.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Wrap field resolver `resolve_func` with a handler for `error_superclass`.
# `RescueFrom` instances are valid field resolvers too.
class RescueFrom
  def initialize(error_superclass, resolve_func)
    @error_superclass = error_superclass
    @resolve_func = resolve_func
  end

  def call(obj, args, ctx)
    @resolve_func.call(obj, args, ctx)
  rescue @error_superclass => err
    # Your error handling logic here:
    # - return an instance of `GraphQL::ExecutionError`
    # - or, return nil:
    nil
  end
end
apply it to fields on an opt-in basis:
app/graphql/mutations/article_mutations.rb
1
2
3
4
field :create_article, ArticleType do
  # Wrap the resolve function with `RescueFrom.new(err_class, ...)`
  resolve RescueFrom.new(ActiveRecord::RecordInvalid, -> (obj, args, ctx) { ... })
end

Reference: GraphQL Ruby website

GraphQL -Mutation Query Implementation - Ruby on Rails

| Comments

Mutation is a special type of query used to change data in the database like Creating, Editing or Deleting Records from a table or Store. These are the equivalent to the POST, PUT, PATCH and DELETE in HTTP/REST speak. Defining mutations is very similar to defining queries. The only difference is how you implement the logic inside the mutation. In mutation, we can control and specify the output data that API need to return after mutation procedure.

In this article, I am Adding a mutation query to add comments to an article which we discussed in previous example.

To add mutations to your GraphQL schema, first we need to define a mutation type in mutations folder
app/graphql/mutations/mutation_type.rb
1
2
3
MutationType = GraphQL::ObjectType.define do
  name "Mutation"
end
Now we need to pass it into the schema
app/graphql/graphql_ruby_sample_schema.rb
1
2
3
4
GraphqlRubySampleSchema = GraphQL::Schema.define do
  query QueryType
  mutation MutationType
end
Like QueryType, MutationType is a root of the schema. Members of MutationType are mutation fields. For GraphQL in general, mutation fields are identical to query fields except that they have side-effects (which mutate application state, eg, update the database).

Since we created new folder for mutations, we have to tell Rails to autoload paths. Put below code in application.rb to autoload it.
config/application.rb
1
config.autoload_paths << Rails.root.join('app/graphql/mutations')

Now we need to define specific mutation query. Following are the process to define a mutation
- give operation a name
- declare its inputs
- declare its outputs
- declare the mutation procedure in resolve block.
resolve should return a hash with a key for each of the return_fields

In out example, we need to define CommentMutations in mutations folder.
app/graphql/mutations/comment_mutations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# encoding: utf-8
module CommentMutations
  Create = GraphQL::Relay::Mutation.define do
    name "AddComment"

    # Define input parameters
    input_field :articleId, !types.ID
    input_field :userId, !types.ID
    input_field :comment, !types.String

    # Define return parameters
    return_field :article, ArticleType
    return_field :errors, types.String


    resolve ->(object, inputs, ctx) {
      article = Article.find_by_id(inputs[:articleId])
      return { errors: 'Article not found' } if article.nil?

      comments = article.comments
      new_comment = comments.build(user_id: inputs[:userId], comment: inputs[:comment])
      if new_comment.save
        { article: article }
      else
        { errors: new_comment.errors.to_a }
      end
    }
  end
end
Here input_field specify the input params we can pass in the query. In return_field, we can specify the fields returning after the update. Inside resolve block, we define the business logic. and resolve should return a hash with a key for each of the return_fields.

After defining this, we need to add the mutation’s derived field to the mutation type.
app/graphql/mutations/mutation_type.rb
1
2
3
4
5
MutationType = GraphQL::ObjectType.define do
  name "Mutation"
  # Add the mutation's derived field to the mutation type
  field :addComment, field: CommentMutations::Create.field
end
Now we can try this mutation in GraphiQL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
mutation addComment{
  addComment(input: { comment: "New comment", articleId: 1, userId: 1})
  {
    article{
      id
      comments{
        comment
        user{
          name
        }
      }
    }
  }
}
Here’s a possible JSON response for that query:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
  "data": {
    "addComment": {
      "article": {
        "id": 1,
        "comments": [
          {
            "comment": "Good article",
            "user": {
              "name": "Shaiju E"
            }
          },
          {
            "comment": "Keep going",
            "user": {
              "name": "Shaiju E"
            }
          },
          {
            "comment": "Another Comment",
            "user": {
              "name": "David"
            }
          },
          {
            "comment": "New Comment",
            "user": {
              "name": "Shaiju E"
            }
          },
          {
            "comment": "Another Comment from User 2",
            "user": {
              "name": "David"
            }
          },
          {
            "comment": "Another Comment from User 1",
            "user": {
              "name": "Shaiju E"
            }
          },
          {
            "comment": "TEST",
            "user": {
              "name": "Shaiju E"
            }
          },
          {
            "comment": "New comment",
            "user": {
              "name": "Shaiju E"
            }
          }
        ]
      }
    }
  }
}
We can call the same query by passing inputs using variables
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mutation addComment($comments: AddCommentInput!){
  addComment(input: $comments){
    article{
      id
      comments{
        comment
        user{
          name
        }
      }
    }
  }
}

Query Variabbles

{
  "comments": {
    "comment": "New comment1",
    "articleId": 1,
    "userId": 1
  }
}
$comments: AddCommentInput! will configure the variable $comments to take values from query variables section. input: $comments will pass $comments as input to mutation query.

Lets write another example for updation mutation. If we want to update a comment, we need to write UpdateComment mutation in comment_mutations.rb
app/graphql/mutations/comment_mutations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# encoding: utf-8
module CommentMutations
  Create = GraphQL::Relay::Mutation.define do
    name "AddComment"

    # Define input parameters
    input_field :articleId, !types.ID
    input_field :userId, !types.ID
    input_field :comment, !types.String

    # Define return parameters
    return_field :article, ArticleType
    return_field :errors, types.String


    resolve ->(object, inputs, ctx) {
      article = Article.find_by_id(inputs[:articleId])
      return { errors: 'Article not found' } if article.nil?

      comments = article.comments
      new_comment = comments.build(user_id: inputs[:userId], comment: inputs[:comment])
      if new_comment.save
        { article: article }
      else
        { errors: new_comment.errors.to_a }
      end
    }
  end

  Update = GraphQL::Relay::Mutation.define do
    name "UpdateComment"

    # Define input parameters
    input_field :id, !types.ID
    input_field :comment, types.ID
    input_field :userId, types.ID
    input_field :articleId, types.ID

    # Define return parameters
    return_field :comment, CommentType
    return_field :errors, types.String


    resolve ->(object, inputs, ctx) {
      comment = Comment.find_by_id(inputs[:id])
      return { errors: 'Comment not found' } if comment.nil?

      valid_inputs = ActiveSupport::HashWithIndifferentAccess.new(inputs.instance_variable_get(:@original_values).select { |k, _| comment.respond_to? "#{k}=".underscore }).except(:id)
      if comment.update_attributes(valid_inputs)
        { comment: comment }
      else
        { errors: comment.errors.to_a }
      end
    }
  end
end
Main defference here is, we need to create valid_inputs. This will allow us mass assignment with update attributes with valied fields which we passed.

After defining this, we need to add the mutation’s derived field to the mutation type.
app/graphql/mutations/mutation_type.rb
1
2
3
4
5
6
MutationType = GraphQL::ObjectType.define do
  name "Mutation"
  # Add the mutation's derived field to the mutation type
  field :addComment, field: CommentMutations::Create.field
  field :updateComment, field: CommentMutations::Update.field
end
Mutation for delete a comment and return post and deleted comment ID
app/graphql/mutations/comment_mutations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# encoding: utf-8
module CommentMutations
  Destroy = GraphQL::Relay::Mutation.define do
    name 'DestroyComment'
    description 'Delete a comment and return post and deleted comment ID'

    # Define input parameters
    input_field :id, !types.ID

    # Define return parameters
    return_field :deletedId, !types.ID
    return_field :article, ArticleType
    return_field :errors, types.String

    resolve ->(_obj, inputs, ctx) {
      comment = Comment.find_by_id(inputs[:id])
      return { errors: 'Comment not found' } if comment.nil?

      article = comment.article
      comment.destroy

      { article: article.reload, deletedId: inputs[:id] }
    }
  end

  # Other mutations defined here....
end
app/graphql/mutations/mutation_type.rb
1
2
3
4
5
6
7
MutationType = GraphQL::ObjectType.define do
  name "Mutation"
  # Add the mutation's derived field to the mutation type
  field :addComment, field: CommentMutations::Create.field
  field :updateComment, field: CommentMutations::Update.field
  field :destroyComment, field: CommentMutations::Destroy.field
end
Now we can try this mutation in GraphiQL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mutation destroyComment($comment: DestroyCommentInput!){
  destroyComment(input: $comment){
    errors
    article{
      id
      comments{
        id
        comment
      }
    }
  }
}

Query Variabbles

{
  "comment": {
    "id": 3
  }
}
You can see sample code here.

GraphiQL IDE Alternatives for Development/Testing/Exploring GraphQL Servers

| Comments

GraphiQL is a graphical interactive in-browser GraphQL IDE. This is a great tool for testing GraphQL endpoints and also for Exploring documentation about GraphQL Servers. There are some limitations for default GraphiQL tool, It does not provide any option for saving queries like we have in postman and other API clients. It only saves the latest query in local storage. Another issue with GraphiQL is, it not providing any option for pass headers into GraphQL queries. It is very critical for the testing of application which needs to pass auth-token or some other headers. In this post Iam exploring few alternativs.

GraphQL IDE

GraphQL IDE is n extensive IDE for exploring GraphQL API’s. It allows manage projects, import/export, store queries, toggle query history, passing custom headers and setting environment variables for dynamic headers. Currently, it is only available for MacOS. Window / Linux version of this application is under development but can build the binary. If you have Homebrew installed on OSX, you can install it by:

1
brew cask install graphql-ide
Execute queries Custom headers Environment variables for dynamic headers Manage projects
GraphiQL.app

GraphiQL.app is light, Electron-based wrapper around GraphiQL. This Provides a tabbed interface for editing and testing GraphQL queries/mutations with GraphiQL. It is only available for MacOS. If you have Homebrew installed on OSX, you can install it by:

1
brew cask install graphiql

GraphiQL Feen(Chrome Extension)

GraphiQL Feen is a Chrome Extension that allows you to explore and test GraphQL endpoints. It utilizes the Popular GraphiQL component. It

Features are:
Save/Load Queries and variables to build a library of useful queries.
* Save/Select Server Definitions so you can have different settings for different servers.
* GET/POST/Multi-part GraphQL requests supported!
* Authorization defined so cookies forwarded to the domain
* Define Headers that will be sent with the request, Headers can even override existing Request headers:
* Define the Origin: header for CORS requests to allow your server to process correctly.
* Define CSRF token headers
* Override all Cookies so you can pass authentication information with your requests.
* EXPORT your entire application state to a JSON file
* IMPORT saved state so you can restore your state

Solving N+1 Query in GraphQL Using Graphql-batch

| Comments

One of the most important pain points in GraphQL is the problem thats commonly referred to as N+1 SQL queries. GraphQL query fields are designed to be stand-alone functions, and resolving those fields with data from a database might result in a new database request per resolved field.

For a simple RESTful API endpoint logic, it’s easy to analyze, detect, and solve N+1 issues by enhancing the constructed SQL queries. For GraphQL dynamically resolved fields, it’s not that simple.

For example, Consider the GraphQL query to fetch article, its comments and commented user which I mentioned in previous post.
1
2
3
4
5
6
7
8
9
10
11
12
query {
  acticle(id: 1){
    title
    comments{
      comment
      user {
        id
        name
      }
    }
  }
}
Output Response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{
  "data": {
    "acticle": {
      "title": "A GraphQL Server",
      "comments": [
        {
          "comment": "Good article",
          "user": {
            "id": 1,
            "name": "Shaiju E"
          }
        },
        {
          "comment": "Keep going",
          "user": {
            "id": 1,
            "name": "Shaiju E"
          }
        },
        {
          "comment": "Another Comment",
          "user": {
            "id": 2,
            "name": "David"
          }
        },
        {
          "comment": "New Comment",
          "user": {
            "id": 1,
            "name": "Shaiju E"
          }
        },
        {
          "comment": "Another Comment from User 2",
          "user": {
            "id": 2,
            "name": "David"
          }
        },
        {
          "comment": "Another Comment from User 1",
          "user": {
            "id": 1,
            "name": "Shaiju E"
          }
        },
        {
          "comment": "TEST",
          "user": {
            "id": 1,
            "name": "Shaiju E"
          }
        }
      ]
    }
  }
}
This will fire user query in for each comment. Total 7 instead of 1 for fetch 2 user. In Rest API we can solve this issue by eager loading users while fetching comments. But GraphQL query fields are designed to be stand-alone functions, and not aware of other functions.

Facebook introduced DataLoader to solve this problem in Javascript projects. Shopify created GraphQL::Batch to solve this N+1 problem in ruby.
GraphQL::Batch Provides an executor for the graphql gem which allows queries to be batched. This is a flexible toolkit for lazy resolution with GraphQL.

Installation
1
gem 'graphql-batch'
Now we need to define a custom loader, which is initialized with arguments that are used for grouping and a perform method for performing the batch load.
app/graphql/record_loader.rb
1
2
3
4
5
6
7
8
9
10
11
require 'graphql/batch'
class RecordLoader < GraphQL::Batch::Loader
  def initialize(model)
    @model = model
  end

  def perform(ids)
    @model.where(id: ids).each { |record| fulfill(record.id, record) }
    ids.each { |id| fulfill(id, nil) unless fulfilled?(id) }
  end
end
Now we need to GraphQL::Batch as a plugin in your schema
app/graphql/graphql_ruby_sample_schema.rb
1
2
3
4
5
6
GraphqlRubySampleSchema = GraphQL::Schema.define do
  query Types::QueryType
  mutation Mutations::MutationType

  use GraphQL::Batch
end
In our comments api example, we need to use above initialized RecordLoader for lazily execute User query
app/graphql/types/comment_type.rb
1
2
3
4
5
6
7
8
9
10
11
12
module Types
  CommentType = GraphQL::ObjectType.define do
    name "Comment"
    field :id, types.Int
    field :comment, types.String
    field :user, -> { UserType } do
      resolve -> (obj, args, ctx) {
        RecordLoader.for(User).load(obj.user_id)
      }
    end
  end
end
Here, resolve -> (obj, args, ctx) {RecordLoader.for(User).load(obj.user_id) } will make user fetching lazy loading there by solve N+1 query problem. Before: After

More information about GraphQL::Batch is available in Gem Documentation
You can see sample code here.

GraphQL Server - Nested Query Implementation Example in Ruby on Rails

| Comments

One of the main advantages of GraphQL is, we can create queries with a different combination of datasets which will avoid multiple requests to fetch required data, thereby eliminating overheads. In this post, I am discussing how we can create nested datasets in GraphQL.

In the previous post, I mentioned about Article model and query to fetch an article. Here I’m going to fetch comments under the article. Assuming we have comments and user model with corresponding associations.

Now we need to add CommentType.
app/graphql/types/comment_type.rb
1
2
3
4
5
6
CommentType = GraphQL::ObjectType.define do
  name "Comment"
  field :id, types.Int
  field :comment, types.String
  field :user, UserType
end
Here we are exposing commented user which is UserType. So we need to define that type also in user_type.rb
app/graphql/types/user_type.rb
1
2
3
4
5
6
UserType = GraphQL::ObjectType.define do
  name "User"
  field :id, types.Int
  field :name, types.String
  field :email, types.String
end
Now we need to expose comments inside article model
app/graphql/types/article_type.rb
1
2
3
4
5
6
7
ArticleType = GraphQL::ObjectType.define do
  name "Article"
  field :id, types.Int
  field :title, types.String
  field :body, types.String
  field :comments, types[CommentType]
end

Here comments are an array of objects so we need to specify CommentType using types keyword. We can see in comment_type.rb we are not specifying types for UserType, as it is returning a single object. Since we defined association in, This will fetch all comments of the article by executing article.comments.

Put below code in application.rb to autoload graphql and types folder like so:
config/application.rb
1
2
config.autoload_paths << Rails.root.join('app/graphql')
config.autoload_paths << Rails.root.join('app/graphql/types')
Here’s an example of a GraphQL query that a client can use to ask a server about the title of the article, corresponding comments and commented user:
1
2
3
4
5
6
7
8
9
10
11
12
query {
  article(id: 1){
    title
    comments{
      comment
      user {
        id
        name
      }
    }
  }
}
Here’s a possible JSON response for that query:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "data": {
    "article": {
      "title": "A GraphQL Server",
      "comments": [
        {
          "comment": "Good article",
          "user": {
            "id": 1,
            "name": "Shaiju E"
          }
        },
        {
          "comment": "Keep going",
          "user": {
            "id": 1,
            "name": "Shaiju E"
          }
        }
      ]
    }
  }
}
You can see sample code here.