Sakiのプログラミング学習ブログ

プログラミングについて学んだことや、学習の振返りを書いています。

【octokit.rb】RubyでGitHubAPI(REST)にリクエストする時に便利なラッパーライブラリ、octokit.rbの使い方

はじめに

フィヨルドブートキャンプ(以下、FBC)の「自分で考えたサービスをリリースする」という最終課題で、GitHubREST API(REST)を利用した。(※近日リリース予定)

Ruby on Railsを使って開発したのだが、RubyでGitHubAPIをたたく際に、GitHubが用意してくれている公式のラッパーライブラリ、octokit.rbを使ったらとても便利だった。

github.com


サービス開発を始めた当初、「octokit.rbというラッパーライブラリがあるよ」、とGitHubの公式ドキュメントで紹介されていて存在は知っていたが、APIをたたくのが初めてなこともあり、使うことでどんなメリットがあるか分からなかったので、最初は素のRubyAPIにリクエストする処理を書いていた。
素のRubyで処理を書いてAPIの操作に慣れてきたところで、ラッパーライブラリを使ったところ、「え!とても便利✨」と感動したので、

  • そもそもラッパーライブラリとは
  • 素のRubyでGitHubAPI(REST)をたたくと、どんなコードになるのか
  • 素のRubyでたたいた時の不便な点→octokit.rbを使えば解決
  • octokit.rbを使った認証方法
  • octokit.rbを使ったGitHubAPI(REST)のたたき方

を中心に、学んだことをまとめた。

目次

そもそもラッパーライブラリとは

ラッパーとは、

ある開発環境向けに提供されたプログラムなどを別の環境で利用できるようにするもの。
(ラッパーとは - 意味をわかりやすく - IT用語辞典 e-Words より)

GitHubAPIには、使用方法を拡張したり簡略化するためのライブラリがたくさんある。
以下の公式ドキュメントで、どのようなラッパーライブラリがあるか、非公式なものも含めて紹介されている。

Rubyには、GitHub公式のoctokit.rbがある。今回はこれを使った。

素のRubyでGitHubAPI(REST)をたたくと、どんなコードになるのか

ラッパーライブラリの便利さを伝えるために、まず素のRubyでGitHubAPIをたたく場合、どのようなコードを書く必要があるか説明していく。

RubyAPIをたたくには、以下のライブラリを使う。

例えば、「とあるリポジトリのうち、指定したidのissueを取得したい」とする。 素のRubyでリクエストする場合、以下のように書く。

require "net/http" # Net::HTTP.getを使うためにrequireする必要がある
require "uri" # parseメソッドを使うためにrequireする必要がある


# リクエストしたいAPIのエンドポイントのURIをパースする
uri = URI.parse("https://api.github.com/repos/fjordllc/bootcamp/issues/1")
json = Net::HTTP.get(uri)

これで、変数jsonの中にfjordllc/bootcampリポジトリのissueが1件、JSON形式で入っている。

変数jsonの中身(非常に長いので折りたたんだ)

