はじめに
フィヨルドブートキャンプ(以下、FBC)の「自分で考えたサービスをリリースする」という最終課題で、GitHubのREST API(REST)を利用した。(※近日リリース予定)
Ruby on Railsを使って開発したのだが、RubyでGitHubAPIをたたく際に、GitHubが用意してくれている公式のラッパーライブラリ、octokit.rbを使ったらとても便利だった。
サービス開発を始めた当初、「octokit.rbというラッパーライブラリがあるよ」、とGitHubの公式ドキュメントで紹介されていて存在は知っていたが、APIをたたくのが初めてなこともあり、使うことでどんなメリットがあるか分からなかったので、最初は素のRubyでAPIにリクエストする処理を書いていた。
素のRubyで処理を書いてAPIの操作に慣れてきたところで、ラッパーライブラリを使ったところ、「え!とても便利✨」と感動したので、
- そもそもラッパーライブラリとは
- 素のRubyでGitHubAPI(REST)をたたくと、どんなコードになるのか
- 素のRubyでたたいた時の不便な点→octokit.rbを使えば解決
- octokit.rbを使った認証方法
- octokit.rbを使ったGitHubAPI(REST)のたたき方
を中心に、学んだことをまとめた。
目次
- はじめに
- 目次
- そもそもラッパーライブラリとは
- 素のRubyでGitHubAPI(REST)をたたくと、どんなコードになるのか
- 素のRubyで書いた時の不便な点
- Octokit.rbの使い方
- おわりに
そもそもラッパーライブラリとは
ラッパーとは、
ある開発環境向けに提供されたプログラムなどを別の環境で利用できるようにするもの。
(ラッパーとは - 意味をわかりやすく - IT用語辞典 e-Words より)
GitHubAPIには、使用方法を拡張したり簡略化するためのライブラリがたくさんある。
以下の公式ドキュメントで、どのようなラッパーライブラリがあるか、非公式なものも含めて紹介されている。
Rubyには、GitHub公式のoctokit.rbがある。今回はこれを使った。
素のRubyでGitHubAPI(REST)をたたくと、どんなコードになるのか
ラッパーライブラリの便利さを伝えるために、まず素のRubyでGitHubAPIをたたく場合、どのようなコードを書く必要があるか説明していく。
- net/httpライブラリ:HTTP を扱うライブラリ。APIにGET,POSTなどのHTTPリクエストを行うために必要。
- uriライブラリ:URIをパースするために必要。
- jsonライブラリ:APIをたたくとJSON形式で返ってくるため、JSONを扱うために必要。
例えば、「とあるリポジトリのうち、指定した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でデータを処理できない
上記のコードだと、
といった処理を必ず書かないといけない。
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より)
認証方法
パーソナルアクセストークンを使う場合
まず、https://github.com/settings/tokens にアクセスし、パーソナルアクセストークンを取得する。
具体的な取得方法は以下のドキュメントで説明されている。
Creating a personal access token - GitHub Docs
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が載っているので、そちらを見ると分かる。
例1:指定したリポジトリのうち、指定したidのissueを取得する
上述の素のRubyでAPIリクエストした例と同じように、「とあるリポジトリの、指定したidのissueを取得したい」とする。
これを取得するには、Octokit.rbが用意しているissue
メソッドを使う。
これを使う場合、以下のように書く。
# 使い方 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.parse
でRubyオブジェクトに変換しなくてすむので、とても便利。
例2: 指定したリポジトリの中で、指定したユーザーにアサインされたissueの一覧を取得する
issueの一覧を取得する場合、list_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リクエストの処理を書いてみてよかったなと思う。