RailsでGraphQLをやってみる

# はじめに

メリークリスマス！
クリスマスはイエス・キリストの誕生日だと思っていたんですが、**あくまで誕生を祝う日であって、イエス・キリストの誕生日ではない**ということを最近知りました。
>クリスマス（英: Christmas）は「キリストのミサ」という意味で、一部の教派が行うイエス・キリストの降誕祭[1]。あくまで誕生を祝う日であって、イエス・キリストの誕生日ではない[2]。
>毎年12月25日に祝われるが、正教会のうちユリウス暦を使用するものは、グレゴリオ暦の1月7日に該当する日にクリスマスを祝う[3][4]。ただし、キリスト教で>最も重要な祭と位置づけられるのはクリスマスではなく、復活祭である[5][6][7][8]。
>キリスト教に先立つユダヤ教の暦、ローマ帝国の暦、およびこれらを引き継いだ教会暦では, 現代の常用時とは異なり、日没を一日の境目としているので、クリスマス・イヴと呼ばれる12月24日夕刻から12月25日朝までも、教会暦上はクリスマスと同じ日に数えられる[9]。したがって、教会暦ではクリスマスは「12月24日の日没から12月25日の日没まで」である。
https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AA%E3%82%B9%E3%83%9E%E3%82%B9

せっかくクリスマスの日に担当になったので、クリスマスに絡めたネタを書こうと思っていたのですが、全く思いつかないので普通に記事を書きます。