"{\"url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1\",\"repository_url\":\"https://api.github.com/repos/fjordllc/bootcamp\",\"labels_url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1/labels{/name}\",\"comments_url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1/comments\",\"events_url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1/events\",\"html_url\":\"https://github.com/fjordllc/bootcamp/pull/1\",\"id\":11961549,\"node_id\":\"MDExOlB1bGxSZXF1ZXN0NDU4NjQxMA==\",\"number\":1,\"title\":\"Update rails 4.0.0.beta\",\"user\":{\"login\":\"hrysd\",\"id\":1663465,\"node_id\":\"MDQ6VXNlcjE2NjM0NjU=\",\"avatar_url\":\"https://avatars.githubusercontent.com/u/1663465?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/hrysd\",\"html_url\":\"https://github.com/hrysd\",\"followers_url\":\"https://api.github.com/users/hrysd/followers\",\"following_url\":\"https://api.github.com/users/hrysd/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/hrysd/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/hrysd/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/hrysd/subscriptions\",\"organizations_url\":\"https://api.github.com/users/hrysd/orgs\",\"repos_url\":\"https://api.github.com/users/hrysd/repos\",\"events_url\":\"https://api.github.com/users/hrysd/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/hrysd/received_events\",\"type\":\"User\",\"site_admin\":false},\"labels\":[],\"state\":\"closed\",\"locked\":false,\"assignee\":null,\"assignees\":[],\"milestone\":null,\"comments\":0,\"created_at\":\"2013-03-13T06:30:12Z\",\"updated_at\":\"2013-03-13T06:39:10Z\",\"closed_at\":\"2013-03-13T06:39:10Z\",\"author_association\":\"CONTRIBUTOR\",\"active_lock_reason\":null,\"draft\":false,\"pull_request\":{\"url\":\"https://api.github.com/repos/fjordllc/bootcamp/pulls/1\",\"html_url\":\"https://github.com/fjordllc/bootcamp/pull/1\",\"diff_url\":\"https://github.com/fjordllc/bootcamp/pull/1.diff\",\"patch_url\":\"https://github.com/fjordllc/bootcamp/pull/1.patch\",\"merged_at\":\"2013-03-13T06:39:10Z\"},\"body\":\"\",\"closed_by\":{\"login\":\"komagata\",\"id\":16577,\"node_id\":\"MDQ6VXNlcjE2NTc3\",\"avatar_url\":\"https://avatars.githubusercontent.com/u/16577?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/komagata\",\"html_url\":\"https://github.com/komagata\",\"followers_url\":\"https://api.github.com/users/komagata/followers\",\"following_url\":\"https://api.github.com/users/komagata/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/komagata/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/komagata/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/komagata/subscriptions\",\"organizations_url\":\"https://api.github.com/users/komagata/orgs\",\"repos_url\":\"https://api.github.com/users/komagata/repos\",\"events_url\":\"https://api.github.com/users/komagata/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/komagata/received_events\",\"type\":\"User\",\"site_admin\":false},\"reactions\":{\"url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1/reactions\",\"total_count\":0,\"+1\":0,\"-1\":0,\"laugh\":0,\"hooray\":0,\"confused\":0,\"heart\":0,\"rocket\":0,\"eyes\":0},\"timeline_url\":\"https://api.github.com/repos/fjordllc/bootcamp/issues/1/timeline\",\"performed_via_github_app\":null,\"state_reason\":null}"


JSON形式になっているので、返ってきたデータをRubyで操作したい場合、jsonライブラリのJSON.parseを使って、Ruby オブジェクトに変換する必要がある。

require "net/http"
require "uri"
# 追記
require "json" #jsonライブラリを使うためにrequireする必要がある


uri = URI.parse("https://api.github.com/repos/fjordllc/bootcamp/issues/1")
json = Net::HTTP.get(uri)

# Rubyオブジェクトに変換したいJSONデータを引数に渡す
issue = JSON.parse(json)

このように、引数にJSONデータを渡すことで、Rubyオブジェクトに変換してくれる。 issueの中身は以下のようになる。(見やすいようにppメソッドで出力している。)


変数issueの中身(非常に長いので折りたたんだ)

