GitHub API を Ruby で使うとき、多くの人が octokit.rb を選びます。REST API のドキュメントは豊富ですが、GraphQL API の使い方はあまり知られていません。この記事では、octokit.rb で GraphQL API を使う方法を実例とともに解説します。
client.post '/graphql' で GraphQL クエリを実行できますSawyer::Resource オブジェクトで返されるため、メソッド形式またはシンボルキーでアクセスしますREST API では、複数のファイルの情報を取得するのに N 回のリクエストが必要です。
# REST API: 100ファイルなら100回のリクエスト
paths.each do |path|
client.commits(repo, branch, path: path, per_page: 1)
end
GraphQL API を使えば、1回のリクエストで複数の情報を取得できます。
# GraphQL API: 100ファイルでも1〜2回のリクエスト
client.post '/graphql', { query: batch_query }.to_json
GitHub API のレート制限は 1 時間あたり 5,000 リクエストです。大量のファイルを扱うアプリケーションでは、GraphQL による一括取得が効果的です。
octokit.rb は主に REST API クライアントとして設計されていますが、post メソッドで GraphQL エンドポイントにアクセスできます。最初に、シンプルな例から始めましょう。
require 'octokit'
client = Octokit::Client.new(access_token: ENV['GITHUB_TOKEN'])
query = <<~GRAPHQL
query {
viewer {
login
name
}
}
GRAPHQL
response = client.post '/graphql', { query: query }.to_json
puts response.data.viewer.login
octokit.rb のレスポンスは Sawyer::Resource オブジェクトで返されます。Hash のようにアクセスできますが、複数のアクセス方法があります。状況に応じて使い分けましょう。
response = client.post '/graphql', { query: query }.to_json
# メソッド形式でアクセス
response.data.viewer.login
# シンボルキーでアクセス
response[:data][:viewer][:login]
# dig でネストしたデータにアクセス
response.dig(:data, :viewer, :login)
Sawyer::Resource を通常の Hash に変換したい場合は、ネストされたオブジェクトも含めて再帰的に変換する必要があります。以下のヘルパー関数を使うと便利です。
def sawyer_to_hash(obj)
case obj
when Sawyer::Resource
obj.to_h.transform_values { |v| sawyer_to_hash(v) }
when Array
obj.map { |v| sawyer_to_hash(v) }
else
obj
end
end
hash = sawyer_to_hash(response)
GraphQL API の利点を活かした実践的な例として、GitHub リポジトリ内の複数ファイルの最終コミット日時を一度に取得する方法を紹介します。
GraphQL のエイリアス機能を活用すれば、複数のクエリを 1 つにまとめられます。これにより、N 回のリクエストが不要になります。
def build_batch_query(owner, repo, branch, paths)
file_queries = paths.each_with_index.map do |path, index|
<<~GRAPHQL
file#{index}: object(expression: "#{branch}") {
... on Commit {
history(path: "#{path}", first: 1) {
nodes {
committedDate
}
}
}
}
GRAPHQL
end.join("\n")
<<~GRAPHQL
query {
repository(owner: "#{owner}", name: "#{repo}") {
#{file_queries}
}
}
GRAPHQL
end
paths = ['README.md', 'src/index.js', 'package.json']
query = build_batch_query('octokit', 'octokit.rb', 'main', paths)
response = client.post '/graphql', { query: query }.to_json
repository = response.data.repository
paths.each_with_index do |path, index|
key = "file#{index}"
committed_date = repository[key]&.history&.nodes&.first&.committedDate
puts "#{path}: #{committed_date}"
end
実行すると、以下のような出力が得られます。
README.md: 2024-01-15T10:30:00Z
src/index.js: 2024-01-10T14:20:00Z
package.json: 2024-01-12T09:15:00ZGraphQL API にも制限があります。1 回のクエリで取得できるノード数には上限があるため、大量のファイルを扱う場合はバッチに分割して処理する必要があります。
BATCH_SIZE = 50
def fetch_all_commit_times(client, owner, repo, branch, paths)
results = {}
paths.each_slice(BATCH_SIZE) do |batch|
query = build_batch_query(owner, repo, branch, batch)
response = client.post '/graphql', { query: query }.to_json
batch.each_with_index do |path, index|
key = "file#{index}"
committed_date = response.dig(:data, :repository, key, :history, :nodes, 0, :committedDate)
results[path] = committed_date
end
end
results
end
GraphQL クエリに外部から値を渡す場合、変数を使うと安全で読みやすいコードになります。以下のようにクエリで変数を定義し、実行時に値を渡します。
query = <<~GRAPHQL
query($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
description
stargazerCount
}
}
GRAPHQL
variables = { owner: 'octokit', name: 'octokit.rb' }
response = client.post '/graphql', { query: query, variables: variables }.to_json
puts response.data.repository.stargazerCount
REST API と異なり、GraphQL はステータスコード 200 を返しながらエラーを返すことがあります。エラーの確認は、レスポンスの errors フィールドをチェックして行います。
response = client.post '/graphql', { query: query }.to_json
if response.errors
response.errors.each do |error|
puts "Error: #{error.message}"
end
else
# 正常処理
end
GraphQL API のレート制限は REST API とは異なり、ポイント制です。複雑なクエリほどコストが高くなるため、rateLimit クエリを使ってコストと残りポイントを確認しましょう。
query = <<~GRAPHQL
query {
rateLimit {
cost
remaining
resetAt
}
viewer {
login
}
}
GRAPHQL
response = client.post '/graphql', { query: query }.to_json
rate_limit = response.data.rateLimit
puts "このクエリのコスト: #{rate_limit.cost}"
puts "残りポイント: #{rate_limit.remaining}"
puts "リセット時刻: #{rate_limit.resetAt}"
octokit.rb で GraphQL API を使う方法を紹介しました。REST API で N 回のリクエストが必要だった処理を、GraphQL で 1〜2 回に削減できます。
ポイントをまとめると、GraphQL API は複数のファイル情報を効率的に取得でき、レート制限が厳しい場面で活躍します。エイリアスを使った一括クエリ実行やバッチ分割による大量データ処理も、octokit.rb で簡単に実装できます。レート制限が気になる場面では、ぜひ GraphQL API を検討してみてください。