【Rails】Kaminariでページネーションしたデータをページごとキャッシュする方法

RubyOnRails

結論

Rails.cache.fetch("page_#{page}", expires_in: 24.hours) do
  items = Item.all.page(page).per(50)
  Kaminari::PaginatableArray.new(items.to_a, limit: items.limit_value, offset: items.offset_value, total_count: items.total_count)
end

概要

Kaminariを使ってページネーションをしていて、データが膨大なのでキャッシュをすることになりました。

しかし、 Item.allをキャッシュするのもまたデータが大きすぎるので、ページごとに絞り込んだ結果でキャッシュしたいということに。

いつも通りの方法で以下の様にやってみたところエラーが…

Rails.cache.fetch("page_#{page}", expires_in: 24.hours) do
  items = Item.all.page(page).per(50)
end

=> can't dump anonymous class #<Module:0x00000001197139b8>

ということで調べました。

なぜエラーになるのか?

ChatGPTによると、エラーの原因は以下になります。

TypeError: can't dump anonymous class というエラーは、Railsのキャッシュ機構で、キャッシュに保存できない(シリアライズできない)オブジェクトを扱っている際に発生します。この場合、キャッシュ対象のオブジェクト(Item.all.page(page).per(50))の中に、キャッシュできない匿名クラスやモジュールが含まれている可能性があります。”

Kaminariのメソッドを使ったことで余計なデータが入ってしまった様です。

解決策

Kaminari::PaginatableArray を使ってKaminariに互換性のある配列に変換します。

単純な配列であればキャッシュができるので、色々調べると絞り込んだデータを .to_a で配列にする様言われました。

ですが、配列にすると今度は paginate で使われる total_pages などが使えなくなってしまうのが問題でした。

それをKaminariに互換性のある配列にすることでどちらの問題も解決することができます。

items = Item.all.page(page).per(50)
Kaminari::PaginatableArray.new(items.to_a, limit: items.limit_value, offset: items.offset_value, total_count: items.total_count)

各パラメータの解説は以下です。

  • 第1引数:こちらは配列型にしたオブジェクトを渡します。
  • limit:Kaminariが設定している1ページあたりの件数(per(50)50)を取得します。これにより、Kaminari::PaginatableArrayに「1ページあたりの件数」が渡されます。
  • offset:現在のページの先頭から何件目までスキップするかを示します。例えば、2ページ目の場合、per(50)だとoffset_valueは50になります。これにより、正確にページのオフセット位置が適用されます。
  • total_count:全体のレコード数を示します。これにより、ページネーションが全体で何ページ必要なのかを計算することができます。

最終的なコード

# controller

def index
  @items = Rails.cache.fetch("page_#{page}", expires_in: 24.hours) do
             items = Item.all.page(page).per(50)
             Kaminari::PaginatableArray.new(items.to_a, limit: items.limit_value, offset: items.offset_value, total_count: items.total_count)
           end
end
# view
<%= paginate @items %>

コメント

タイトルとURLをコピーしました