{"url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1",
 "repository_url"=>"https://api.github.com/repos/fjordllc/bootcamp",
 "labels_url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/labels{/name}",
 "comments_url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/comments",
 "events_url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/events",
 "html_url"=>"https://github.com/fjordllc/bootcamp/pull/1",
 "id"=>11961549,
 "node_id"=>"MDExOlB1bGxSZXF1ZXN0NDU4NjQxMA==",
 "number"=>1,
 "title"=>"Update rails 4.0.0.beta",
 "user"=>
  {"login"=>"hrysd",
   "id"=>1663465,
   "node_id"=>"MDQ6VXNlcjE2NjM0NjU=",
   "avatar_url"=>"https://avatars.githubusercontent.com/u/1663465?v=4",
   "gravatar_id"=>"",
   "url"=>"https://api.github.com/users/hrysd",
   "html_url"=>"https://github.com/hrysd",
   "followers_url"=>"https://api.github.com/users/hrysd/followers",
   "following_url"=>"https://api.github.com/users/hrysd/following{/other_user}",
   "gists_url"=>"https://api.github.com/users/hrysd/gists{/gist_id}",
   "starred_url"=>"https://api.github.com/users/hrysd/starred{/owner}{/repo}",
   "subscriptions_url"=>"https://api.github.com/users/hrysd/subscriptions",
   "organizations_url"=>"https://api.github.com/users/hrysd/orgs",
   "repos_url"=>"https://api.github.com/users/hrysd/repos",
   "events_url"=>"https://api.github.com/users/hrysd/events{/privacy}",
   "received_events_url"=>"https://api.github.com/users/hrysd/received_events",
   "type"=>"User",
   "site_admin"=>false},
 "labels"=>[],
 "state"=>"closed",
 "locked"=>false,
 "assignee"=>nil,
 "assignees"=>[],
 "milestone"=>nil,
 "comments"=>0,
 "created_at"=>"2013-03-13T06:30:12Z",
 "updated_at"=>"2013-03-13T06:39:10Z",
 "closed_at"=>"2013-03-13T06:39:10Z",
 "author_association"=>"CONTRIBUTOR",
 "active_lock_reason"=>nil,
 "draft"=>false,
 "pull_request"=>
  {"url"=>"https://api.github.com/repos/fjordllc/bootcamp/pulls/1",
   "html_url"=>"https://github.com/fjordllc/bootcamp/pull/1",
   "diff_url"=>"https://github.com/fjordllc/bootcamp/pull/1.diff",
   "patch_url"=>"https://github.com/fjordllc/bootcamp/pull/1.patch",
   "merged_at"=>"2013-03-13T06:39:10Z"},
 "body"=>"",
 "closed_by"=>
  {"login"=>"komagata",
   "id"=>16577,
   "node_id"=>"MDQ6VXNlcjE2NTc3",
   "avatar_url"=>"https://avatars.githubusercontent.com/u/16577?v=4",
   "gravatar_id"=>"",
   "url"=>"https://api.github.com/users/komagata",
   "html_url"=>"https://github.com/komagata",
   "followers_url"=>"https://api.github.com/users/komagata/followers",
   "following_url"=>"https://api.github.com/users/komagata/following{/other_user}",
   "gists_url"=>"https://api.github.com/users/komagata/gists{/gist_id}",
   "starred_url"=>"https://api.github.com/users/komagata/starred{/owner}{/repo}",
   "subscriptions_url"=>"https://api.github.com/users/komagata/subscriptions",
   "organizations_url"=>"https://api.github.com/users/komagata/orgs",
   "repos_url"=>"https://api.github.com/users/komagata/repos",
   "events_url"=>"https://api.github.com/users/komagata/events{/privacy}",
   "received_events_url"=>"https://api.github.com/users/komagata/received_events",
   "type"=>"User",
   "site_admin"=>false},
 "reactions"=>
  {"url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/reactions",
   "total_count"=>0,
   "+1"=>0,
   "-1"=>0,
   "laugh"=>0,
   "hooray"=>0,
   "confused"=>0,
   "heart"=>0,
   "rocket"=>0,
   "eyes"=>0},
 "timeline_url"=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/timeline",
 "performed_via_github_app"=>nil,
 "state_reason"=>nil}


このようにRubyのハッシュに変換されるので、Hashクラスのメソッド等を使って、返ってきたデータを操作することができる。 しかし、このコードでは2つの不便な点がある。

素のRubyで書いた時の不便な点

1. 認証を行っていないので、1時間のリクエスト回数が60回までになっている

上記のコードだと、GitHubに認証していない状態でREST APIにリクエストしているので、1時間あたりのリクエスト回数が60回までしか許可されていない。詳細は以下のドキュメントに載っている。

Octokit.rbを使えば簡単に認証の処理が書けるので、後で認証方法を説明する。

2. 返ってきたデータをいちいちJSON.parseしないとRubyでデータを処理できない

上記のコードだと、

  • APIリクエストしたいエンドポイントのURLをパースする
  • 返ってきたJSONデータをRubyで操作したい場合は、JSON.parseRubyオブジェクトに変換する

といった処理を必ず書かないといけない。
Octokit.rbを使えば、これらの処理を自分で書く必要がない。具体的な使い方は後で説明する。

Octokit.rbの使い方

インストール

gem installコマンドで、

$ gem install octokit

を実行するか、

Gemfileに以下を追記して、

gem "octokit", "~> 5.0"

追記後bundle installを実行する。

※さらに、Ruby on Railsではなく、RubyファイルでOctokit.rbを使いたい場合は、ファイルの一行目にrequire 'octokit'を追加する。

(octokit/octokit.rb - installationより)

認証方法

パーソナルアクセストークンを使う場合

require 'octokit'

client = Octokit::Client.new(:access_token => '<取得したパーソナルアクセストークン>')

