Railsのフラグメントキャッシュを分解調査する(翻訳)

概要 原著者の許諾を得て翻訳・公開いたします。 英語記事: Disassembling Rails — Fragment Caching – Stan Lo – Medium 原文公開日: 2018/06/23 著者: Stan Lo — Goby言語の作者でありRails開発者です。 なお、本記事はRubyWeekly #405でも紹介されています。 Railsのフラグメントキャッシュを分解調査する(翻訳) 本記事は「Rails分解調査」シリーズの第一弾です。このシリーズのねらいは、Railsのコンポーネントを使うときにそれぞれの機能(今回のフラグメントキャッシュなど)が互いにどう絡み合うかという点について一般的な概念を示すことです。何ぶんこの種の記事を書くのは初めてですので、何かお気づきの点がありましたらぜひ元記事までコメントをどうぞ。 Railsのフラグメントキャッシュはほとんどの方がご存知か使ったことがあると思いますが、そのしくみについてはあまり知られていないのではないでしょうか。そこで本記事では、Railsの背後のフラグメントキャッシュのしくみについて簡単にご紹介したいと思います。 まず、フラグメントキャッシュの実行に関連する以下のコンポーネント(クラスやモジュール)を見ていきましょう。 ActionView::Helpers::CacheHelper AbstractController::Caching::Fragments ActiveSupport::Cache::Store もちろんここでは一般的な概念を示すにとどめますので、すべてのクラスやモジュールまではリストアップしていません。 ユーザーインターフェイス フラグメントキャッシュの使い方はいたってシンプルです。次のようにcacheを呼ぶだけで完了します。 <% cache project do %> <b>All the topics on this project</b> <%= render project.topics %> <% end %> このcacheメソッドがあるのはActionView::Helpers::CacheHelperというビューヘルパーです。 # File actionview/lib/action_view/helpers/cache_helper.rb, line 165 module ActionView module Helpers module CacheHelper def cache(name = {}, options = {}, &block) if controller.respond_to?(:perform_caching) && controller.perform_caching name_options = options.slice(:skip_digest, :virtual_path) safe_concat(fragment_for(cache_fragment_name(name, name_options), options, &block)) else yield end nil end end end end ここでは主に次の2つを行っています。 cache_fragment_nameでキャッシュのキーを生成する fragment_forでこのブロックをキャッシュする fragment_forの動作を見てみましょう(訳注: これはprivateメソッドです)。 # actionview/lib/action_view/helpers/cache_helper.rb def fragment_for(name = {}, options = nil, &block) if content = read_fragment_for(name, options) …… content else …… write_fragment_for(name, options, &block) end end この動作も非常にシンプルで、キャッシュが存在する場合は内容を読み出し、なければコンテンツを書き込んでいるだけです。キャッシュの内容の読み書き以外に、以下のようにテンプレートのレンダラーにキャッシュのヒットの有無も通知します。 # actionview/lib/action_view/helpers/cache_helper.rb def fragment_for(name = {}, options = nil, &block) if content = read_fragment_for(name, options) @view_renderer.cache_hits[@virtual_path] = :hit if defined?(@view_renderer) …… else @view_renderer.cache_hits[@virtual_path] = :miss if defined?(@view_renderer) …… end end Railsはキャッシュヒットの有無をこのようにして認識します。そしてfragment_forメソッドはキャッシュの内容の読み書きのためにread_fragment_forとwrite_fragment_forを使っています(訳注: これらもprivateメソッドです)。 # actionview/lib/action_view/helpers/cache_helper.rb def read_fragment_for(name, options) controller.read_fragment(name, options) end def write_fragment_for(name, options) pos = output_buffer.length yield …… fragment = output_buffer.slice!(pos..-1) …… controller.write_fragment(name, fragment, options) end ご覧のように、最終的にcontrollerという名前の変数に対してread_fragmentとwrite_fragmentが呼び出されています。ご推察のとおり、このcontrollerとは現在のリクエストを処理しているRailsのコントローラなのです。つまりここから主体がビューからコントローラに移ることになります。controller.write_fragmentのソースを追ってみると、AbstractController::Caching::Fragmentsにあるのがわかります。 メインのロジック:  AbstractController::Caching::Fragments このモジュールが提供する本質的なキャッシュ操作はread_fragment、write_fragment、expire_fragmentの3つです。 … Continue reading Railsのフラグメントキャッシュを分解調査する(翻訳)