📃

RailsのAPIでpaginationを考える

2024/06/21に公開

5月になり、もう夏か〜となったら6月梅雨シーズンで半袖が寒い〜というあるあるを感じています...
今回はタイトルの通り、自社のサービスのpaginationを考え直す機会があったのでそのアウトプットです。
Railsには色んなpaginationのライブラリがあったり、結局paginationで必要なパラメーターやレスポンスってなんだ..?となったので同じような悩みを抱えた方の参考になれば嬉しいです。

やりたいこと

前述の通り、Ruby on Railsであるmodelのindexでpaginationを含んだレスポンスを返したいです。
Rubyのpaginationのライブラリはviewも加味したライブラリが多いため選定には少し苦戦しました。

ライブラリ選定

paginationの実装は以下の手段があがりました。

  • 自前で実装する
  • kaminariを入れる
  • pagyを入れる

自前で実装する

ライブラリをできるだけ削り、できるだけ軽量なRailsを保つようにしているためこの案が出てきました。

  • メリット
    • 軽量なRailsを保てる(はず)
    • paginationの実装がライブラリまでいかなくても追える
  • デメリット
    • 車輪の再開発
    • メンテコスト

別のライブラリを入れる場合でも同じことが言えるなぁって思いながら↑書いてましたw

pagyを入れる

GitHubのREADMEにもある通り、kaminariなど他ライブラリと比べて軽量らしいです。
(手元で比較していないため らしい にとどめておきます)

kaminariを入れる

有名なライブラリも候補にあげました。

  • メリット
    • チーム内で導入した実績がある
  • デメリット
    • pagyと比べて重そう..?
    • APIサーバーであるためUI部分のライブラリは削りたい

結果

色々試してみた結果、自分たちのチームでは kaminari を利用することになりました。
理由としては以下です。

  • チーム内で導入した実績がある
  • メンテコスト
    • ライブラリを使う
    • version upが頻繁ではない

kaminari はrelease tag自体は最新が2021年12月ですが、commitを見ると今もメンテされてそう(めちゃめちゃありがたい)なのでありがたく使わせてもらうことにしました。

スキーマ定義

パラメータは特に変わったことはしておらず、何ページ目かと1ページあたりの表示数を受け付けています。

getのパラメータ
  parameters:
    - name: page
      in: query
      description: ページ番号
      required: false
      schema:
        type: integer
    - name: perPage
      in: query
      description: 1ページあたりの表示数
      required: false
      schema:
        type: integer

レスポンスとしては startPositionendPosition は珍しいかなと思います。
これは要件としてどこからどこまでを表示しているかを返す必要があったため入れています。
fromto とかにしても良いかなと思いますね。

response
type: object
properties:
  totalPage:
    type: integer
    description: 全体のページ数
  currentPage:
    type: integer
    description: 現在のページ番号
  totalCount:
    type: integer
    description: 全体のレコード数
  startPosition:
    type: integer
    description: 現在のページの開始位置
  endPosition:
    type: integer
    description: 現在のページの終了位置
required:
  - totalPage
  - currentPage
  - totalCount
  - startPosition
  - endPosition

controller実装

レスポンスでは kaminari のおかげで生えているメソッドを駆使して返しています。

controller
def hoge
  page = params[:page] || 0
  per_page = params[:per_page] || 20
  hoges = Hoge.page(page).per(per_page)
  render json: {
    hoges: hoges.map do |hoge|
      {
        id: hoge.id,
        name: hoge.name
      }
    end,
    pagination: {
      totalPage: hoges.total_pages,
      currentPage: hoges.current_page,
      totalCount: hoges.total_count,
      startPosition: hoges.count.zero? ? 0 : hoges.offset_value + 1,
      endPosition: hoges.offset_value + hoges.count
    }
  }.to_json
end

まとめ

自分たちのチームでは暫定として kaminari を使うことにしましたが、APIサーバーとしては以下のライブラリは必要がないので改善していく予定です!!
あくまで自分たちのチームではという前提なので他のチームや状況であれば pagywill_paginate などを導入するかと思います。
また、導入にあたりそこまで時間がなかったためより良いライブラリなどがあればぜひ教えてほしいです!!

Gemfile.lock
    kaminari (1.2.2)
      activesupport (>= 4.1.0)
      kaminari-actionview (= 1.2.2) <-これ!!!!!!!!!!!!!!!!
      kaminari-activerecord (= 1.2.2)
      kaminari-core (= 1.2.2)
    kaminari-actionview (1.2.2) <-これ!!!!!!!!!!!!!!!!
      actionview
      kaminari-core (= 1.2.2)
    kaminari-activerecord (1.2.2)
      activerecord
      kaminari-core (= 1.2.2)
    kaminari-core (1.2.2)
SMARTCAMP Engineer Blog

Discussion