userメソッドを使うと、現在認証しているユーザーの情報が返ってくる。

p client.user

#=>
{:login=>"Saki-htr",
 :id=>58052292,
 :node_id=>"MDQ6VXNlcjU4MDUyMjky",
 :avatar_url=>"https://avatars.githubusercontent.com/u/58052292?v=4",
 :gravatar_id=>"",
 :url=>"https://api.github.com/users/Saki-htr",
 :html_url=>"https://github.com/Saki-htr",
 :followers_url=>"https://api.github.com/users/Saki-htr/followers",
 :following_url=>
  "https://api.github.com/users/Saki-htr/following{/other_user}",
 :gists_url=>"https://api.github.com/users/Saki-htr/gists{/gist_id}",
 :starred_url=>"https://api.github.com/users/Saki-htr/starred{/owner}{/repo}",
 :subscriptions_url=>"https://api.github.com/users/Saki-htr/subscriptions",
 :organizations_url=>"https://api.github.com/users/Saki-htr/orgs",
 :repos_url=>"https://api.github.com/users/Saki-htr/repos",
 :events_url=>"https://api.github.com/users/Saki-htr/events{/privacy}",
 :received_events_url=>"https://api.github.com/users/Saki-htr/received_events",
 :type=>"User",
 :site_admin=>false,
 :name=>"服部紗希",
 :company=>nil,
 :blog=>"https://saki-htr.hatenablog.com/",
 :location=>"Tokyo Japan",
 :email=>nil,
 :hireable=>nil,
 :bio=>nil,
 :twitter_username=>nil,
 :public_repos=>33,
 :public_gists=>1,
 :followers=>1,
 :following=>0,
 :created_at=>2019-11-21 21:35:26 UTC,
 :updated_at=>2022-07-25 12:05:10 UTC,
 :private_gists=>16,
 :total_private_repos=>1,
 :owned_private_repos=>1,
 :disk_usage=>6912,
 :collaborators=>0,
 :two_factor_authentication=>true,
 :plan=>
  {:name=>"free", :space=>976562499, :collaborators=>0, :private_repos=>10000}}

これで認証は完了している。

ヘッダー情報の取得

リクエスト回数の制限が増えているか確認したい場合、ヘッダー情報を取得する。 取得するには、last_responseメソッドとheadersメソッドを使う。(octokit/octokit.rb - HTTP 応答へのアクセス より)

require 'octokit'

client = Octokit::Client.new(:access_token => '<取得したパーソナルアクセストークン>')
client.user #ヘッダー情報を見るために何かしらのAPIリクエストを行う必要があるので、ここでリクエストしている

pp client.last_response.headers

#=>
{"server"=>"GitHub.com",
 "date"=>"Thu, 10 Nov 2022 06:41:12 GMT",
 "content-type"=>"application/json; charset=utf-8",
 "transfer-encoding"=>"chunked",
 "cache-control"=>"private, max-age=60, s-maxage=60",
 "vary"=>"Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With",
 "etag"=>"W/\"cc2ff6079e9d00572f10fdd5af28e5d360d38cb29e52ded493a68d130e011c26\"",
 "last-modified"=>"Mon, 25 Jul 2022 12:05:10 GMT",
 "x-oauth-scopes"=>"read:enterprise, read:public_key, read:repo_hook, read:user, user:email",
 "x-accepted-oauth-scopes"=>"",
 "github-authentication-token-expiration"=>"2023-05-12 15:00:00 UTC",
 "x-github-media-type"=>"github.v3; format=json",
 "x-ratelimit-limit"=>"5000",
 "x-ratelimit-remaining"=>"4989",
 "x-ratelimit-reset"=>"1668064617",
 "x-ratelimit-used"=>"11",
 "x-ratelimit-resource"=>"core",
 "access-control-expose-headers"=>
  "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
 "access-control-allow-origin"=>"*",
 "strict-transport-security"=>"max-age=31536000; includeSubdomains; preload",
 "x-frame-options"=>"deny",
 "x-content-type-options"=>"nosniff",
 "x-xss-protection"=>"0",
 "referrer-policy"=>"origin-when-cross-origin, strict-origin-when-cross-origin",
 "content-security-policy"=>"default-src 'none'",
 "content-encoding"=>"gzip",
 "x-github-request-id"=>"CC82:5060:5E4B98:66FA98:636C9D08"}

