LoginSignup
1
2

More than 3 years have passed since last update.

GraphQLを試してみた2 -RubyでGraphQLを試してみた-

Last updated at Posted at 2019-05-20

本稿はRubyでGraphQLのバックエンド側の処理を書いた際(というか調べた際)のメモ。
基本的にググったり、公式ページを見たりした。
(結果から言うと、公式の情報だけで十分だった気もする……)

尚、本稿の前提は以下の通り。

  • Rubyが分かる。
  • Bundlerをインストール済み (なくても意味は分かれば十分)。
  • GraphQLが何者を知ってる。

本稿の各サンプルの実行方法

最初に本稿の各サンプルコードの実行方法を示す。

  • 以下のようなGemfileを用意する。
Gemfile
source "https://rubygems.org"

gem "graphql"
  • GraphQLライブラリをインストールする。
$ bundle install --path vendor/bundle
  • サンプルコードを保存し、以下のように実行する。
$ bundle exec ruby xxxx.rb

ファイルの保存が面倒であれば以下のようにirb経由でも良い。

$ bundle exec irb

Query

GraphQLライブラリでQueryを書く方法は以下の2つ。

  • defineメソッドを使用する方法
  • 子クラスを定義する方法

defineメソッドを定義する方法

test1.rb
require 'graphql'

QueryType = GraphQL::ObjectType.define do
  name 'Query'
  field :name do
    type types.String
    description 'the name of this thing'
    resolve -> (obj, args, ctx) { 'Nanashi' }
  end
end

Schema = GraphQL::Schema.define do
  query QueryType
end

puts Schema.execute('{name}').to_json
puts Schema.execute('{n1:name, n2:name}').to_json
実行結果
$ bundle exec ruby test1.rb 
{"data":{"name":"Nanashi"}}
{"data":{"n1":"Nanashi","n2":"Nanashi"}}
$

子クラスを定義する方法

Nullableの設定有無などの差異はあるが、以下は基本的にtest1.rbと同じような内容である。

test2.rb
require 'graphql'

class QueryType < GraphQL::Schema::Object
  description 'query'
  field :name, String, null:true, description:'the name of this thing'
  def name()
    'Nanashi'
  end
end

class Schema < GraphQL::Schema
  query QueryType
end

puts Schema.execute('{name}').to_json
puts Schema.execute('{n1:name, n2:name}').to_json

実行結果はtest1.rbと完全に同じである。


Queryを使ったサンプル

動的にクエリ結果を変更する例

test3.rb
require 'graphql'

$id = 0

class QueryType < GraphQL::Schema::Object
  field :id, ID, null:true, description:'id'
  field :name, String, null:true, description:'name'
  def id()
    retval = $id
    $id += 1
    return retval
  end
  def name()
    'Nanashi'
  end
end

class Schema < GraphQL::Schema
  query QueryType
end

puts Schema.execute('{id}').to_json
puts Schema.execute('{id,name}').to_json
puts Schema.execute('{id1:id, id2:id}').to_json
実行結果
$ bundle exec ruby test3.rb
{"data":{"id":"0"}}
{"data":{"id":"1","name":"Nanashi"}}
{"data":{"id1":"2","id2":"3"}}
$

引数ありクエリ

test4.rb
require 'graphql'

class QueryType < GraphQL::Schema::Object
  field :func, String, null:false do
    argument :id, Int, required: true
    argument :sex, String, required: false
    description 'the name of this thing'
  end
  def func(id:, sex:'male')
    "No. #{id}_sex:#{sex}"
  end
end

class Schema < GraphQL::Schema
  query QueryType
end

puts Schema.execute('{func(id:123)}').to_json
puts Schema.execute('{func(id:123, sex:"female")}').to_json
実行結果
$ bundle exec ruby test4.rb
{"data":{"func":"No. 123_sex:male"}}
{"data":{"func":"No. 123_sex:female"}}
$

型定義

型定義については本稿では省略する。
興味がある場合は公式ページの下記を参照。

Mutation

Rubyでは、Mutationを以下のように記載する。

test5.rb
require 'graphql'

class UpdateAddressMutation < GraphQL::Schema::Mutation
  null true

  argument :pcode, Int, required: false
  argument :address, String, required: false

  field :rc, String, null:false
  field :output, Int, null:false

  def resolve (pcode: nil, address: nil)
    str="pcode:#{pcode}, address:#{address}"
    puts str
    { rc:'OK', output:str }
  end
end

class MutationType < GraphQL::Schema::Object
  field :updateAddress, mutation: UpdateAddressMutation
end

class Schema < GraphQL::Schema
  mutation MutationType
end

puts Schema.execute('mutation { updateAddress(pcode:123456, address:"Fuga-shi, Hoge-ken"){ rc } }').to_json
実行結果
$ bundle exec test6.rb
pcode:123456, address:Fuga-shi, Hoge-ken
{"data":{"updateAddress":{"rc":"OK"}}}
$

Relay形式のMutation

Relayについての詳細は割愛するが、Relay準拠でMutationを使用する場合、
「GraphQL::Relay::Mutation」を使用する。

test6.rb
require 'graphql'

