Ruby
Rails
kaminari
RubyOnRails

DBからlimitで件数を絞ったデータをkaminariでページネーションする

TL;DR

limit で絞ったデータを kaminari で取り扱う場合は
一旦、Arrayに置き換えてから Kaminari.paginate_array を使って表現する

問題点と回避策

問題点

kaminari は Modelで検索したデータに
.page.per してあげるだけで
簡単にページネーションできるgemですが
ActiveModellimit は無視されてしまいます。1

例えば『直近で更新したユーザ100人のデータを1ページ5件ずつに表示させたい』
といったようなデータを抜き出すときに

直感的に書いた場合のコード
list_data = User.order(updated_at: :desc).limit(100).page(1).per(5)

と書けばできそうな雰囲気がありますが .limit(100) の内容は無視されてしまうので
Userのデータが100件以上あれば20ページ目以降(100 ÷ 5 = 20)も取れてしまう

コード
list_data = User.order(updated_at: :desc).limit(100).page(25).per(5)         
実際に走るクエリ
SELECT  `users`.* FROM `users` ORDER BY `users`.`updated_at` DESC LIMIT 5 OFFSET 120
# kaminari側でlimitを設定しているので .limit(100) は無視される

回避策

ページネーションするときにDBの検索がかかるため
先にデータの抽出が終わった状態のものに
ページネーション処理を行えば別の処理として扱えます。

なので、一旦Arrayにしてから
そのArrayオブジェクトに対してページネーション処理をすれば
求めていた結果を得ることができます。

先程の例に対して適応すると以下のようになります。

arrayに置き換え後、Kaminari.paginate_arrayでページネーション
user_result = User.order(updated_at: :desc).limit(100)
user_array = user_result.each_with_object [] do |user, result|
  result << user
end

list_data = Kaminari.paginate_array(user_array).page(1).per(5)

ただし、これは部分的に抽出してくれるkaminariの利点を削っているので
大量のデータを絞り込んで使う場合はいらぬ負荷をかけてしまう懸念があるので
ご注意ください。

参考ページ


  1. Githubにある、kaminariのReadmeに『Keep in mind that per internally utilizes limit and so it will override any limit that was set previously. 』 とある