client.last_response.headersは、最後のAPIリクエストに対するレスポンスの、ヘッダー情報を返している。
このうち、以下の部分がリクエスト回数の制限についての情報を示している。

# 1時間あたりに許可されるリクエストの最大数
 "x-ratelimit-limit"=>"5000",
# 現在のレート制限ウィンドウに残っているリクエストの数
 "x-ratelimit-remaining"=>"4989",
# 現在のレート制限ウィンドウがリセットされる時刻 (UTCエポック秒)
 "x-ratelimit-reset"=>"1668064617",
# 現在のレート制限ウィンドウで行ったリクエストの数
 "x-ratelimit-used"=>"11",

x-ratelimit-limitが5000になっているので、認証した上でAPIリクエストしていることが分かる。

OAuthトークンを使う場合

他のユーザーに代わってAPIを使用して個人情報を読み書きする必要があるアプリは、OAuthトークンを使用する必要がある。
APIリクエストさせたいアプリケーションをOAuthアプリとして登録する方法は、以下のドキュメントに詳しく載っている。

取得したら、APIリクエストさせるファイルに、以下のように書く。(octokit/octokit.rb - Application authenticationより)

client = Octokit::Client.new(client_id: '<OAuthトークンのキー20文字>', client_secret: '<OAuthトークンのシークレット40文字>')

このclientインスタンスを使ってAPIリクエストすると、認証した上でリクエストを送ることができる。
例えば、指定したユーザーのpublicな情報を取得したい場合は、以下のように書く。

client = Octokit::Client.new(client_id: '<OAuthトークンのキー20文字>', client_secret: '<OAuthトークンのシークレット40文字>')#

# 指定したユーザーのpublicな情報を取得する
client.user 'Saki-htr'

#=>
{:login=>"Saki-htr",
 :id=>58052292,
 :node_id=>"MDQ6VXNlcjU4MDUyMjky",
 :avatar_url=>"https://avatars.githubusercontent.com/u/58052292?v=4",
 :gravatar_id=>"",
 :url=>"https://api.github.com/users/Saki-htr",
 :html_url=>"https://github.com/Saki-htr",
 :followers_url=>"https://api.github.com/users/Saki-htr/followers",
 :following_url=>
  "https://api.github.com/users/Saki-htr/following{/other_user}",
 :gists_url=>"https://api.github.com/users/Saki-htr/gists{/gist_id}",
 :starred_url=>"https://api.github.com/users/Saki-htr/starred{/owner}{/repo}",
 :subscriptions_url=>"https://api.github.com/users/Saki-htr/subscriptions",
 :organizations_url=>"https://api.github.com/users/Saki-htr/orgs",
 :repos_url=>"https://api.github.com/users/Saki-htr/repos",
 :events_url=>"https://api.github.com/users/Saki-htr/events{/privacy}",
 :received_events_url=>"https://api.github.com/users/Saki-htr/received_events",
 :type=>"User",
 :site_admin=>false,
 :name=>"服部紗希",
 :company=>nil,
 :blog=>"https://saki-htr.hatenablog.com/",
 :location=>"Tokyo Japan",
 :email=>nil,
 :hireable=>nil,
 :bio=>nil,
 :twitter_username=>nil,
 :public_repos=>33,
 :public_gists=>1,
 :followers=>1,
 :following=>0,
 :created_at=>2019-11-21 21:35:26 UTC,
 :updated_at=>2022-07-25 12:05:10 UTC}

これは、REST APIのエンドポイントhttps://api.github.com/users/<ユーザー名>にアクセスしている。 このエンドポイントについての詳細は以下に載っている。

ヘッダー情報の取得

これもパーソナルアクセストークンと同じで、client.last_response.headersで取得できる。ここにレート制限の情報も入っている。

pp client.last_response.headers

