逃げる8回で会心の一撃

Web エンジニアのサトウリョウスケが開発とか色々書くブログです

my_api_client v0.15.0 をリリースしました🚀

その前に v0.14.0 もリリースしているのですが、こちらはリファクタリングと Integration Test の実装だけで新機能はありませんでした。 差分が +2,799 -1,246 もあるので中身は結構書き換わっています。

Release v0.14.0 · ryz310/my_api_client · GitHub

Integration Test では Ruby on Jets を使って AWS Lambda でサーバーを建てて、CI でのテストで my_api_client を使って実際に HTTP リクエストが成功することを確認しているので、デグレの心配が随分と緩和されました 😌

新機能: Pagination API のサポート

ここからは v0.15.0 の話になります。

Release v0.15.0 · ryz310/my_api_client · GitHub

v0.15.0 のメイン機能が Pagination API のサポートになります。JSON:API というしっかりとした仕様もあるようですが、 my_api_client ではそこまで厳密な仕様に則っている訳ではなく、レスポンスに含まれる URL を認識して enumerable に HTTP リクエストを実行する、というざっくりした機能になります。 レスポンスヘッダの Link などで次のページの URL を返すケースもあるようですが、そちらは現時点では未対応です 🙏

Pagination API という単語は Django REST Framework の Pagination 機能の説明で出てきます。

www.django-rest-framework.org

要するに一度のリクエストで結果を全件取得させるのではなく、一定の件数を返却し、続きを取得できる Link を一緒に返却する API のことですね。

Request:

GET https://api.example.org/accounts/?page=4

Response:

HTTP 200 OK
{
    "count": 1023
    "next": "https://api.example.org/accounts/?page=5",
    "previous": "https://api.example.org/accounts/?page=3",
    "results": []
}

使い方

my_api_client での使い方は以下のようになります。

class MyPaginationApiClient < ApplicationApiClient
  endpoint 'https://example.com/v1'

  # GET pagination?page=1
  def pagination
    pageable_get 'pagination', paging: '$.links.next', headers: headers, query: { page: 1 }
  end

  private

  def headers
    { 'Content-Type': 'application/json;charset=UTF-8' }
  end
end

通常であれば #get を使って HTTP リクエストを実行させるのですが、ここでは #pageable_get というメソッドを使用しています。 #pageable_get だと長いので #pget というエイリアスも用意しています。 また、 paging というキーワード引数も新たに出てきました。 paging ではレスポンスのどの部分に次のページの URL が含まれるかを JSONPath expression で指定します。

goessner.net

以下のような JSON であれば、 $.links.next という JSONPath expression は "https://example.com/pagination?page=3" を取得します。

{
  "links": {
    "next": "https://example.com/pagination?page=3",
    "previous": "https://example.com/pagination?page=1",
  },
  "page": 2
}

作成した API Client は以下のように使用できます。

api_clinet = MyPaginationApiClient.new
api_clinet.pagination.each do |response|
  # Do something.
end

p = api_clinet.pagination
p.next # => 1st page result
p.next # => 2nd page result
p.next # => 3rd page result

結果は Enumerator::Lazy で返却される

#pageable_getEnumerator::Lazy を返却するので、 Enumerable で定義されているメソッドは一通り利用可能です。

docs.ruby-lang.org

Enumerator で返してしまうと #take で 100 ページ目まで結果を取得するような処理を記述したときに、

  1. 100 ページ分の HTTP リクエストを実行
  2. 結果を #each で回す

という動きになり、 100 回分の HTTP リクエストが完了するまで次の処理に移ることができません。

Enumerator::Lazy であれば、

  1. 1 ページ目の HTTP リクエストを実行
  2. 結果を処理する
  3. 2ページ目の HTTP リクエストを実行
  4. 結果を処理する
  5. ...

という動きになってくれます。便利ですね ✨

EnumeratorEnumerator::Lazy の違いは以下の記事が参考になると思います。

qiita.com