これは[Hameeアドベントカレンダー](https://qiita.com/advent-calendar/2020/hamee)25日目の記事です。

# 背景

普段、ふるさと納税関連のお仕事をしています。現在、アプリケーションエンジニアチームは2人。複数のアプリケーションを開発していて、 API開発での課題感がでてきました。

- エンドポイントがたくさん。IFを実装するたびに都度作成していて、管理しにくくなってきている
- スピード重視・少人数のため、APIドキュメント管理できていない

上記のこと、またいまさらですがGraphQLをやってみたかったので、やってみることにしました。

# GraphQLとは

GraphQLの紹介は多くの方が記事にされているので、簡単にまとめます。(RESTとの違いについてもここでは説明しません)
GraphQLはFacebookが開発しているWeb APIのための規格で、「クエリ言語」と「スキーマ言語」からなります。
以下の特徴があります。

- スキーマ(Web APIの仕様)を構築し、クエリでどのようなデータを取得するかを表現
- 単一のエンドポイント(/graphql)のみ。ほしいデータはhttp bodyに明記して取得可能
- GraphiQLという専用のIDEを使えば、スキーマ等のドキュメントも確認可能
- クエリ言語にはデータ取得系の**query**、データ更新系の**mutation**、pub/subモデルでサーバーサイドのイベントを受け取る**subscription**がある

以下の記事は大変参考にさせて頂きました。
[「GraphQL」徹底入門 ─ RESTとの比較、API・フロント双方の実装から学ぶ](https://eh-career.com/engineerhub/entry/2018/12/26/103000)
[Web API初心者と学ぶGraphQL](https://qiita.com/SiragumoHuin/items/cc58f456bc43a1be41b4)

# Hello worldやってみる

今回はRubyライブラリとして提供されているgraphql、graphql-railsを使って、GraphQLをやってみます。

## 実行環境

|  | version | 
|:-----------|:------------|
| ruby       | 2.6.3       | 
| rails     | 5.2.3      | 
| gem graphql      | 1.11.6        | 
| gem graphql-rails         | 1.7.0          | 

## 手順

```ruby:Gemfile
gem 'graphql'

group :development, :test do
  gem 'graphiql-rails'
end
```

Gemfileに記載後、以下のコマンドを実行します。
実行後に以下のファイル郡が作成されます。

```
$ bundle install
$ rails generate graphql:install
$ bundle exec rails g graphql:install
create  app/graphql/types
      create  app/graphql/types/.keep
      create  app/graphql/app_schema.rb
      create  app/graphql/types/base_object.rb
      create  app/graphql/types/base_argument.rb
      create  app/graphql/types/base_field.rb
      create  app/graphql/types/base_enum.rb
      create  app/graphql/types/base_input_object.rb
      create  app/graphql/types/base_interface.rb
      create  app/graphql/types/base_scalar.rb
      create  app/graphql/types/base_union.rb
      create  app/graphql/types/query_type.rb
add_root_type  query
      create  app/graphql/mutations
      create  app/graphql/mutations/.keep
      create  app/graphql/mutations/base_mutation.rb
      create  app/graphql/types/mutation_type.rb
add_root_type  mutation
      create  app/controllers/graphql_controller.rb
       route  post "/graphql", to: "graphql#execute"
     gemfile  graphiql-rails
       route  graphiql-rails
Gemfile has been modified, make sure you `bundle install`
```

また、専用のIDEも/graphiqlで確認できます。
(私の場合は初期画面ではQUERY VALIABLESのみしか見えない状態でしたが、リロードで表示できました)
最初に以下のクエリを実行(左側)して、実行結果が正しく表示されていれば(右側)、Hello world成功です。

```
query {
  testField
}
```

また、右上のDocs>queryを選択すると、API側で設定されているスキーマ定義やクエリを確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30891/f6d0d5d7-5f49-71c7-0ed9-41e3ad77fe8f.png)

# サンプルデータでやってみる

年末なので、ふるさと納税に絡めて、リレーションがある3つのサンプルデータを作ってみます。

- local_governments: 自治体
    - id: 自治体ID
- suppliers: 返礼品事業者
    - id: 返礼品事業者ID
    - local_government_id: 自治体ID
- return_gifts: 返礼品
    - id: 返礼品ID
    - supplier_id: 返礼品事業者ID
    - name: 返礼品名

上記のデータは以下のリレーションがあります。
![IMG_1926.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30891/8debdae4-059d-8841-c2ad-d35ce48f6fda.jpeg)

## スキーマ定義

まずはスキーマ定義からです。
各テーブルと対になる形で定義していきます。

```ruby:app/graphql/types/local_government_type.rb
module Types
  class LocalGovernmentType < Types::BaseObject
    field :id, ID, null: false
    field :suppliers, [Types::SupplierType], null: false

    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end
```
```ruby:app/graphql/types/supplier_type.rb
module Types
  class SupplierType < Types::BaseObject
    field :id, ID, null: false
    field :name, String, null: false
    def name
      object.info['supplier_name']
    end
    field :return_gifts, [Types::ReturnGiftType], null: false

    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end
```
↑objectという変数でResolverからオブジェクトが渡されます。suppliers.nameは本来存在しないのですが、json型で定義されているデータを渡してます。decoratorみたいな感じで使えるのかなと思います。

```ruby:app/graphql/types/return_gift_type.rb
module Types
  class ReturnGiftType < Types::BaseObject
    field :id, ID, null: false
    field :name, String, null: false
    field :supplier, Types::SupplierType, null: false

    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end

```

## Query

サンプルデータから特定の自治体に紐づく返礼品事業者、返礼品すべてを取得したいと思います。

### 定義
Query定義、Resolver作成をしていきます。

```ruby:app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    field :local_government, LocalGovernmentType, null: false, description: '自治体情報' do
      argument :id, Int, '自治体ID', required: true
    end
    def local_government(id:)
      LocalGovernment.find_by(id: id)
    end
  end
end
```
自治体情報はIDで条件指定をできるようにしておきます。


### 実行

```
query {
  localGovernment(id: 1){
    id
    suppliers{
      id
      returnGifts{
        id
        name
      }
    }
  }
}
```

上記のクエリを実行すると、下記のレスポンスが返却されることがわかります。

```
{
  "data": {
    "localGovernment": {
      "id": "1",
      "suppliers": [
        {
          "id": "1",
          "name": "返礼品事業者X",
          "returnGifts": [
            {
              "id": "1",
              "name": "返礼品A"
            },
            {
              "id": "4",
              "name": "返礼品B"
            },
            {
              "id": "5",
              "name": "返礼品C"
            }
          ]
        },
        {
          "id": "5",
          "name": "返礼品事業者Y",
          "returnGifts": [
            {
              "id": "11",
              "name": "返礼品I"
            },
            {
              "id": "12",
              "name": "返礼品J"
            }
          ]
        }
      ]
    }
  }
}
```
関連情報も上手く取得できています。

## Mutation

次は特定の返礼品事業者の返礼品情報を追加したいと思います。
流れとしては、Mutationクラス作成→Mutation定義作成→クエリ実行になります。

### Mutationクラス

```ruby:app/graphql/mutations/create_return_gift_mutation.rb
module Mutations
  class CreateReturnGiftMutation < Mutations::BaseMutation
    argument :supplier_id, Int, required: true
    argument :name, String, required: true

    field :return_gift, Types::ReturnGiftType, null: true
    field :errors, [String], null: false

    def resolve(supplier_id:, name:)
      return_gift = ReturnGift.new(
        supplier_id: supplier_id,
        name: name
      )

      if return_gift.save
        {
          return_gift: return_gift,
          errors: []
        }
      else
        {
          return_gift: nil,
          errors: return_gift.errors.full_messages
        }
      end
    end
  end
end
```

### Mutation定義

```ruby:app/graphql/mutations/create_return_gift_mutation.rb
module Types
  class MutationType < Types::BaseObject
    field :create_return_gift, mutation: Mutations::CreateReturnGiftMutation
  end
end
```

### 実行

```
mutation {
  createReturnGift(input: {
    supplierId: 5
    name: "追加した返礼品"
  }) 
  { 
    returnGift{
      id
      supplier{
        id
        name
        returnGifts{
          id
          name
        }
      }
    }
  }
}
```
上記のクエリを実行すると、下記のレスポンスが返却されることがわかります。
返礼品データも作成されています。

```
{
  "data": {
    "createReturnGift": {
      "returnGift": {
        "id": "24",
        "supplier": {
          "id": "5",
          "name": "返礼品事業者Y",
          "returnGifts": [
            {
              "id": "11",
              "name": "返礼品I"
            },
            {
              "id": "12",
              "name": "返礼品J"
            },
            {
              "id": "24",
              "name": "追加した返礼品"
            }
          ]
        }
      }
    }
  }
}
```

# おわりに

今回はGraphQLでデータ取得、データ作成をやってみました。
意識せずにドキュメントが作られる点(Graphiql便利すぎ)や単一エンドポイントな点、(正しく命名できていれば)クエリが直感的でわかりやすい点など、結構感動しました。
他にもgraphql-batchを使った場合や型定義など、ベストプラクティスを学んでいきたいと思います。

Hameeアドベントカレンダー2020お疲れさまでした！来年もまたやりましょおおお

(あと、来年度控除のふるさと納税は12/31になってます。まだ間に合うのでやってない方はぜひ。)
https://yummy.hamee-furusato.jp/

# REF
[graphql-ruby.org](https://graphql-ruby.org/)
[【Rails】graphql-rubyでAPIを作成](https://qiita.com/k-penguin-sato/items/07fef2f26fd6339e0e69#mutation-create)
[GraphQL Ruby の使い方 (基礎編)](https://qiita.com/sukechannnn/items/b2168d7214992e5d37bf)
[Ruby on RailsエンジニアがGraphQLを使うと簡単に最強のWebAPIを作れる話。(デモもあるよ！)](https://spirits.appirits.com/type/technology/7055/)