2019年6月10日月曜日

Ruby (Sinatra) で GraphQL 入門

概要

前回 Sinatra + ActiveRecord を使った Web アプリを作成してみました
ついでなのでアプリを GraphQL 化してみました
GraphQL は REST や RPC に変わる API で JSON のようなクエリを使ってデータの CRUD が行えます
1 つのリクエストで複数のデータを取得するバッチリクエストが送れます
また RPC のように一つのパス (POST /graphql) に対してアクセスします

環境

  • macOS 10.14.5
  • sqlite3 3.24.0
  • Ruby 2.6.2p47
    • activerecord 5.2.3
    • sqlite3 1.4.1
    • 1.9.6

事前準備

必要なアプリなどは前回のものをそのまま使います
gem のインストールや app.rb の作成は済ませておいてください

graphql-ruby の追加

Ruby で GraphQL を実装するためのライブラリがあるので使わせていただきます

  • vim Gemfile
gem "graphql"
gem "rack-contrib"
  • bundle install --path vendor

上記 2 つを追記したらインストールしましょう

タイプの追加

まずはタイプを追加します
GraphQL の重要な要素として「タイプ」「クエリ」「スキーマ」があります
タイプはデータの構造を定義します

  • mkdir -p graphql/types
  • vim graphql/types/base_object.rb
require 'graphql'

module Types
  class BaseObject < GraphQL::Schema::Object
  end
end
  • vim graphql/types/user.rb
require './graphql/types/base_object.rb'

class Types::User < Types::BaseObject
  description 'Resembles a User Object Type'

  field :id, ID, null: false
  field :name, String, null: false
end

BaseObject を使って新しいタイプを追加します
今回は name と id という field を持つ User タイプを追加しました
前回 sqlite3 に同じようなテーブルを作成しましたが基本的にはそれに合わせましょう

クエリの追加

クエリはリクエストに含まれるフィールドの情報を定義します
リクエストに users というフィールドが含まれている場合に sqlite3 からデータを取得して先程のタイプにバインドしてデータを返却します

  • vim graphql/query.rb
require 'graphql'
require './graphql/types/user.rb'
require './models/user.rb'

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

  field :users, [Types::User], null: false do
    description 'Get all users'
  end

  def users
    User.all
  end
end

スキーマの追加

スキーマでは定義したクエリを登録します
今回は 1 つしかクエリを定義してないのでそれだけ登録します

  • vim graphql/schema.rb
require 'graphql'
require './graphql/query.rb'

class TestDBSchema < GraphQL::Schema
  query QueryType
end

アプリ修正

ではアプリを修正しましょう
アプリではスキーマを使います

  • vim app.rb
require 'sinatra'
require 'rack/contrib'
require 'sinatra/json'
require "sinatra/activerecord"
require './graphql/schema.rb'

class DBTestApp < Sinatra::Base
  register Sinatra::ActiveRecordExtension
  set :database, {:adapter => "sqlite3", :database => "usersdb.sqlite3"}
  use Rack::PostBodyContentTypeParser

  post '/graphql' do
    ret = TestDBSchema.execute(
      params['query'],
      variables: params['variables'],
      context: { current_user: nil },
    )
    json ret
  end
end

スキーマには execute というメソッドが実装されておりこれに query の内容を投げます
それ以外に variablescontext が投げれますが今回は特に使っていません

動作確認

アプリを起動して動作確認しましょう

  • bundle exec rackup config.ru

curl でリクエストを投げみます

  • curl -X POST -H 'Content-Type: application/json' -d '{"query":"{users{name}}"}' localhost:9292/graphql

=> {"data":{"users":[{"name":"hawk"},{"name":"snowlog"}]}}

こんな感じで users テーブルからデータが取得できると思います
GraphQL の場合 data というフィールド配下に結果が入ってきます

おまけ: Argument

例えば id:1 のユーザだけ取得したい場合には GraphQL では以下のようにリクエストします

  • curl -X POST -H 'Content-Type: application/json' -d '{"query":"{users(id:1){id name}}"}' localhost:9292/graphql

これに対応するにはクエリを以下のように修正します

  • vim graphql/query.rb
require 'graphql'
require './graphql/types/user.rb'
require './models/user.rb'

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

  field :users, [Types::User], null: false do
    argument :id, Integer, required: false
    description 'Get all users'
  end

  def users(**arg)
    if arg.empty?
      User.all
    else
      [User.find(arg[:id])]
    end
  end
end

ポイントは argument を使って指定可能な引数を定義する点です
あとは呼び出されるメソッド (users) に可変長のハッシュ引数を受け取れるようにします
こうすることで引数なしと引数あり両方のクエリに対応できます
あとは sqlite3 に発行するとクエリを allfind に分けます
また返り値は配列である必要があるので find の場合に配列にします

最後に

Sinatra で GraphQL に入門してみました
タイプ、クエリ、スキーマの基本的な使い方は抑えておきましょう
このあとはデータを保存したり更新するための mutation などを勉強すると良いかなと思います

参考サイト

0 件のコメント:

コメントを投稿