本稿はRubyでGraphQLのバックエンド側の処理を書いた際(というか調べた際)のメモ。
基本的にググったり、公式ページを見たりした。
(結果から言うと、公式の情報だけで十分だった気もする……)
尚、本稿の前提は以下の通り。
- Rubyが分かる。
- Bundlerをインストール済み (なくても意味は分かれば十分)。
- GraphQLが何者を知ってる。
本稿の各サンプルの実行方法
最初に本稿の各サンプルコードの実行方法を示す。
- 以下のような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メソッドを定義する方法
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と同じような内容である。
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を使ったサンプル
動的にクエリ結果を変更する例
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"}}
$
引数ありクエリ
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を以下のように記載する。
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」を使用する。
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定義を変更したものである。
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を使ったサンプルは以下の通りである。
source "https://rubygems.org"
gem "sinatra"
gem "sinatra-contrib"
gem 'rack-contrib'
gem "graphql"
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のユーザを問い合わせる(ユーザなし)。
$ curl -H 'Content-type: application/json' -X POST http://localhost:4567/graphql -d '{ "query" : "query { user(id:0)}" }'
"{\"data\":{\"user\":null}}"
- ユーザを登録する。
$ curl -H 'Content-type: applicatalhost:4567/graphql -d '{ "query" : "mutation { create(name:'Ken'){id} }" }'
"{\"data\":{\"create\":{\"id\":0}}}"
- ID=0のユーザを問い合わせる(ユーザあり)。
$ curl -H 'Content-type: application/json' -X POST http://localhost:4567/graphql -d '{ "query" : "query { user(id:0)}" }'
"{\"data\":{\"user\":\"Ken\"}}"
- 2ユーザをまとめて登録する。
$ 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}}}"