コレクションキャッシュでキャッシュのキーをカスタマイズする方法
コレクションキャッシュについてコードリーディングを行ったところ、キャッシュキーのカスタマイズ方法がわかったのでメモ。
この記事のまとめ
Railsガイドには書いてありませんが、:cached
に callable なオブジェクトを渡すと、その結果をキャッシュキーとして使ってくれます。
# cached: true と同じ <%= render partial: 'posts/post', collection: @posts, cached: -> (post) { post } %> # posts.comments の結果に変更があった場合も違うキーになる <%= render partial: 'posts/post', collection: @posts, cached: -> (post) { [post, post.comments] } %> # locals はキャシュキーに影響しないため、locals によってキーを変えるときは自分で指定してやる必要がある <%= render partial: 'posts/post', collection: @posts, locals: { flag: true }, cached: -> (post) { [{flag: true}, post] } %>
コレクションキャッシュ
一覧などを表示する際に便利な :collection
オプションは :cached
オプションを渡すことで、各アイテムの描画でキャッシュを使ってくれます。
たとえば
<%= render partial: 'posts/post', collection: @posts, cached: true %>
とすると、ログは次のようになります。
Rendered collection of posts/_post.html.erb [0 / 10 cache hits] (Duration: 1733.3ms | Allocations: 2034040) Rendered collection of posts/_post.html.erb [10 / 10 cache hits] (Duration: 1.2ms | Allocations: 546)
キャッシュのキー
キャッシュのキーは、ビューファイルのパス、内容のハッシュ値、コレクションアイテム(今回の場合Postインスタンスオブジェクト)による配列となります。(6.0.3現在)
# キャッシュのキー [:views, "posts/_post:88c97eec416c8a7c8aaa37acb5edbbbd", #<Post id: 123, ... >]
rails/collection_caching.rb at b378bda17d710eb08949771390e7b7d77ee2b39d · rails/rails · GitHub
なお、配列のキーの場合、Railsのキャッシュ機構は配列要素から文字列のキーとキャッシュバージョンを取り出して使用します。(5.2から)
今回の場合、 Post#cache_version #=> updated_at から作った文字列
がキャッシュバージョンに含まれるため、 Post
を更新すると新しいキャッシュとなります。
キャッシュのキーを変更したいケース
上記のような標準のキャッシュキーではうまくキャッシュを破棄できない場合があります。
コレクションアイテムの依存先の変更で描画内容を変化させたい
次のような Post#comments
によって描画内容が変化するテンプレートの場合
<!-- posts/_post.html.erb --> <h3><%= post.title %></h3> <p><%= post.comments.count %> comments</p>
post.comments.count
が変わってもにキャッシュキーが変化しないため、
<%= render partial: 'posts/post', collection: @posts, cached: true %>
ではキャッシュが入れ替わりません。
これを避けるためには、次のどちらかがよく採用されると思います。
comments
が変更される度に、post
のupdated_at
を更新してキャッシュバージョンを変化させる(post.touch
)- キャッシュキーに
comments
も含める
1の方法はコールバックなどの仕組みが必要で、モデルの管理が難しくなるので、私は2の方が使いやすいと思います。
コレクションアイテム以外の入力で描画内容が変化するとき
次の例は、人気の記事と新着記事で同一のテンプレートを使い、一方では人気順のバッジを表示する、というものです。
<h2>人気の記事<h2> <%= render partial: 'posts/post', collection: @popular_posts, locals: { show_rank_badge: true }, cache: true %> <h2>新着記事</h2> <%= render partial: 'posts/post', collection: @recent_posts, locals: { show_rank_badge: false }, cache: true %>
このような場合、たとえば @popular_posts
と @recent_posts
に同じ記事が含まれていた場合、新着記事の表示欄でもバッジ付きの表示になってしまいます。
これを避けるためには、それぞれ違うキャッシュキーになるようにしなければいけません。
キャッシュのキーを変更する方法
ActionView::CollectionCaching のコードを確認すると次のようにしてキーを変更することができました。(Rails 6.0.3)
<%= render partial: 'posts/post', collection: @posts, cached: -> (post) { [post, post.comments] } %> <%= render partial: 'posts/post', collection: @posts, locals: { show_rank_badge: true }, cached: -> (post) { [post, { show_rank_badge: true }] } %>