#=>
{"server"=>"GitHub.com",
 "date"=>"Thu, 10 Nov 2022 07:04:45 GMT",
 "content-type"=>"application/json; charset=utf-8",
 "transfer-encoding"=>"chunked",
 "cache-control"=>"public, max-age=60, s-maxage=60",
 "vary"=>"Accept, Accept-Encoding, Accept, X-Requested-With",
 "etag"=>"W/\"49e08a708a784b2cf84ba6a0f6b5a79b665b2a6449e103c7a5ade32d925a82dc\"",
 "last-modified"=>"Mon, 25 Jul 2022 12:05:10 GMT",
 "x-github-media-type"=>"github.v3; format=json",
 "x-ratelimit-limit"=>"5000",
 "x-ratelimit-remaining"=>"4994",
 "x-ratelimit-reset"=>"1668066924",
 "x-ratelimit-used"=>"6",
 "x-ratelimit-resource"=>"core",
 "access-control-expose-headers"=>
  "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
 "access-control-allow-origin"=>"*",
 "strict-transport-security"=>"max-age=31536000; includeSubdomains; preload",
 "x-frame-options"=>"deny",
 "x-content-type-options"=>"nosniff",
 "x-xss-protection"=>"0",
 "referrer-policy"=>"origin-when-cross-origin, strict-origin-when-cross-origin",
 "content-security-policy"=>"default-src 'none'",
 "content-encoding"=>"gzip",
 "x-github-request-id"=>"CE6B:379A:141FCB:1C9892:636CA28D"}

Octokit.rbを使うととても簡単に認証できる

このように、Octokit.rbを使えば、Octokit::Client.newの引数にトークンを渡すだけで簡単に認証することができるので、とても便利。

Octokit.rbでAPIをたたく

GitHubAPIに対して自分がやりたいことを実現する方法を探すには、以下の公式ドキュメントから探す。

自分が使うOctokit.rbのメソッドが、APIのどのエンドポイントにリクエストするか知りたい場合は、各メソッドの説明の「See Also:」に、各エンドポイントへの公式ドキュメントのURLが載っているので、そちらを見ると分かる。

Module: Octokit::Client::Users —より

例1:指定したリポジトリのうち、指定したidのissueを取得する

上述の素のRubyAPIリクエストした例と同じように、「とあるリポジトリの、指定したidのissueを取得したい」とする。
これを取得するには、Octokit.rbが用意しているissueメソッドを使う。

Module: Octokit::Client::Issues —より

これを使う場合、以下のように書く。

# 使い方
client.issue('アカウント名/リポジトリ名', 'issueのid')
# 認証方法はどちらでも良い
  # パーソナルアクセストークンで認証
client = Octokit::Client.new(:access_token => '<取得したパーソナルアクセストークン>')

  # OAuthトークンで認証
client = Octokit::Client.new(client_id: '<OAuthトークンのキー20文字>', client_secret: '<OAuthトークンのシークレット40文字>')

# fjordllc/bootcampリポジトリのidが1のissueを取得する
client.issue('fjordllc/bootcamp', '1')

これで、以下のデータが返ってくる。

返ってくるデータ(長いので折り畳んだ)

