入門 GraphQL Ruby  〜基本のCRUD処理〜

 [GraphQL Ruby](https://github.com/rmosolgo/graphql-ruby)の紹介です。
Todoアプリ用の簡単なAPIサーバーの作成をチュートリアル形式でまとめます。

こちらで紹介するコードは全て以下リポジトリにあります。
https://github.com/kawamataryo/practice-graphql-ruby-2


# GraphQL Ruby とは?
https://github.com/rmosolgo/graphql-ruby

RubyでGraphQLのAPIサーバーを立てるためのGemです。
他のライブラリと異なりGraphQLのスキーマを直接書かず、Rubyのコードで実装とともにスキーマを定義していくという特徴があります。


# Setup
ここからTodoアプリのAPIサーバーを作っていきます。
いつも通り`rails new`でプロジェクトを作成します。不要なモジュールはスキップします。


```bash
rails new todo-app --skip-action-mailer --skip-action-mailbox --skip-action-text --skip-active-storage --skip-action-cable --skip-javascript --skip-system-test --skip-webpack-install
```

`rails s`で起動すると、おなじみの画面が表示されます。

```
cd todo-app
bundle exec rails s
```

<img width="1104" alt="スクリーンショット 2020-04-19 20.19.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/f1f9797b-4054-8abf-45c2-f621b877db98.png">

# GraphQL Ruby の導入
次にGraphQL Rubyを導入します。
Gemfileに以下を追記してください。

`graphql` はGraphQL Ruby本体。
`grphiql-rails`はプラウザ上で実行できるGraphQLのクライアント`GraphiQL`を立てるためのものです。

```Gemfile
gem 'graphql' 
gem 'graphiql-rails', group: :development
```

bundle installした後で、generateコマンドを実行し関連ファイルを作成します。

```
bundle i
bundle exec rails generate graphql:install
```

ローカルサーバーを再起動して以下にアクセスします。

http://localhost:3000/graphiql

GraphiQLのコンソールが起動するので、以下クエリを実行しましょう。

```graphql
query {
  testField
}
```

無事Hello Worldが表示されましたか？ これでGraphQL Rubyの導入は完了です。

<img width="890" alt="スクリーンショット 2020-04-19 20.38.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/968822e9-35c0-f091-97d5-69ae4cd88e80.png">

**※ 2020/04/19現在はgraphiqlで以下エラーが発生するようです**

```
Asset `graphiql/rails/application.css` was not declared to be precompiled in production.
Declare links to your assets in `app/assets/config/manifest.js`
```

その場合は、`config/environments/development.rb` のconfigの末尾に以下を追記しましょう。

```ruby:config/environments/development.rb
  config.assets.precompile += ['graphiql/rails/application.js', 'graphiql/rails/application.css']
```


# タスク一覧取得のQuery
タスク一覧を取得するクエリを作成します。

Taskモデルをgenerateで作成し、さらにDBのマイグレーションを実行しましょう。

```bash
bundle exec rails g model Task title:string note:string completed:boolean

bundle exec rails db:migrate
```

続いてgraphql-rubyのgeneraterでTaskのTypeを作成します。
このTypeがレスポンスの型となります。

```
rails g graphql:object TaskType id:ID! title:String! note:String completed_at:Boolean
```

`app/graphql/types/task_type.rb`が作成されます。

```app/graphql/types/task_type.rb
module Types
  class TaskType < Types::BaseObject
    field :id, ID, null: false
    field :title, String, null: false
    field :note, String, null: true
    field :completed, Boolean, null: true
  end
end
```

次に`resolvers`ディレクトリを作りTasksのリゾルバを作成します。
resolve関数にてTask.allでを返しています。これがレスポンスとなります。

```app/graphql/resolvers/tasks.rb
module Resolvers
  class Tasks < GraphQL::Schema::Resolver
    type [Types::TaskType], null: false

    def resolve
      Task.all
    end
  end
end

```

そしてquery_type.rbで`tasks`fieldをリゾルバと紐づけて宣言します。

```app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    field :tasks, resolver: Resolvers::Tasks
  end
end
```
以上で最初のQueryの実装は完了です。

初期データがないと空配列が返るので`bundle exec rails c` でRailsコンソールを起動して、タスクを追加しておきます。

```bash
Task.create!(title: '買い物', note: 'たまご, 牛乳')
Task.create!(title: '掃除', note: 'リビング')
```

これで確認の準備が整いました。

http://localhost:3000/graphiql

にアクセスしてGraphiQLで以下クエリを実行してみましょう。

```graphql
query {
  tasks {
    title
    note
    completed
  } 
}
```

<img width="1025" alt="スクリーンショット 2020-04-21 6.04.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/b586bace-bd5c-f8c9-7895-25541aa301f3.png">

`Task.all`の取得結果が返ってきますね。graphql-rubyがActiveRecordをパースしてGraphQLのデータを返してくれています。

# タスク追加のMutation
次にタスクの追加をできるようにしましょう。
まず`mutaitions`ディレクトリに`create_task.rb`を作ります。

```app/graphql/mutations/create_task.rb
module Mutations
  class CreateTask < GraphQL::Schema::Mutation
    argument(:title, String, required: true)
    argument(:note, String, required: false)

    type Types::TaskType

    def resolve(title: nil, note: nil)
      Task.create!(
        title: title,
        note: note
      )
    end
  end
end
```
（本来はBaseMutationを継承するべきなのですが、簡略化のため`GraphQL::Schema::Mutation`を直接継承させています。）

そしてmutation_type.rbで`:create_task`fieldをCreateTaskミューテーションと紐づけて宣言します。


```app/graphql/types/mutation_type.rb
module Types
  class MutationType < Types::BaseObject
    field :create_task, mutation: Mutations::CreateTask, null: false
  end
end
```

これだけでタスクの作成ができるはずです。

以下ミューテーションを実行しましょう。

```graphql
mutation {
  createTask(title: "勉強" note: "graphql-rubyのチュートリアル") {
    id
    title
    note
    completed
  }
}
```

<img width="1054" alt="スクリーンショット 2020-04-21 6.05.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/73a80221-0bd7-d1c9-1ccf-b390307ce42d.png">

無事Taskが作成できましたね。

# タスク更新のMutation

続いてタスクの更新のMutationを作っていきます。
created_task.rbをコピーしてmutationsディレクトリにupdate_task.rbを作成しましょう。

```app/graphql/mutations/update_task.rb
module Mutations
  class UpdateTask < GraphQL::Schema::Mutation
    argument(:id, ID, required: true)
    argument(:completed, Boolean, required: true)
    argument(:title, String, required: false)
    argument(:note, String, required: false)

    type Types::TaskType

    def resolve(id: nil, completed: nil, title: nil, note: nil)
      task = Task.find(id)
      task.completed = completed
      task.title = title if title
      task.note = note if note
      task.tap(&:save!)
    end
  end
end
```

そしてmutation_type.rbにupdate_taskを追加します。

```app/graphql/types/update_task.rb
module Types
  class MutationType < Types::BaseObject
    field :create_task, mutation: Mutations::CreateTask, null: false
    field :update_task, mutation: Mutations::UpdateTask, null: false # こちらを追加
  end
end

```

これで更新は完了です。
GraphiQLコンソールで以下ミューテーションを実行しましょう。

```graphql
mutation {
  update_task(id: 3 completed: true) {
    id
    title
    note
    completed    
  }
}

```

<img width="1048" alt="スクリーンショット 2020-04-21 6.29.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/c800033c-be84-0bc0-b4ae-82e80a97f848.png">

# タスク削除のMutation
最後にタスクの削除を実装します。
delete_task.rbを作成します。
deleteのレスポンスはTaskのオブジェクトではなく`deleted: Boolean`としたかったので、
Resolver内で`Types::BaseObject`を継承した`DeleteResultType` を定義し、typeで指定しています。

```app/graphql/mutations/delete_task.rb
module Mutations
  class DeleteTask < GraphQL::Schema::Mutation
    argument(:id, ID, required: true)

    class DeleteResultType < Types::BaseObject
      field :deleted, Boolean, null: false
    end

    type DeleteResultType

    def resolve(id: nil)
      result = Task.find(id).destroy
      { deleted: result }
    end
  end
end
```

mutation_type.rbにもdlete_taskを追加します。

```app/graphql/types/mutation_type.rb
module Types
  class MutationType < Types::BaseObject
    field :create_task, mutation: Mutations::CreateTask, null: false
    field :update_task, mutation: Mutations::UpdateTask, null: false
    field :delete_task, mutation: Mutations::DeleteTask, null: false # これを追加
  end
end
```

ここまで出来たら以下ミューテーションを実行してみましょう。

```graphql
mutation {
  deleteTask(id: 2) {
    deleted
  }
}
```

<img width="1037" alt="スクリーンショット 2020-04-21 7.05.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/e4178dd6-80eb-1fea-a9b7-439e9931ff73.png">

無事タスクが削除できましたね。

これでTODOアプリのCRUD処理の完成です。

# Next step

今回紹介出来なかった認証や、ページネーションなどの実装方法はGraphQL公式のチュートリアルに記載されています。是非、次のステップとしてトライしてみてください。

https://www.howtographql.com/graphql-ruby/1-getting-started/

# 終わりに

以上、GraphQL Rubyの紹介でした。
RailsでさくっとGraphQLのAPIサーバーを作れるのは良いですよね。
REST APIとの共存もできるので、新規API作成時の選択肢の一つになるのかなと思います。

# 参考

- https://employment.en-japan.com/engineerhub/entry/2018/12/26/103000