RailsでGraphQL APIからデータを取得・表示する


はじめに

初めまして、hinokageと申します。
Qiitaへの記事投稿初めて且つ、
プログラミングスクールを卒業したての知識で本記事を書きましたので
お手柔らかにお願いいたします。

経緯

「Annict」という、有志が開発しているアニメ関連の情報が網羅してあるDBのGraphQLAPIが公開されています。
Railsでアプリ制作をしている際、制作していたアプリ内でこれを利用するためRailsからGraphQLAPIを叩く方法を調べたところ、「Railsで制作したアプリをAPIにする方法としてGraphQLを使用する」という内容の記事は結構な数ヒットするものの、「Railsアプリ内から公開されているGraphQLAPIを叩いてデータ取得する」という内容の記事が全くヒットせず、実装に非常に苦しみました。
私と同じように「有志が制作したGraphQLAPIをRailsで利用したい!」という方に向けて少しでも役に立てるよう本記事を投稿いたします。

開発環境

  • Ruby(2.6.5)
  • Ruby on Rails(5.2.3)

導入

まず最初にGraphQLをrailsで使えるように
Ruby製のGraphQL Client 「graphql-client」を導入します。
(公式リファレンス:https://github.com/github/graphql-client

Gemfile
gem 'graphql-client'(追記)

gemfileに追記後、bundle install

bundle install

次に、使用したいAPIのアクセストークンの作成を行います。
ここでは例としてGithubのGraphQL APIを使用します。

GitHub APIのアクセストークンは
GitHubの自分のアイコンをクリック
Settings > Developer settings > Personal access tokens >Generate new token
から作成できます。
(ここでは詳しい作成方法は省略させていただきます。)

httpアダプタ、Schema、クライアント設定のためapplication.rbに以下を追記します。
(今回はgql_testというアプリ名で制作しています。)

config/application.rb
  require 'rails/all'
  require "graphql/client" #(追記)
  require "graphql/client/http" #(追記)

module GqlTest(ご自身のアプリ名)
  ...

  AUTH_HEADER = "Bearer xxx"
 # xxx=作成した際に表示されたアクセストークン
  HTTP = GraphQL::Client::HTTP.new("https://api.github.com/graphql") do 
  #上記には、接続したいAPIのエンドポイントURLを記入
    def headers(context)
      { "Authorization": AUTH_HEADER }
    end
  end
  Schema = GraphQL::Client.load_schema(HTTP)
  # 上記を使って API サーバーから GraphQL Schema 情報を取得
  Client = GraphQL::Client.new(schema: Schema, execute: HTTP)
  # 上記を使ってクライアントを作成
end

これで大まかな設定は完了です。

クエリ作成

次に取得したいデータのクエリを書きます。
ちなみに

クエリとは
データベース管理システムに対する問合せ(処理要求)のこと。

簡単に書くと
「欲しいデータを取得するためのコード」
です。

GraphQLのクエリの書き方は
https://employment.en-japan.com/engineerhub/entry/2018/12/26/103000
上記のサイトが参考になると思います。もし自分でクエリを書きたい場合はご活用ください。

今回はGitHubAPIを使用するのでクエリは以下のようなものを使用しました。
自分のリポジトリの新しいものから5つ分取得するクエリとなっています。

query {
    viewer {
      repositories(last:5) {
        edges {
          node {
            name
          }
        }
      }
    }
  }

また、自分でクエリを書きたい場合はAPIに応じたSchemaを知る必要があります。
ちなみに

Schemaとは
GraphQL APIの仕様を表現するものです。スキーマ定義言語(SDL(Schema Definition Language))を使って表現します。

先程のクエリを例にすると「viewer」「repositories」「node」「name」など全てSchemaです。
API作成時、作成者がデータを取り出す方法をSchemaとして文字に定義しており、
それをAPI使用者がSchemaを用いてデータを取得するために書くコードがクエリだと考えてもらえば
わかりやすいかもしれません。

「クエリがどういうものなのか」を分かりやすく伝えるための自分の中で辿り着いた例えが
辞典でいう目次みたいなものがSchemaです。

例えば、辞典でとある動物を調べたい、
しかしその動物の〇〇類〇〇科〇〇目などは分かるが名前はわからないみたいな時に
目次のページを見ると思います。この目次のページがSchemaです。
目次を見て〇〇類のページを開きます。
またその辞典には〇〇類の最初のページに「〇〇科一覧」みたいな目次があるかもしれません。
これもSchemaです。

つまりはSchemaは
目的のデータに辿り着きたい時の検索手段
みたいなものだと考えれば分かりやすいかもしれません。
その検索手段をAPI作成者側が文字に定義しているのが
schemaだと私は解釈して使用しています。
(間違っていたら申し訳ないので、ぜひ一度GraphQL Schemaとググって調べてください)

さて、Schemaの種類や使い方については
各APIの公式リファレンスを読めば書いてあると思いますが
私は「GraphiQL」というアプリケーションを使用して調べることが多いです。

GraphiQLはSchemaの確認や検索ができるほか、
APIへの接続やクエリ、返ってくるデータの確認ができるため非常に便利なツールとなっています。

このアプリはブラウザ上で使用することもできますが
オススメはスタンドアローンで使用できるダウンロード版です。
https://electronjs.org/apps/graphiql

少し話が逸れてしまいました本題に戻ります。

実装

先ほどのクエリをcontorollerに追加します。
その際、下記のように記述します。

controllers/blogs_controller.rb
class BlogsController < ApplicationController
  Query = GqlTest::Client.parse <<-GRAPHQL
  query {
    viewer {
      repositories(last:5) {
        edges {
          node {
            name
          }
        }
      }
    }
  }
    GRAPHQL

  def index
    @works = result
  end

  private
  def result
    response = GqlTest::Client.query(Query)
  end
end

privateメソッドの下層に
resultというメソッドを用意して
クエリの結果データをrailsで使用できるように加工しています。
そして、使用したいアクション内でresultを用いることで、
結果データを使用することができます。

@worksというインスタンス変数にAPIからの結果データを格納したので
これをviewで使用してみます。最新5件のリポジトリ名が表示されればOKです。

index.html.erb
<h2>私が最近作成したリポジトリは以下の5つです</h2>
<%= @works%>

あれ?オブジェクトしか表示されない…?

実はこの時点では取り出したいデータを指定していなかったので
オブジェクトが表示されるだけになっています。
これは以下のようにすると表示することができます。

controllers/blogs_controller.rb
  def index
    @works = result.data.viewer.repositories.edges
  end

private
  def result
    response = GqlTest::Client.query(Query)
  end

これだけだと、変数内に配列のようにリポジトリ名が複数入っている状態なので、
下記のように表示されてしまいます。

そのため、viewファイルの方でeachメソッドを使い1つずつ表示するようにします。

index.html.erb
<% @works.each do |work|%>
  <p><%= work.node.name%></p>
<% end %>

こうすることで表示することができました。

…しかしこれだと、追記したコードの意味が分からないですよね。

コードの解説

実は取得したデータだけでは目的の
「自分の最新リポジトリ5件のタイトル」というデータ以外のものも含まれるので、
クエリで書いたように何の情報が欲しいのかを記述する必要があります。

つまり、「自分の最新リポジトリ5件のタイトルを取得する」というクエリを書いても、
結果として返ってくるデータは「自分の最新リポジトリ5件のタイトル」だけでは無いということです。

GraphiQLを使って、先程と同じクエリを送った時にどのようなデータが返ってくるのか見ると非常にわかりやすいです。

画面左側のコードが先程と同じ「最新リポジトリ5件のタイトルを取得する」クエリです
画面右側はそのクエリをAPIに送った時に返ってきた結果データです。

結果データを見るとクエリで書いたSchemaに値が入って返ってきてるのが分かると思います。
また、コードの書かれ方がハッシュと同じような形でkeyとvalueが含まれているのが分かると思います。

この結果データがハッシュであった場合、リポジトリのタイトルであるnameの値を取り出そうとすると
resultという変数に結果データが格納されていた時

result[:data][:viewer][:repositories][:edges].each do |work|
 work[:node][:name]
end

このようなコードを書くと思います。
結果データはハッシュでは無いため、このような取り出し方はできないのですが
(ちなみに結果データはJSON形式で返ってきているようです。)
公式リファレンスを読むとハッシュでkeyになっていたものをメソッドのように扱うことで
上記で書いたハッシュの展開と似たような方法で取り出すことができるようです。
つまり、コードは

controllers/blogs_controller.rb
 def index
   @works = result.data.viewer.repositories.edges
 end

private
 def result
   response = GqlTest::Client.query(Query)
 end
index.html.erb
<% @works.each do |work|%>
  <p><%= work.node.name%></p>
<% end %>

と書くことで目的のデータが表示できるようになります。

(夜中に殴り書きしたので色々誤字脱字あるかもしれません…)