UpdateAddressMutation = GraphQL::Relay::Mutation.define do
  name 'UpdateAddress'
  description 'example'

  input_field :postal_code, !types.Int
  input_field :address, !types.String

  return_field :rc, !types.Int
  return_field :v, !types.String

  resolve ->(_obj, inputs, ctx) {
    { rc:200, v:"No.#{inputs[:postal_code]}"}
  }
end

class MutationType < GraphQL::Schema::Object
  field :updateAddress, field: UpdateAddressMutation.field
end

class Schema < GraphQL::Schema
  mutation MutationType
end

puts Schema.execute('mutation { updateAddress(input :{postal_code:123, address:"Hoge-Ken"}){ v } }').to_json
puts Schema.execute('mutation { updateAddress(input :{postal_code:123, address:"Hoge-Ken"}){ rc, v } }').to_json
puts Schema.execute('mutation { v1:updateAddress(input :{postal_code:123, address:"Hoge-Ken"}){ v }, v2:updateAddress(input :{postal_code:345, address:"Fuga-Ken"}){ rc } }').to_json
実行結果
$ bundle exec ruby test6.rb
{"data":{"updateAddress":{"v":"No.123"}}}
{"data":{"updateAddress":{"rc":200,"v":"No.123"}}}
{"data":{"v1":{"v":"No.123"},"v2":{"rc":200}}}
$

詳細は以下を参照。

ググった感じでは、Ruby+GraphQLの日本語記事は大体Relay形式だった。

その他

エラーを返却

エラーを返却する場合は以下のように例外を送出する。
以下はtest5.rbのresolve定義を変更したものである。

test7.rb
require 'graphql'

class UpdateAddressMutation < GraphQL::Schema::Mutation
  null true

  argument :pcode, Int, required: false
  argument :address, String, required: false

  field :rc, String, null:false
  field :output, Int, null:false

  def resolve(pcode: nil, address: nil)
    raise GraphQL::ExecutionError.new('Oh!')
  end
end

class MutationType < GraphQL::Schema::Object
  field :updateAddress, mutation: UpdateAddressMutation
end

class Schema < GraphQL::Schema
  mutation MutationType
end

puts Schema.execute('mutation { updateAddress(pcode:123456, address:"Fuga-shi, Hoge-ken"){ rc } }').to_json
実行結果
{"data":{"updateAddress":null},"errors":[{"message":"Oh!","locations":[{"line":1,"column":12}],"path":["updateAddress"]}]}

Sinatra + GraphQL

Sinatra+GraphQLを使ったサンプルは以下の通りである。

Gemfile
source "https://rubygems.org"

gem "sinatra"
gem "sinatra-contrib"
gem 'rack-contrib'
gem "graphql"
sinatra_graphql.rb
require 'graphql'
require 'sinatra'
require 'sinatra/json'
require 'rack/contrib'
use Rack::PostBodyContentTypeParser

$list = {}
$id = 0

def reqId()
  rc = $id
  $id += 1
  return rc
end

class QueryType < GraphQL::Schema::Object
  field :user, String, null:true do
    argument :id, Int, required:false
  end
  def user(id:)
    $list[id]
  end
end

class CreateMutation < GraphQL::Schema::Mutation
  argument :name, String, required: true

  field :id, Int, null:false
  field :rc, String, null:false

  def resolve (name:)
    i = reqId()
    $list[i] = name
    { rc:'OK', id:i }
  end
end

class MutationType < GraphQL::Schema::Object
  field :create, mutation: CreateMutation
end

class Schema < GraphQL::Schema
  query QueryType
  mutation MutationType
end

post '/graphql' do
  json Schema.execute(params[:query], variables: params[:variables]).to_json
end
  • サーバ側でアプリを実行する。
サーバ側_実行開始
$ bundle exec ruby sinatra_graphql.rb
[2019-05-20 18:23:08] INFO  WEBrick 1.3.1
[2019-05-20 18:23:08] INFO  ruby 2.3.3 (2016-11-21) [x86_64-linux-gnu]
== Sinatra (v2.0.5) has taken the stage on 4567 for development with backup from WEBrick
[2019-05-20 18:23:08] INFO  WEBrick::HTTPServer#start: pid=1361 port=4567
  • ID=0のユーザを問い合わせる(ユーザなし)。
実行結果1
$ curl -H 'Content-type: application/json' -X POST http://localhost:4567/graphql -d '{ "query" : "query { user(id:0)}" }'
"{\"data\":{\"user\":null}}"
  • ユーザを登録する。
実行結果2
$ curl -H 'Content-type: applicatalhost:4567/graphql -d '{ "query" : "mutation { create(name:'Ken'){id} }" }'
"{\"data\":{\"create\":{\"id\":0}}}"
  • ID=0のユーザを問い合わせる(ユーザあり)。
実行結果3
$ curl -H 'Content-type: application/json' -X POST http://localhost:4567/graphql -d '{ "query" : "query { user(id:0)}" }'
"{\"data\":{\"user\":\"Ken\"}}"
  • 2ユーザをまとめて登録する。
実行結果4
$ curl -H 'Content-type: application/json' -X POST http://localhost:4567/graphql -d '{ "query" : "mutation { mike:create(name:'Mike'){rc,id}, john:create(name:'John'){rc,id} }" }'
"{\"data\":{\"mike\":{\"rc\":\"OK\",\"id\":1},\"john\":{\"rc\":\"OK\",\"id\":2}}}"
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2