Rails Kitchen

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

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.

A GraphQL Server Implementation - Ruby on Rails

| Comments

GraphQL is a query language for APIs developed by the Facebook team. This language gives clients the power to ask for exactly what they need and nothing more makes it easier to evolve APIs over time and enables powerful developer tools. Facebook open sourced it in 2015.

video courtesy: graphql.org

A GraphQL request can be either a query (read operation) or a mutation (write operation). For both cases, the request is a simple string that a GraphQL service can interpret, execute, and resolve with data in a specified format.

Main features of GraphQL are:

Retrieve only the data your client needs in a single request - No over-fetching:

Send a GraphQL query to your API with the fields you want to fetch and get only that set of fields in return. Apps using GraphQL are fast and stable because they control the data they get, not the server.

Get many resources in a single round-trip:

We can create queries with a different combination of datasets which will avoid multiple requests to fetch required data, thereby eliminating overheads.

Describe what’s possible with a type system:

GraphQL APIs have a single endpoint and it organizes API with requesting types and fields.

Evolve your API without versions - maintainability:

Add new fields and types to your GraphQL API without impacting existing queries. Aging fields can be deprecated and hidden from tools. By using a single evolving version, GraphQL APIs give apps continuous access to new features and encourage cleaner, more maintainable server code.

GraphQL Queries:

Here’s an example of a GraphQL query that a client can use to ask a server about the name and email of user #1:

1
2
3
4
5
6
7
{
  user(id: 1) {
    firstName,
    lastName,
    email
  }
}
Here’s a possible JSON response for that query:
1
2
3
4
5
6
7
8
9
{
  "data": {
    "user": {
      "firstName": "Shaiju",
      "lastName": "E",
      "email": "eshaiju@gmail.com"
     }
   }
}
GraphQL server implementation in Ruby On Rails Application

Here we are assuming, we have an existing ROR application with model Article and fields title and body.

You can install graphql from RubyGems by adding to your application’s Gemfile:
Gemfile
1
2
3
# Gemfile
gem "graphql"
gem 'graphiql-rails', group: :development
Then, run bundle install.

Now you can get started with a few GraphQL generators:
1
rails g graphql:install
This will:
  • Set up a folder structure in app/graphql/
  • Add schema definition
  • Add a query type definition
  • Add a route and controller for executing queries
  • After installing you can see your new schema by:
    1
    2
    3
    
    bundle install
    rails server
    open localhost:3000/graphiql
    After this, you can build a GraphQL server by hand or GraphQL generators

    Here I am building by hand.

    Define some types: Add article_type.rb in ‘types’ folder which will define ArticleType
    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
    
    Now we need to build schema, which is what we use to query
    app/graphql/types/query_type.rb
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    QueryType = GraphQL::ObjectType.define do
      name "Query"
      description "The query root of this schema"
    
      field :article do
        type ArticleType
        argument :id, !types.ID
        description "Find a Article by ID"
        resolve ->(obj, args, ctx) { Article.find_by_id(args["id"]) }
      end
    end
    
    This will query article by accepting ID as argument Then, build a schema with QueryType as the query entry point: Add following in graphql_ruby_sample_schema.rb
    app/graphql/graphql_ruby_sample_schema.rb
    1
    2
    3
    4
    
    # encoding: utf-8
    GraphqlRubySampleSchema = GraphQL::Schema.define do
      query QueryType
    end
    
    Since, we created new folders we have to tell Rails to autoload paths. 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')
    
    This schema is ready to serve GraphQL queries!. play around this query in GraphiQL.

    Here’s an example of a GraphQL query that a client can use to ask a server about the title of article #1:
    1
    2
    3
    4
    5
    
    query {
      article(id: 1){
        title
      }
    }
    
    Here’s a possible JSON response for that query:
    1
    2
    3
    4
    5
    6
    7
    
    {
      "data": {
        "article": {
          "title": "A GraphQL Server"
        }
      }
    }
    
    You can execute queries from a query string:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    query_string = "
    {
      article(id: 1) {
        id
        title
      }
    }"
    result_hash = GraphqlRubySampleSchema.execute(query_string)
    
    output:
    # {
    #   "data" => {
    #     "article" => {
    #        "id" => 1,
    #        "title" => "A GraphQL Server"
    #     }
    #   }
    # }
    
    You can see sample code here.

Delayed_job_web - a Resque Inspired Interface for Delayed_job

| Comments

delayed_job_web is a gem which will provide the Resque inspired interface for delayed_job. This gem will provide a nice interface for monitor all activities in delayed jobs instead of searching it in the database directly.
Some features:
  • Easily view jobs enqueued, working, pending, and failed.
  • Queue any single job. or all pending jobs, to run immediately.
  • Remove a failed job, or easily remove all failed jobs.
  • Watch delayed_job operation with live ajax polling.
  • Filter delayed_jobs by queue name.
Quick Start For Rails 5 Applications:

Add the dependency to your Gemfile and bundle it.
1
2
3
4
5
6
gem 'delayed_job_web'

# For dependency resolution of 'delayed_job_web' gem
# More info - https://github.com/ejschmitt/delayed_job_web/issues/84
gem 'rack-protection', github: 'sinatra/sinatra'
gem 'sinatra', github: 'sinatra/sinatra'

Add the following route to your application for accessing the interface, and retrying failed jobs.
1
2
3
get '/delayed_job' => DelayedJobWeb, :anchor => false
put '/delayed_job' => DelayedJobWeb, :anchor => false
post '/delayed_job' => DelayedJobWeb, :anchor => false
You can authenticate web interface by adding something like this,
1
2
3
4
5
authenticate :user, ->(u) { u.super_admin? } do
  get '/delayed_job' => DelayedJobWeb, :anchor => false
  put '/delayed_job' => DelayedJobWeb, :anchor => false
  post '/delayed_job' => DelayedJobWeb, :anchor => false
end
now web interface will be available only for super admin login.