{:url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1",
 :repository_url=>"https://api.github.com/repos/fjordllc/bootcamp",
 :labels_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/labels{/name}",
 :comments_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/comments",
 :events_url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/events",
 :html_url=>"https://github.com/fjordllc/bootcamp/pull/1",
 :id=>11961549,
 :node_id=>"MDExOlB1bGxSZXF1ZXN0NDU4NjQxMA==",
 :number=>1,
 :title=>"Update rails 4.0.0.beta",
 :user=>
  {:login=>"hrysd",
   :id=>1663465,
   :node_id=>"MDQ6VXNlcjE2NjM0NjU=",
   :avatar_url=>"https://avatars.githubusercontent.com/u/1663465?v=4",
   :gravatar_id=>"",
   :url=>"https://api.github.com/users/hrysd",
   :html_url=>"https://github.com/hrysd",
   :followers_url=>"https://api.github.com/users/hrysd/followers",
   :following_url=>"https://api.github.com/users/hrysd/following{/other_user}",
   :gists_url=>"https://api.github.com/users/hrysd/gists{/gist_id}",
   :starred_url=>"https://api.github.com/users/hrysd/starred{/owner}{/repo}",
   :subscriptions_url=>"https://api.github.com/users/hrysd/subscriptions",
   :organizations_url=>"https://api.github.com/users/hrysd/orgs",
   :repos_url=>"https://api.github.com/users/hrysd/repos",
   :events_url=>"https://api.github.com/users/hrysd/events{/privacy}",
   :received_events_url=>"https://api.github.com/users/hrysd/received_events",
   :type=>"User",
   :site_admin=>false},
 :labels=>[],
 :state=>"closed",
 :locked=>false,
 :assignee=>nil,
 :assignees=>[],
 :milestone=>nil,
 :comments=>0,
 :created_at=>2013-03-13 06:30:12 UTC,
 :updated_at=>2013-03-13 06:39:10 UTC,
 :closed_at=>2013-03-13 06:39:10 UTC,
 :author_association=>"CONTRIBUTOR",
 :active_lock_reason=>nil,
 :draft=>false,
 :pull_request=>
  {:url=>"https://api.github.com/repos/fjordllc/bootcamp/pulls/1",
   :html_url=>"https://github.com/fjordllc/bootcamp/pull/1",
   :diff_url=>"https://github.com/fjordllc/bootcamp/pull/1.diff",
   :patch_url=>"https://github.com/fjordllc/bootcamp/pull/1.patch",
   :merged_at=>2013-03-13 06:39:10 UTC},
 :body=>"",
 :closed_by=>
  {:login=>"komagata",
   :id=>16577,
   :node_id=>"MDQ6VXNlcjE2NTc3",
   :avatar_url=>"https://avatars.githubusercontent.com/u/16577?v=4",
   :gravatar_id=>"",
   :url=>"https://api.github.com/users/komagata",
   :html_url=>"https://github.com/komagata",
   :followers_url=>"https://api.github.com/users/komagata/followers",
   :following_url=>
    "https://api.github.com/users/komagata/following{/other_user}",
   :gists_url=>"https://api.github.com/users/komagata/gists{/gist_id}",
   :starred_url=>
    "https://api.github.com/users/komagata/starred{/owner}{/repo}",
   :subscriptions_url=>"https://api.github.com/users/komagata/subscriptions",
   :organizations_url=>"https://api.github.com/users/komagata/orgs",
   :repos_url=>"https://api.github.com/users/komagata/repos",
   :events_url=>"https://api.github.com/users/komagata/events{/privacy}",
   :received_events_url=>
    "https://api.github.com/users/komagata/received_events",
   :type=>"User",
   :site_admin=>false},
 :reactions=>
  {:url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/reactions",
   :total_count=>0,
   :"+1"=>0,
   :"-1"=>0,
   :laugh=>0,
   :hooray=>0,
   :confused=>0,
   :heart=>0,
   :rocket=>0,
   :eyes=>0},
 :timeline_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/timeline",
 :performed_via_github_app=>nil,
 :state_reason=>nil}

