sinatra GraphQL導入（mongodbとの連携方法）

# まえがき
友人より、REST APIの代替である[GraphQL](https://graphql.org/)というものを教えてもらった
どのようなものか気になったので試したことを備忘録として記事にします。
※sinatra導入の記事が少なかったので・・・

今回はsinatraにてGraphQLを利用するために[graphql-ruby](https://graphql-ruby.org/)というgemを使用しています。
上記gemの公式ページに使用方法等書いてありますので、一読するのがよいでしょう。

上記gemを利用し、graphqlを通した、mongodbへのquery,insertまでをやっていきます。

# 参考
GraphQL公式ページ：https://graphql.org/
=>GraphQLの公式ページです。GraphQLがどんなものなのか、公式ページで学習することをおすすめします。

graphql-ruby公式ページ：https://graphql-ruby.org/
=>GraphQLをrubyで使用できるようにするgraphql-rubyの公式ページです。使用方法等細かく乗っているので参考にしてください。

sinatra導入参考：https://medium.com/hash32/graphql-server-with-sinatra-ruby-part-1-fdd664170715
=>sinatraへの導入で参考になった記事です。


# sinatra GraphQL
今回の記事で最終的に作成するコードが下記となります。
https://github.com/Gen-Arch/graphql_test

## mongodbの用意
本記事ではdockerで立てます。
上記、githubにdocker-compose.ymlがあるので、使用してください。

```
docker-compose up
```
mongo-expressも同時に入れるようにしてあるのでmongoの確認できます。
http://localhost:8081


## gem install

### gemの説明
今回導入するgemです。

- [sinatra](https://rubygems.org/gems/sinatra)
    - rubyの軽量webフレームワーク

- [sinatra-contrib](https://rubygems.org/gems/sinatra-contrib)
    - sinatraの機能拡張

- [rack-contrib](https://rubygems.org/gems/rack-contrib)
    - rackの機能拡張

- [graphql](https://rubygems.org/gems/graphql)
    - graphql用

- [mongoid](https://rubygems.org/gems/mongoid)
    - mongodb用

- [bson_ext](https://rubygems.org/gems/mongoid)
    - mongodbで使用するBSONを早くするため

### install
gem file作成

```
mkdir graphql_test
bundle init
```

gemfileに追記

```ruby:Gemfile
gem 'sinatra'
gem 'sinatra-contrib'
gem 'rack-contrib'
gem 'graphql'
gem 'mongoid'
gem 'bson_ext'
```
install

```
bundle install --path vendor/bundle
```

## mongodbの設定
わかりやすいようにmongodb用のディレクトリを分けます。

```
mkdir models
```

mongodb接続用の設定ファイル

```yaml:models/mongoid.yml
development:
  clients:
    default:
      database: test
      hosts:
        - localhost:27017
```

mongodb接続用のruby
※class名＝table名と思ってください。

```ruby:models/test.rb
require 'mongoid'
Mongoid.load!(File.join(__dir__, 'mongoid.yml'))

class Test
  include Mongoid::Document

  field :name,  type: String
  field :text,  type: String
end
```
## graphqlの設定
こちらもgraphql用のディレクトリを分けます。

```
mkdir graphql
```
graphqlでは参照用のqueryと値追加のmutationを定義します。
ですが、まずはmongodbにて追加するデータのtypeを指定します。

### type定義
ここもディレクトリを分けます。
typeを作成する際はGraphQL::Schema::Objectを継承します。

```
mkdir types
```

```ruby:graphql/types/test.rb
module Types
  class Test < GraphQL::Schema::Object
    field :name, String, null: false
    field :text, String, null: false
  end
end
```
null: false => null値の追加を禁止

### query定義
```ruby:graphql/query.rb
require_relative 'types/test'
require 'models/test'

class QueryType < GraphQL::Schema::Object
  description 'The query root of this schema'

  field :all, [Types::Test], null: true do
    description 'Response All Data'
  end

  def all
    Test.all
  end
end
```
この定義で下記のように値が取れます。

```graphql
{
  all {
    name
    text
  }
}

# response
#{
#  "data": {
#    "all": [
#      {
#        "name": "test",
#        "text": "test hoge"
#      }
#    ]
#  }
#}
```

### mutation定義
ここもディレクトリを分けます。

```
mkdir mutation
```

値追加用のmutationを定義

```ruby:graphql/mutation/create_test.rb
$: << File.expand_path('../../', __dir__)
require 'models/test.rb'

module Mutations
  class CreateTest < GraphQL::Schema::Mutation
    description 'Creates a Test'

    argument :name, String, required: true
    argument :text, String, required: true

    field :success, Boolean, null: false
    field :errors, [String], null: false

    def resolve(name: ,text:)
      test = Test.new(
        name: name,
        text: text,
      )

      if test.save
        {
          success: true,
          errors: []
        }
      else
        {
          success: false,
          errors: test.errors.full_messages
        }
      end
    end
  end
end
```

上記を呼び出すためのtypeを作成

```ruby:graphql/mutation.rb
require_relative 'mutation/create_test'

class MutationType < GraphQL::Schema::Object
  field :createTest, mutation: Mutations::CreateTest
end
```
この定義で下記のように値が追加できます。

```graphql
mutation {
  createTest(name: "test", text: "test hoge") {
    success
    errors
  }
}

# response
#{
#  "data": {
#    "createTest": {
#      "success": true,
#      "errors": []
#    }
#  }
#}
```

## スキーマ定義
最後にqueryとmutationを呼ぶ用のスキーマを定義

```ruby:graphql/appschema.rb
require 'graphql'
require_relative 'mutation'
require_relative 'query'

class AppSchema < GraphQL::Schema
  query QueryType          #=> query
  mutation MutationType    #=> insert
end
```
※sinatraではこれを使用します。

## sinatra設定
sinatraの記述

```ruby:app.rb
require 'sinatra'
require 'sinatra/json' #=> sinatraでjson形式でレスポンスする為
require_relative 'graphql/appschema'

# sinatraでjsonリクエストを受け取れるようにしています。
require 'rack/contrib'
use Rack::PostBodyContentTypeParser

# grapfqlのリクエスト処理
post '/graphql' do
  content_type :json
  puts params[:query]
  result = AppSchema.execute(
    params[:query],
    variables: params[:variables],
    context: { current_user: nil },
  )
  json result
end
```

## sinatra起動
```
bundle exec ruby app.rb
```

## graphql参照
graphqlを使用するために便利なツールがありますので使用をおすすめします。

[GraphiQL.app](https://electronjs.org/apps/graphiql)

### mutation
![DeepinScreenshot_select-area_20190406200313.png](https://qiita-image-store.s3.amazonaws.com/0/96921/fab6c31b-2de9-5a4e-c07c-f40794d6af90.png)

### query
![DeepinScreenshot_select-area_20190406200250.png](https://qiita-image-store.s3.amazonaws.com/0/96921/aab57586-598b-b3a6-b2e3-b46085128ba3.png)



う〜ん、拡張性ありそうで好きになれそうですね。