【Rails】graphql-rubyでAPIを作成

# 概要
 [GraphQL Ruby](http://graphql-ruby.org/)のバージョン`1.8~`以降で導入された`Class-based`の書き方を使用し、簡単なAPIを作成する方法を紹介します。
ネット上で`Class-based`の書き方に関する記事が少なかったので、誰かの助けになれば幸いです。
 
__`GraphQL`,`GraphQL Ruby`の基本概念等は以下で学習して頂けます。__
- [GraphQL入門 - 使いたくなるGraphQL - Qiita](https://qiita.com/bananaumai/items/3eb77a67102f53e8a1ad)
- [GraphQL Rubyに関して](http://graphql-ruby.org/guides)
- [GraphQL公式](https://graphql.org/learn/)
- [How to GraphQL - The Fullstack Tutorial for GraphQL](https://www.howtographql.com/)


# GraphQL with Ruby on Rails
ユーザーが記事(`post`)の取得、作成、更新、削除を行える簡単なapiを`Ruby on Rails`と`GraphQL`を用いて作成します。
ソースコードは[こちら](https://github.com/K-Sato1995/GraphqlApi)から確認する事ができます。


## Installation
まず、以下のコマンドで`api`モードでrailsアプリケーションを作成します。

```console
$ rails new blog --api
```
`Gemfile`に`graphql`と`graphql-rail` を加えます。

```ruby:Gemfile
gem 'graphql'
gem 'graphiql-rails' #開発環境でgraphiqlを使用可能にします。こちらは必須ではありません。
```

通常のrailsアプリならこれだけでセットアップは完了ですが、`apiモード`として作成する場合は、`config/application.rb`に以下の文を加えます。
(開発環境で[graphiql](https://github.com/rmosolgo/graphiql-rails)を使用する可能にする為です。こちらが必要ない場合は下記を追加する必要はありません。)

```ruby:config/application.rb
require "sprockets/railtie"
```

必要なgem等を追加したら、以下のコマンドを流しましょう。
2つめのコマンドを流すと`app/graphql`ディレクトリが作成され、GraphQL関連のファイルはこのディレクトリ下に配置されます。

```console
$ bundle install
$ rails generate graphql:install
```

## Queries
大雑把に説明すると`GraphQL`では全てが`Type`(オブジェクトの設計書のようなもの)を持っており、その`Type`に従い、`Queries`を使用して、データの取得を行い、`Mutations`を使用して、データの作成・更新を行います。
まず、データの取得を行う`Queries`からみていきましょう。

`Post`モデルを作成し、 `rake db:migrate`を流します。

```console
$ rails g model Post title:string description:text
$ rake db:migrate
```

Queryでデータを取得できる事を確認するため、 `rails console`を使用して事前にデータを作成しましょう。

```console
$ rails c
$ Post.create(title: "What is Ruby?", description:"Ruby is a programming language")
$ Post.create(title: "How to learn Ruby", description:"Read some books brah")
```

`GraphQL`では各`Type`を基に`Query`の実行を行なっていきます。
そこで、`Post`の`Type`を定義するため、以下のコマンドを流し`Post Type`を作成します。

```
$ rails g graphql:object Post id:ID! title:String! description:String!
```

以下が上記のコマンドで作成されたファイルの中身です。

```ruby:app/graphql/types/post_type.rb
module Types
  class PostType < Types::BaseObject
    field :id, ID, null: false
    field :title, String, null: false
    field :description, String, null: false
  end
end
```
`class~end`を使用してよりSytaxが以前と比べてよりわかりやすくなっています。
`Class Based Syntax`について詳しく知りたい方は[公式](http://graphql-ruby.org/schema/class_based_api.html#classes)でわかりやすく説明されています。

## Query Resolver
Typeは定義されましたが、サーバはこのタイプをどのように扱えば良いかわかりません。
そこで、実際に`queries`を実行するために`resolver`を作成していきます。

先ほど`rails g graphql:install`を流した際に作成された `app/graphql/types/query_type.rb`に以下のコードを追加する事で、全ての`Post`の取得と特定の`id`を持つ`Post`の取得ができます。

```ruby
# app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    field :posts, [Types::PostType], null: false
    def posts
      Post.all
    end

    field :post, Types::PostType, null: false do
      argument :id, Int, required: false
    end
    def post(id:)
      Post.find(id)
    end
  end
end
```

### GraphiQLで確認
以下を`routes.rb`に追加します。

```ruby
Rails.application.routes.draw do
  if Rails.env.development?
    # add the url of your end-point to graphql_path.
    mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" 
  end
  post '/graphql', to: 'graphql#execute'
end
```
上記の`if~end`を加える事で`http://localhost:3000/graphiql`にアクセスし、実際に先ほど定義した`query`の確認を行う事ができます。

実際に `rails s`をコンソールで流し、`http://localhost:3000/graphiql`で以下の`query` を実行してみましょう。

```
#　全てのPost取得
{
 posts {
   id
   title
   description
 }
}

############## 結果 #####################
{
  "data": {
    "posts": [
      {
        "id": 1,
        "title": "title1",
        "description": "description1"
      },
      {
        "id": 2,
        "title": "title2",
        "description": "description2"
      },
      {
        "id": 3,
        "title": "title3",
        "description": "description3"
      },
     ]
   }
}

# 特定のPostの取得
{
 post(id:1) {
   id
   title
   description
 }
}

############## 結果 #####################
{
  "data": {
    "post": {
      "id": 1,
      "title": "title1",
      "description": "description1"
    }
  }
}

```
## Mutations
上記で簡単に説明したように`GraphQL`では`Mutations`を使用してデータの作成・更新を行います。
以下の`app/graphql/types/mutation_type.rb`も最初に`rails g graphql:install`を流した際に作成されています。
 
```ruby
module Types
  class MutationType < Types::BaseObject
    # TODO: remove me
    field :test_field, String, null: false,
      description: "An example field added by the generator"
    def test_field
      "Hello World"
    end
  end
end
```

### Mutation Create 
`Post`を作成する為に使用される`mutation`を作成する為に以下のコマンドを流します。

```console
$ rails g graphql:mutation CreatePost
``` 
上記のコマンドは以下の2つの事を行います。
- (1)`graphql/mutations/create_post.rb`の作成。
- (2)`field :createPost, mutation: Mutations::CreatePost`を`graphql/types/mutations_type.rb`に追記する。

```ruby:graphql/mutations/create_post.rb
module Mutations
  class CreatePost < GraphQL::Schema::RelayClassicMutation
    # TODO: define return fields
    # field :post, Types::PostType, null: false

    # TODO: define arguments
    # argument :name, String, required: true

    # TODO: define resolve method
    # def resolve(name:)
    #   { post: ... }
    # end
  end
end
```

```ruby 
# app/graphql/types/mutation_type.rb
module Types
  class MutationType < Types::BaseObject
    field :createPost, mutation: Mutations::CreatePost
  end
end
```

 `graphql/mutations/create_post.rb` に書かれている `TODOs` に従い、`field`や`argument`を定義して、`Post`の作成が行えるよう、以下のように編集していきます。

```ruby
:graphql/mutations/create_post.rb
module Mutations
  class CreatePost < GraphQL::Schema::RelayClassicMutation
    graphql_name 'CreatePost'

    field :post, Types::PostType, null: true
    field :result, Boolean, null: true

    argument :title, String, required: false
    argument :description, String, required: false

    def resolve(**args)
      post = Post.create(title: args[:title], description: args[:description])
      {
        post: post,
        result: post.errors.blank?
      }
    end
  end
end
```

### GraphiQLで確認
先ほどと同じく`rails s`でサーバを立ち上げ [GraphQL](http://localhost:3000/graphiql) で`Post`の作成ができるか試してみましょう。

```
mutation {
  createPost(
    input:{
      title: "title1"
      description: "description1"
    }
  ){
    post {
      id
      title 
      description
    }
  }
}

############## 結果 #####################
{
  "data": {
    "createPost": {
      "post": {
        "id": 15,
        "title": "title1",
        "description": "description1"
      }
    }
  }
}
```
### Mutation Update
`Post`を更新する為に使用される`mutation`を作成する為に以下のコマンドを流します

```console
$ rails g graphql:mutation UpdatePost
```

自動作成された `/graphql/mutations/update_post.rb` を以下のように変更しましょう。
`/graphql/mutations/create_post.rb`と非常に似ており、違いは`argument`にて`id`を指定する箇所くらいです。

```ruby
module Mutations
  class UpdatePost < GraphQL::Schema::RelayClassicMutation
    graphql_name 'UpdatePost'

    field :post, Types::PostType, null: true
    field :result, Boolean, null: true

    argument :id, ID, required: true
    argument :title, String, required: false
    argument :description, String, required: false

    def resolve(**args)
      post = Post.find(args[:id])
      post.update(title: args[:title], description: args[:description])
      {
        post: post,
        result: post.errors.blank?
      }
    end
  end
end
```

### GraphiQLで確認
`rails s`でサーバを立ち上げ [GraphQL](http://localhost:3000/graphiql) で`Post`の更新ができるか試してみましょう。

```
mutation {
  updatePost(
    input:{
      id: 1
      title: "Updated"
      description: "UPdated"
    }
  ){
    post {
      id
      title 
      description
    }
  }
}

############## 結果 #####################
{
  "data": {
    "updatePost": {
      "post": {
        "id": 1,
        "title": "Updated",
        "description": "UPdated"
      }
    }
  }
}
```

### Mutation Delete
作成、更新の`mutations`を作成したのとほぼ同じ工程で削除の為の`mutation`を作成します。
以下のコマンドを流します。

```console
$ rails g graphql:mutation DeletePost
```

今回は削除の為に指定する`id`のみを`argument`にセットします。

```ruby:graphql/mutations/delete_post.rb
module Mutations
  class DeletePost < GraphQL::Schema::RelayClassicMutation
    graphql_name 'DeletePost'

    field :post, Types::PostType, null: true
    field :result, Boolean, null: true

    argument :id, ID, required: true

    def resolve(**args)
      post = Post.find(args[:id])
      post.destroy
      {
        post: post,
        result: post.errors.blank?
      }
    end
  end
end
```

### GraphiQLで確認
`rails s`でサーバを立ち上げ [GraphQL](http://localhost:3000/graphiql) で`Post`の削除ができるか試してみましょう。

```
mutation {
  deletePost(
    input:{
      id: 1
    }
  ){
    post {
      id
      title 
      description
    }
  }
}

############## 結果 #####################
{
  "data": {
    "posts": [
      {
        "id": 2,
        "title": "How to learn Ruby"
      },
      {
        "id": 3,
        "title": "title1"
      }]
  }
}
```

## Connection fields(1)
`GraphQL`では`types`,`queries`,`mutations`の働きがわかればアソーシエーションも簡単に対処できます。
実際に各ポストが持つコメントを取得できるようにしてみましょう。

まず、`Comment`モデルを作成し、`Post`と `多対１(has_many/belongs_to)`の関係をもたせます。
また、ここで確認の為のデータを作成しておきましょう。

```console
$ rails g model Comment content:string post:references
$ rake db:migrate
```

```ruby
# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :post
end

# app/models/post.rb
class Post < ApplicationRecord
  has_many :comments, dependent: :destroy
end
```

```ruby:db/seeds.rb
Post.new.tap do |post|
  post.title = 'title'
  post.description = 'description'
  post.comments.build(content: 'comment1')
  post.save!
end
```

```console
$ rake db:seed
```

次に、以下のコマンドで`Comment type`を作成しましょう。

```console
$ rails g graphql:object Comment id:ID! content:String!
```
上記のコマンドは以下のファイルを作成します。

```ruby:app/graphql/types/comment_type.rb
module Types
  class CommentType < Types::BaseObject
    description 'Comment'

    field :id, ID, null: false
    field :content, String, null: false
  end
end
```
そして、`comments`という`field`を`Post type`に追加しましょう。

```ruby:app/graphql/types/post_type.rb
module Types
  class PostType < Types::BaseObject
    description 'Post'

    field :id, Int, null: false
    field :title, String, null: false
    field :description, String, null: false
    field :comments, [Types::CommentType], null: false 
  end
end
```
### GraphiQLで確認
`rails s`でサーバを立ち上げ [GraphQL](http://localhost:3000/graphiql) で`Post`の`comments`が取得できるか試してみましょう。


```
{
  posts {
    id
    title
    comments {
      id
      content
    }
  }
}

############## 結果 #####################
{
  "data": {
    "posts": [
      {
        "id": "1",
        "title": "title",
        "comments": [
          {
            "id": "1",
            "content": "comment1"
          }
        ]
      }
    ]
  }
}
```

## Connection fields(2)
`Comment`を作成できるようにしてみましょう。
以下のコマンドを流して`/graphql/types/mutation_type.rb`を作成します。

```console
$ rails g graphql:mutation CreateComment
```
次の`field :post, Types::PostType, null: false`を`app/graphql/types/comment_type.rb`に追加しましょう。

```ruby:app/graphql/types/comment_type.rb
module Types
  class CommentType < Types::BaseObject
    description 'Comment'
    field :id, ID, null: false
    field :content, String, null: false
    field :post, Types::PostType, null: false
  end
end
```

そして、 `app/graphql/mutations/create_comment.rb`を以下のように変更します。

```ruby:app/graphql/mutations/create_comment.rb
module Mutations
  class CreateComment < GraphQL::Schema::RelayClassicMutation
    graphql_name 'CreateComment'

    field :comment, Types::CommentType, null: true
    field :result, Boolean, null: true

    argument :post_id, ID, required: true
    argument :content, String, required: true

    def resolve(**args)
      post = Post.find(args[:post_id])
      comment = post.comments.build(content: args[:content])
      comment.save
      {
        comment: comment,
        result: post.errors.blank?
      }
    end
  end
end
```

### GraphiQLで確認
`rails s`でサーバを立ち上げ [GraphQL](http://localhost:3000/graphiql) で`Post`の`comments`が作成できるか試してみましょう。

```
mutation {
  createComment(
    input:{
      postId: 1
      content: "NEW COMMENT"
    }
  ){
    comment {
      id
      content
      post {
        id
        title
        comments {
          id
          content
        }
      }
    }
  }
}

############## 結果 #####################
{
  "data": {
    "createComment": {
      "comment": {
        "id": "2",
        "content": "NEW COMMENT",
        "post": {
          "id": "1",
          "title": "title",
          "comments": [
            {
              "id": "1",
              "content": "comment1"
            },
            {
              "id": "2",
              "content": "NEW COMMENT"
            }
          ]
        }
      }
    }
  }
}
```
`Comment`の更新や削除を行いたい場合は`Post`の更新、削除の方法を真似て作成してみてください。


## 参考
- [GraphQL Ruby(Class-based API) - DEV Community 👩‍💻👨‍💻](https://dev.to/ksato1995/graphql-rubyclass-based-api-523n)
- [Building a GraphQL Server with Ruby Backend Tutorial](https://www.howtographql.com/graphql-ruby/0-introduction/)
- [A Guide to GraphQL in Plain English – freeCodeCamp.org](https://medium.freecodecamp.org/a-beginners-guide-to-graphql-60e43b0a41f5)
- [GraphQL Ruby](http://graphql-ruby.org/)
- [How to Implement a GraphQL API in Rails - via @codeship | via @codeship](https://blog.codeship.com/how-to-implement-a-graphql-api-in-rails/)
- [When you use graphiql-rails for your api only rails application](https://github.com/rmosolgo/graphiql-rails/issues/13)
- [When you use graphiql-rails for your api only rails application](https://github.com/rmosolgo/graphiql-rails/issues/13)