hattorisaki@Mac-4 ~/blog (elevator)> ruby app.rb
{:url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1",
 :repository_url=>"https://api.github.com/repos/fjordllc/bootcamp",
 :labels_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/labels{/name}",
 :comments_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/comments",
 :events_url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/events",
 :html_url=>"https://github.com/fjordllc/bootcamp/pull/1",
 :id=>11961549,
 :node_id=>"MDExOlB1bGxSZXF1ZXN0NDU4NjQxMA==",
 :number=>1,
 :title=>"Update rails 4.0.0.beta",
 :user=>
  {:login=>"hrysd",
   :id=>1663465,
   :node_id=>"MDQ6VXNlcjE2NjM0NjU=",
   :avatar_url=>"https://avatars.githubusercontent.com/u/1663465?v=4",
   :gravatar_id=>"",
   :url=>"https://api.github.com/users/hrysd",
   :html_url=>"https://github.com/hrysd",
   :followers_url=>"https://api.github.com/users/hrysd/followers",
   :following_url=>"https://api.github.com/users/hrysd/following{/other_user}",
   :gists_url=>"https://api.github.com/users/hrysd/gists{/gist_id}",
   :starred_url=>"https://api.github.com/users/hrysd/starred{/owner}{/repo}",
   :subscriptions_url=>"https://api.github.com/users/hrysd/subscriptions",
   :organizations_url=>"https://api.github.com/users/hrysd/orgs",
   :repos_url=>"https://api.github.com/users/hrysd/repos",
   :events_url=>"https://api.github.com/users/hrysd/events{/privacy}",
   :received_events_url=>"https://api.github.com/users/hrysd/received_events",
   :type=>"User",
   :site_admin=>false},
 :labels=>[],
 :state=>"closed",
 :locked=>false,
 :assignee=>nil,
 :assignees=>[],
 :milestone=>nil,
 :comments=>0,
 :created_at=>2013-03-13 06:30:12 UTC,
 :updated_at=>2013-03-13 06:39:10 UTC,
 :closed_at=>2013-03-13 06:39:10 UTC,
 :author_association=>"CONTRIBUTOR",
 :active_lock_reason=>nil,
 :draft=>false,
 :pull_request=>
  {:url=>"https://api.github.com/repos/fjordllc/bootcamp/pulls/1",
   :html_url=>"https://github.com/fjordllc/bootcamp/pull/1",
   :diff_url=>"https://github.com/fjordllc/bootcamp/pull/1.diff",
   :patch_url=>"https://github.com/fjordllc/bootcamp/pull/1.patch",
   :merged_at=>2013-03-13 06:39:10 UTC},
 :body=>"",
 :closed_by=>
  {:login=>"komagata",
   :id=>16577,
   :node_id=>"MDQ6VXNlcjE2NTc3",
   :avatar_url=>"https://avatars.githubusercontent.com/u/16577?v=4",
   :gravatar_id=>"",
   :url=>"https://api.github.com/users/komagata",
   :html_url=>"https://github.com/komagata",
   :followers_url=>"https://api.github.com/users/komagata/followers",
   :following_url=>
    "https://api.github.com/users/komagata/following{/other_user}",
   :gists_url=>"https://api.github.com/users/komagata/gists{/gist_id}",
   :starred_url=>
    "https://api.github.com/users/komagata/starred{/owner}{/repo}",
   :subscriptions_url=>"https://api.github.com/users/komagata/subscriptions",
   :organizations_url=>"https://api.github.com/users/komagata/orgs",
   :repos_url=>"https://api.github.com/users/komagata/repos",
   :events_url=>"https://api.github.com/users/komagata/events{/privacy}",
   :received_events_url=>
    "https://api.github.com/users/komagata/received_events",
   :type=>"User",
   :site_admin=>false},
 :reactions=>
  {:url=>"https://api.github.com/repos/fjordllc/bootcamp/issues/1/reactions",
   :total_count=>0,
   :"+1"=>0,
   :"-1"=>0,
   :laugh=>0,
   :hooray=>0,
   :confused=>0,
   :heart=>0,
   :rocket=>0,
   :eyes=>0},
 :timeline_url=>
  "https://api.github.com/repos/fjordllc/bootcamp/issues/1/timeline",
 :performed_via_github_app=>nil,
 :state_reason=>nil}


このように、octokit.rbを使うと、JSON形式ではなく、Rubyオブジェクトでデータを返してくれるので、JSON.parseRubyオブジェクトに変換しなくてすむので、とても便利。

例2: 指定したリポジトリの中で、指定したユーザーにアサインされたissueの一覧を取得する

issueの一覧を取得する場合、list_issuesメソッドを使う。

Module: Octokit::Client::Issues —より

Options Hashの欄に、:assignee (String) — User login.とある。Optionsはハッシュで渡すので、以下のように書く。

client.list_issues('アカウント名/リポジトリ名', { assignee: 'ユーザー名' })

すると、以下のようなイメージで、配列に各issueのデータがハッシュに入って返ってくる。

[
  {1つ目のissueのデータ},
  {2つ目のissueのデータ},
  {3つ目のissueのデータ}
]

複数人分、一気に取得したい場合

自作サービスで、複数人分、つまりTanakaさんにアサインされたissueとSatoさんにアサインされたissueを一気にAPIリクエストして取得したかったので、以下のように書いて取得した。

# 取得したいユーザー名を入れた配列を用意しておく
students = ['Tanaka', 'Sato']

# studentをeachで回してissueを取得する
assigned_issues = students.flat_map do |student|
  client.list_issues('fjordllc/bootcamp', { assignee: student })
end

flat_mapメソッドを使っている理由は、mapメソッドを使うと、

[[
  {1つ目のissue},
  {2つ目のissue},
  {3つ目のissue}
]]

のように、二重配列になってしまうため、ネストしている配列をなくした結果を返してくれるflat_mapメソッドを使った。
(参考:配列に使える便利メソッド flat_map/collect_concatについて - Qiita)

おわりに

一回素のRubyで書いたことで、ラッパーライブラリの便利さが分かり、開発してくださった方へのありがたみが増したので、遠回りかもしれないが、使う場合と使わない場合の両方のパターンでAPIリクエストの処理を書いてみてよかったなと思う。