はじめに
本記事は、社内でGraphQL入門勉強会を実施した際の内容です。
本記事での対象は、
- graphql-ruby/graphiql-railsの設定
- Query(データ取得)
- Mutation(データ更新)
になります。
内部的な仕組みなど、細かい内容は出てきません。
とりあえず動かして雰囲気を掴むことを目的としています。
環境
| バージョン | |
|---|---|
| Ruby | 2.6.0 | 
| Rails | 5.2.2 | 
| graphql-ruby | 1.9.3 | 
| graphiql-rails | 1.6.0 | 
GraphQLとは
GraphQLは、2012年にFacebookによって開発されたデータクエリ言語および仕様です。
RESTベースのアーキテクチャに代わるものを提供します。
GraphQLはGitHubのAPI(GraphQL API v4)でも採用されています。
GraphQL API v4 (https://developer.github.com/v4/)
GraphQLでは、クエリ言語を使用してデータの取得や更新を行います。
- query (データ取得系)
- mutation (データ更新系)
- subscription (イベント通知) ※本記事では対象外
Preparation
graphql-ruby導入前のサンプルプロジェクトを用意しています。
本記事では、このサンプルプロジェクトにgraphql-rubyを導入していきます。
git clone git@github.com:dkawabata/graphql-ruby-sample.git
cd graphql-ruby-sample
bundle install --path vendor/bundle
bundle exec rails db:migrate
bundle exec rails db:seed
サンプルプロジェクトは、usersテーブル/postsテーブル/commentsテーブルの3テーブルで構成しています。
Setup
GraphQL Rubyのインストール
Gemfileを編集します。
+ gem 'graphql'
group :development do
+  gem 'graphiql-rails'
end
graphiqlというブラウザでクエリを実行して、GraphQLの動作確認ができるツールのRails版がgraphiql-railsです。
GraphQLでの開発に必須ではありませんが、今回はgraphiqlで動作確認するため、追加しています。
Gemfileを編集したら、インストールします。
bundle install
bundle exec rails g graphql:install
APIモードでは無い場合、graphiql-railsの設定(Gemfileへの追加や、以降で行うroutes.rbの編集)は不要です。
rails g graphql:installのタイミングで自動的に設定されますが、APIモードの場合はSkipされるようです。
Graphiqlの設定
routes.rbの編集
if Rails.env.development?
  mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql'
end
application.rbの編集
Rails APIモードでgraphiqlを利用するために、application.rbのrequire "sprockets/railtie"のコメントアウトを外します。
graphiql-railsのREADMEにも記載されています。
If you're using Rails 5 in "API mode", you'll also need to add require "sprockets/railtie" to your application.rb.
- # require "sprockets/railtie"
+ require "sprockets/railtie"
サンプルクエリの実行
ここまでで一通りの準備は完了です。
rails sでサーバを起動して、http://localhost:3000/graphiqlにアクセスしてみましょう。
graphiqlの画面が表示されます。
左側にクエリを入力して実行すると、右側に結果が表示されます。
下図のように、testFieldというクエリを実行して、"Hello World!"が表示されれば成功です 
追記
Sprockets 4を使うと、Expected to find a manifest file in app/assets/config/manifest.js (Sprockets::Railtie::ManifestNeededError)というエラーが発生します。
app/assets/config/manifest.jsを作成して、下記を記載すると、とりあえずエラーは回避できます。
//= link graphiql/rails/application.css
//= link graphiql/rails/application.js
Query
Queryはデータを取得するために定義します。
Queryを追加する前に、各モデルに対応したTypeを定義しましょう。
型の定義
user/post/commentの各モデルに対応したTypeを定義します。
module Types
  class UserType < Types::BaseObject
    field :id, Int, null: false
    field :name, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
    field :posts, [Types::PostType], null: false
    field :comments, [Types::CommentType], null: false
  end
end
module Types
  class PostType < Types::BaseObject
    field :id, Int, null: false
    field :user, Types::UserType, null: false
    field :subject, String, null: false
    field :body, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
    field :comments, [Types::CommentType], null: false
  end
end
module Types
  class CommentType < Types::BaseObject
    field :id, Int, null: false
    field :post, Types::PostType, null: false
    field :user, Types::UserType, null: false
    field :body, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end
graphql-rubyの場合、各Typeに定義しているfieldと同名のメソッド/attributeがモデルに定義されている場合、自動的にデータを取得できます。
fieldで型を指定しますが、標準で用意されている型には以下のようなものがあります。
- String
- Int
- Float
- Boolean
- ID
- ISO8601DateTime
※ []で囲うと配列になります。
Queryの追加
それでは、実際にデータを取得するためのQueryを追加しましょう。
特定のidを持つUserを1件返すQueryと、全てのUserを返すQueryを定義してみましょう。
QueryはTypes::QueryTypeに定義を追加します。
module Types
  class QueryType < Types::BaseObject
-    # Add root-level fields here.
-    # They will be entry points for queries on your schema.
-    # TODO: remove me
-    field :test_field, String, null: false,
-      description: "An example field added by the generator"
-    def test_field
-      "Hello World!"
-    end
+    field :user, Types::UserType, null: false do
+      description 'ユーザ情報を1件取得する'
+      argument :id, Int, required: true, description: 'ユーザID'
+    end
+    def user(id:)
+      User.find(id)
+    end
+    field :users, [Types::UserType], null: false, description: 'ユーザ情報を全件取得する'
+    def users
+      User.all
+    end
  end
end
Queryの実行
実際にgraphiqlからQueryを実行してみましょう。
まずは、idを指定して1件取得してみます。
query {
  user(id: 1) {
    id
    name
  }
}
次に、登録されているuser全てを取得します。
query {
  users {
    id
    name
    posts {
      id
      subject
      body
    }
  }
}
上記のQueryでは、各ユーザのidとname、そのユーザが投稿したPostを全て取得しています。
Postが不要な場合は、idとnameだけ取得するということも可能です。
Queryを実行する側が、必要なデータを選択して取得することができる点は、GraphQLのいい点ですね!
Mutation
次はデータを登録するためのMutationを定義しましょう。
特定のPostにコメントを追加するMutationを定義します。
Mutationクラスの作成
module Mutations
  class BaseMutation < GraphQL::Schema::RelayClassicMutation
  end
end
module Mutations
  class CreateComment < Mutations::BaseMutation
    argument :user_id, Int, required: true
    argument :post_id, Int, required: true
    argument :body, String, required: true
    field :comment, Types::CommentType, null: true
    field :errors, [String], null: false
    def resolve(user_id:, post_id:, body:)
      comment = Comment.new(user_id: user_id, post_id: post_id, body: body)
      if comment.save
        {
          comment: comment,
          errors: []
        }
      else
        {
          comment: nil,
          errors: comment.errors.full_messages
        }
      end
    end
  end
end
Mutation定義の追加
先ほど作成したCreateCommentを使えるようにするため、Mutationの定義を追加します。
module Types
  class MutationType < Types::BaseObject
    field :create_comment, mutation: Mutations::CreateComment
  end
end
Mutationの実行
では、graphiqlからMutationを実行してみましょう。
mutation {
  createComment(input: {
    userId: 1
    postId: 1
    body: "コメント追加"
  })
  {
    comment {
      id
      body
    }
  }
}
{
  comment {
    id
    body
  }
}
の部分は、登録したcommentの内容を返却するための記述です。
まとめ
ざっくりですが、RailsのプロジェクトにgraphQLを導入して、QueryとMutationを定義する部分を行いました。
一部古い情報もありますが、graphql-rubyのガイド( https://graphql-ruby.org/guides )を見ると、色々載っているので参考になります。
参考URL
https://github.com/rmosolgo/graphql-ruby
https://github.com/rmosolgo/graphiql-rails
https://graphql-ruby.org/guides



