edge
詳細はrubyonrails.orgで: もっとRuby on Rails

Railsでのキャッシュ:概要

このガイドは、キャッシュを使用してRailsアプリケーションのパフォーマンスを向上させるための入門です。

キャッシュとは、リクエストとレスポンスのサイクル中に生成されたコンテンツを保存し、類似のリクエストに対して再利用することを意味します。

キャッシュは、アプリケーションのパフォーマンスを向上させるための最も効果的な方法です。キャッシュを通じて、単一のサーバーと単一のデータベースで実行されているウェブサイトは、数千の同時ユーザーの負荷を持続することができます。

Railsは、デフォルトで一連のキャッシュ機能を提供しています。このガイドでは、それぞれのスコープと目的を学びます。これらのテクニックをマスターすれば、Railsアプリケーションは高いレスポンス時間やサーバーの請求書を必要とせずに数百万のビューを提供することができます。

このガイドを読み終えると、以下のことがわかるようになります:

1 基本的なキャッシュ

このセクションでは、ページキャッシュ、アクションキャッシュ、フラグメントキャッシュの3つのキャッシュ技術について紹介します。デフォルトでは、Railsはフラグメントキャッシュを提供します。ページキャッシュとアクションキャッシュを使用するには、Gemfileactionpack-page_cachingactionpack-action_cachingを追加する必要があります。

デフォルトでは、キャッシュは本番環境でのみ有効になっています。rails dev:cacheを実行するか、config/environments/development.rbconfig.action_controller.perform_cachingtrueに設定することで、ローカルでキャッシュを試すことができます。

注意:config.action_controller.perform_cachingの値を変更しても、Action Controllerが提供するキャッシュにのみ影響を与えます。たとえば、低レベルのキャッシュには影響しません。以下で説明する低レベルのキャッシュには影響しません。

1.1 ページキャッシュ

ページキャッシュは、ウェブサーバー(ApacheやNGINXなど)を介さずに生成されたページのリクエストを処理するRailsの仕組みです。これは非常に高速ですが、認証が必要なページなど、すべての状況に適用することはできません。また、ウェブサーバーがファイルを直接ファイルシステムから提供するため、キャッシュの期限切れを実装する必要があります。

情報:Rails 4からページキャッシュは削除されました。actionpack-page_caching gemを参照してください。

1.2 アクションキャッシュ

ページキャッシュは、beforeフィルタを持つアクションには使用できません。たとえば、認証が必要なページなどです。これがアクションキャッシュの役割です。アクションキャッシュは、ページキャッシュと同様に機能しますが、キャッシュが提供される前に着信ウェブリクエストがRailsスタックに到達するため、beforeフィルタを実行することができます。これにより、認証やその他の制限を実行しながら、キャッシュされたコピーの出力結果を提供することができます。

情報:Rails 4からアクションキャッシュは削除されました。actionpack-action_caching gemを参照してください。新しい推奨方法については、DHHのキーに基づくキャッシュの期限切れの概要を参照してください。

1.3 フラグメントキャッシュ

動的なウェブアプリケーションでは、異なるキャッシュ特性を持つさまざまなコンポーネントでページを構築することが一般的です。ページの異なる部分を個別にキャッシュし、別々に期限切れにする必要がある場合は、フラグメントキャッシュを使用できます。

フラグメントキャッシュは、ビューロジックの一部をキャッシュブロックで囲み、次のリクエストが来たときにキャッシュストアから提供することができます。

たとえば、ページ上の各製品をキャッシュしたい場合、次のコードを使用できます:

<% @products.each do |product| %>
  <% cache product do %>
    <%= render product %>
  <% end %>
<% end %>

アプリケーションがこのページに最初のリクエストを受け取ると、Railsは一意のキーで新しいキャッシュエントリを書き込みます。キーは次のようなものです:

views/products/index:bea67108094918eeba42cd4a6e786901/products/1

中央の文字列はテンプレートツリーダイジェストです。これは、キャッシュしているビューフラグメントの内容に基づいて計算されたハッシュダイジェストです。ビューフラグメント(たとえば、HTMLの変更)を変更すると、ダイジェストが変更され、既存のファイルが期限切れになります。

キャッシュエントリには、製品レコードから派生したキャッシュバージョンが格納されます。製品が変更されると、キャッシュバージョンが変更され、以前のバージョンを含むキャッシュされたフラグメントは無視されます。

ヒント:Memcachedなどのキャッシュストアは、古いキャッシュファイルを自動的に削除します。

特定の条件下でフラグメントをキャッシュしたい場合は、cache_ifまたはcache_unlessを使用できます:

<% cache_if admin?, product do %>
  <%= render product %>
<% end %>

1.3.1 コレクションキャッシュ

renderヘルパーは、コレクションに対して個別のテンプレートをキャッシュすることもできます。さらに、前の例をeachで一括してキャッシュテンプレートを一度に読み込むこともできます。これは、コレクションをレンダリングする際にcached: trueを渡すことで実現できます: html+erb <%= render partial: 'products/product', collection: @products, cached: true %>

以前のレンダリングからキャッシュされたテンプレートは一度に非常に高速に取得されます。さらに、まだキャッシュされていないテンプレートはキャッシュに書き込まれ、次のレンダリング時にマルチフェッチされます。

1.4 ロシア人の人形キャッシュ

キャッシュされたフラグメントを他のキャッシュされたフラグメントの中にネストしたい場合があります。これはロシア人の人形キャッシュと呼ばれます。

ロシア人の人形キャッシュの利点は、1つの製品が更新された場合、他の内部フラグメントが外部フラグメントを再生成する際に再利用できることです。

前のセクションで説明したように、キャッシュされたファイルは、キャッシュされたファイルが直接依存するレコードのupdated_atの値が変更された場合に期限切れになります。ただし、これによってフラグメントがネストされているキャッシュは期限切れになりません。

たとえば、次のビューを考えてみましょう:

<% cache product do %>
  <%= render product.games %>
<% end %>

これによって次のビューがレンダリングされます:

<% cache game do %>
  <%= render game %>
<% end %>

ゲームの属性のいずれかが変更されると、updated_atの値が現在の時刻に設定され、キャッシュが期限切れになります。ただし、製品オブジェクトのupdated_atは変更されないため、そのキャッシュは期限切れにならず、アプリケーションは古いデータを提供します。これを修正するには、モデルをtouchメソッドで結び付けます:

class Product < ApplicationRecord
  has_many :games
end

class Game < ApplicationRecord
  belongs_to :product, touch: true
end

touchtrueに設定すると、ゲームレコードのupdated_atを変更するアクションは、関連する製品のupdated_atも変更し、キャッシュを期限切れにします。

1.5 共有パーシャルキャッシュ

異なるMIMEタイプを持つファイル間でパーシャルと関連するキャッシュを共有することができます。たとえば、共有パーシャルキャッシュを使用すると、テンプレート作成者はHTMLファイルとJavaScriptファイルの間でパーシャルを共有できます。テンプレートがテンプレートリゾルバファイルパスに収集されるとき、テンプレート言語の拡張子のみが含まれ、MIMEタイプは含まれません。そのため、テンプレートは複数のMIMEタイプに使用することができます。以下のコードは、HTMLとJavaScriptの両方のリクエストに応答します:

render(partial: 'hotels/hotel', collection: @hotels, cached: true)

これにより、hotels/hotel.erbという名前のファイルがロードされます。

別のオプションは、レンダリングするパーシャルの完全なファイル名を含めることです。

render(partial: 'hotels/hotel.html.erb', collection: @hotels, cached: true)

これにより、hotels/hotel.html.erbという名前のファイルが、任意のファイルMIMEタイプでロードされます。たとえば、このパーシャルをJavaScriptファイルに含めることができます。

1.6 依存関係の管理

キャッシュを正しく無効にするためには、キャッシュの依存関係を適切に定義する必要があります。Railsは一般的なケースをうまく処理できるので、何も指定する必要はありません。ただし、カスタムヘルパーなどを扱う場合など、明示的に定義する必要がある場合もあります。

1.6.1 暗黙の依存関係

ほとんどのテンプレートの依存関係は、テンプレート自体でのrender呼び出しから派生することができます。以下は、ActionView::Digestorがデコードできるrender呼び出しのいくつかの例です:

render partial: "comments/comment", collection: commentable.comments
render "comments/comments"
render 'comments/comments'
render('comments/comments')

render "header"  render("comments/header") に変換されます

render(@topic)          render("topics/topic") に変換されます
render(topics)          render("topics/topic") に変換されます
render(message.topics)  render("topics/topic") に変換されます

一方、一部の呼び出しはキャッシュが正しく機能するように変更する必要があります。たとえば、カスタムコレクションを渡す場合は、次のように変更する必要があります:

render @project.documents.where(published: true)

次のように変更する必要があります:

render partial: "documents/document", collection: @project.documents.where(published: true)

1.6.2 明示的な依存関係

時には、まったく派生できないテンプレートの依存関係があります。これは通常、ヘルパーでレンダリングが行われる場合です。以下は例です:

<%= render_sortable_todolists @project.todolists %>

これらを呼び出すためには、特殊なコメント形式を使用する必要があります:

<%# Template Dependency: todolists/todolist %>
<%= render_sortable_todolists @project.todolists %>

単一テーブル継承のセットアップなど、いくつかの明示的な依存関係がある場合、すべてのテンプレートを書き出す代わりに、ワイルドカードを使用してディレクトリ内の任意のテンプレートに一致させることができます:

<%# Template Dependency: events/* %>
<%= render_categorizable_events @person.events %>

コレクションキャッシュに関しては、パーシャルテンプレートがクリーンなキャッシュ呼び出しで始まらない場合でも、テンプレートのどこにでも特殊なコメント形式を追加することで、コレクションキャッシュの恩恵を受けることができます。たとえば:

<%# Template Collection: notification %>
<% my_helper_that_calls_cache(some_arg, notification) do %>
  <%= notification.name %>
<% end %>

1.6.3 外部依存関係

キャッシュされたブロック内でヘルパーメソッドを使用し、そのヘルパーメソッドを更新した場合、キャッシュも更新する必要があります。どのように更新するかは重要ではありませんが、テンプレートファイルのMD5が変更される必要があります。一つの推奨方法は、コメントで明示的に指定することです。

<%# Helper Dependency Updated: Jul 28, 2015 at 7pm %>
<%= some_helper_method(person) %>

1.7 低レベルキャッシュ

ビューフラグメントのキャッシュではなく、特定の値やクエリ結果をキャッシュする必要がある場合があります。Railsのキャッシュメカニズムは、シリアライズ可能な情報を格納するのに非常に効率的です。

低レベルキャッシュを実装する最も効率的な方法は、Rails.cache.fetchメソッドを使用することです。このメソッドはキャッシュの読み書きを両方行います。単一の引数の場合、キーを取得し、キャッシュから値を返します。ブロックが渡された場合、キャッシュミスの場合にそのブロックが実行されます。ブロックの戻り値は、指定されたキャッシュキーの下にキャッシュに書き込まれ、その戻り値が返されます。キャッシュヒットの場合、キャッシュされた値がブロックを実行せずに返されます。

以下の例を考えてみましょう。アプリケーションには、競合するウェブサイトで製品の価格を検索するインスタンスメソッドを持つProductモデルがあります。このメソッドが返すデータは、低レベルキャッシュに適しています。

class Product < ApplicationRecord
  def competing_price
    Rails.cache.fetch("#{cache_key_with_version}/competing_price", expires_in: 12.hours) do
      Competitor::API.find_price(id)
    end
  end
end

注意: この例では、cache_key_with_versionメソッドを使用しているため、生成されるキャッシュキーはproducts/233-20140225082222765838000/competing_priceのようなものになります。cache_key_with_versionは、モデルのクラス名、idupdated_at属性に基づいて文字列を生成します。これは一般的な慣例であり、製品が更新されるたびにキャッシュを無効にする利点があります。一般的に、低レベルキャッシュを使用する場合は、キャッシュキーを生成する必要があります。

1.7.1 Active Recordオブジェクトのインスタンスのキャッシュを避ける

次の例では、キャッシュにスーパーユーザーを表すActive Recordオブジェクトのリストを保存しています。

# super_adminsは高コストなSQLクエリなので、頻繁に実行しないでください
Rails.cache.fetch("super_admin_users", expires_in: 12.hours) do
  User.super_admins.to_a
end

このパターンは避けるべきです。なぜなら、インスタンスが変更される可能性があるからです。本番環境では、属性が異なる場合やレコードが削除される場合があります。開発環境では、コードの変更時にキャッシュストアがコードを再読み込みするため、信頼性が低くなります。

代わりに、IDや他のプリミティブなデータ型をキャッシュしてください。例えば:

# super_adminsは高コストなSQLクエリなので、頻繁に実行しないでください
ids = Rails.cache.fetch("super_admin_user_ids", expires_in: 12.hours) do
  User.super_admins.pluck(:id)
end
User.where(id: ids).to_a

1.8 SQLキャッシュ

クエリキャッシュは、各クエリの結果セットをキャッシュするRailsの機能です。Railsは同じクエリが再度実行される場合、データベースに対してクエリを実行する代わりに、キャッシュされた結果セットを使用します。

例えば:

class ProductsController < ApplicationController
  def index
    # findクエリを実行する
    @products = Product.all

    # ...

    # 同じクエリを再度実行する
    @products = Product.all
  end
end

同じクエリがデータベースに対して再度実行される場合、実際にはデータベースにアクセスしません。最初の結果はクエリキャッシュ(メモリ上)に保存され、2回目はメモリから取得されます。

ただし、クエリキャッシュはアクションの開始時に作成され、アクションの終了時に破棄されるため、アクションの実行時間のみ有効です。より永続的な方法でクエリ結果を保存したい場合は、低レベルキャッシュを使用することができます。

2 キャッシュストア

Railsは、SQLキャッシュやページキャッシュ以外のキャッシュデータのために異なるストアを提供しています。

2.1 設定

config.cache_store設定オプションを設定することで、アプリケーションのデフォルトのキャッシュストアを設定することができます。その他のパラメータは、キャッシュストアのコンストラクタに引数として渡すことができます。

config.cache_store = :memory_store, { size: 64.megabytes }

または、設定ブロックの外部でActionController::Base.cache_storeを設定することもできます。

Rails.cacheを呼び出すことでキャッシュにアクセスすることができます。

2.1.1 コネクションプールオプション

デフォルトでは、:mem_cache_store:redis_cache_storeはコネクションプーリングを使用するように設定されています。これは、Pumaなどのスレッド型サーバーを使用している場合、複数のスレッドが同時にキャッシュストアにクエリを実行できることを意味します。 接続プーリングを無効にする場合は、キャッシュストアを設定する際に:poolオプションをfalseに設定します。

config.cache_store = :mem_cache_store, "cache.example.com", pool: false

また、poolオプションに個別のオプションを指定することで、デフォルトのプール設定を上書きすることもできます。

config.cache_store = :mem_cache_store, "cache.example.com", pool: { size: 32, timeout: 1 }
  • :size - このオプションはプロセスごとの接続数を設定します(デフォルトは5)。

  • :timeout - このオプションは接続を待機する秒数を設定します(デフォルトは5)。タイムアウト内に利用可能な接続がない場合、Timeout::Errorが発生します。

2.2 ActiveSupport::Cache::Store

ActiveSupport::Cache::Storeは、Railsでキャッシュとのやり取りを行うための基盤を提供します。これは抽象クラスであり、単独では使用することはできません。代わりに、ストレージエンジンに結び付けられた具体的な実装クラスを使用する必要があります。Railsにはいくつかの実装が付属しており、以下で説明します。

主なAPIメソッドはreadwritedeleteexist?fetchです。

キャッシュストアのコンストラクタに渡されたオプションは、適切なAPIメソッドのデフォルトオプションとして扱われます。

2.3 ActiveSupport::Cache::MemoryStore

ActiveSupport::Cache::MemoryStoreは、エントリをRubyプロセス内のメモリに保持します。キャッシュストアは、イニシャライザにsizeオプションを送信して指定されたサイズで制限されます(デフォルトは32MB)。キャッシュが割り当てられたサイズを超えると、クリーンアップが行われ、最も最近使用されていないエントリが削除されます。

config.cache_store = :memory_store, { size: 64.megabytes }

複数のRuby on Railsサーバープロセスを実行している場合(Phusion Passengerやpumaのクラスタモードを使用している場合)、Railsサーバープロセスのインスタンスは互いにキャッシュデータを共有できません。このキャッシュストアは、大規模なアプリケーション展開には適していません。ただし、数個のサーバープロセスだけで運営される小規模で低トラフィックのサイトや開発・テスト環境ではうまく機能します。

新しいRailsプロジェクトは、デフォルトで開発環境でこの実装を使用するように設定されています。

注意::memory_storeを使用すると、プロセスがキャッシュデータを共有しないため、Railsコンソールを介してキャッシュを手動で読み取ったり、書き込んだり、期限を切ったりすることはできません。

2.4 ActiveSupport::Cache::FileStore

ActiveSupport::Cache::FileStoreは、エントリをファイルシステムに格納します。キャッシュを初期化する際に、ストアファイルが格納されるディレクトリのパスを指定する必要があります。

config.cache_store = :file_store, "/path/to/cache/directory"

このキャッシュストアでは、同じホスト上の複数のサーバープロセスがキャッシュを共有できます。このキャッシュストアは、1つまたは2つのホストから提供される低から中程度のトラフィックのサイトに適しています。異なるホストで実行されるサーバープロセスは、共有ファイルシステムを使用してキャッシュを共有することもできますが、この設定は推奨されません。

キャッシュはディスクがいっぱいになるまで成長するため、定期的に古いエントリを削除することをおすすめします。

これは、明示的なconfig.cache_storeが指定されていない場合、デフォルトのキャッシュストア実装("#{root}/tmp/cache/")です。

2.5 ActiveSupport::Cache::MemCacheStore

ActiveSupport::Cache::MemCacheStoreは、Dangaのmemcachedサーバーを使用してアプリケーションの中央キャッシュを提供します。Railsはデフォルトでバンドルされたdalliジェムを使用します。これは現在、プロダクションのウェブサイトで最も人気のあるキャッシュストアです。非常に高いパフォーマンスと冗長性を持つ単一の共有キャッシュクラスタを提供するために使用できます。

キャッシュを初期化する際に、クラスタ内のすべてのmemcachedサーバーのアドレスを指定するか、MEMCACHE_SERVERS環境変数が適切に設定されていることを確認する必要があります。

config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"

どちらも指定されていない場合、デフォルトではmemcachedがlocalhostのデフォルトポート(127.0.0.1:11211)で実行されているものと見なされますが、これは大規模なサイトには理想的な設定ではありません。

config.cache_store = :mem_cache_store # $MEMCACHE_SERVERS、次に127.0.0.1:11211にフォールバックします

サポートされるアドレスのタイプについては、Dalli::Clientのドキュメントを参照してください。

このキャッシュのwrite(およびfetch)メソッドは、memcached固有の機能を利用するための追加オプションを受け入れます。

2.6 ActiveSupport::Cache::RedisCacheStore

ActiveSupport::Cache::RedisCacheStoreは、Redisが最大メモリに達したときに自動的にエントリを削除するRedisのサポートを利用し、Memcachedキャッシュサーバーのように動作することができます。

デプロイメントの注意:Redisはデフォルトではキーの有効期限が切れないため、専用のRedisキャッシュサーバーを使用するように注意してください。永続的なRedisサーバーを揮発性のあるキャッシュデータで埋めることは避けてください!詳細については、Redisキャッシュサーバーのセットアップガイド( https://redis.io/topics/lru-cache )を読んでください。

キャッシュ専用のRedisサーバーの場合、maxmemory-policyをallkeysのバリアントの1つに設定します。Redis 4以降では、最も使用頻度の低いエントリ(allkeys-lfu)を選択するのが最適な選択肢です。Redis 3以前では、最も最近使用されていないエントリ(allkeys-lru)を使用する必要があります。 キャッシュの読み取りと書き込みのタイムアウトを比較的低く設定します。キャッシュされた値を再生成する方が、それを取得するために1秒以上待つよりも速いことがよくあります。読み取りと書き込みのタイムアウトはデフォルトで1秒に設定されていますが、ネットワークが一貫して低遅延である場合はさらに低く設定することもできます。

デフォルトでは、接続がリクエスト中に失敗した場合、キャッシュストアはRedisに再接続しようとしません。頻繁な切断が発生する場合は、再接続を有効にすることを検討してください。

キャッシュの読み取りと書き込みは例外を発生させることはありません。代わりに、キャッシュに何もないかのようにnilを返します。キャッシュが例外を発生しているかどうかを判断するために、error_handlerを提供して例外収集サービスに報告することができます。これは、method(元々呼び出されたキャッシュストアのメソッド)、returning(通常はnilの値)、exception(救出された例外)の3つのキーワード引数を受け入れる必要があります。

始めるには、Gemfileにredis gemを追加します:

gem 'redis'

最後に、関連するconfig/environments/*.rbファイルに設定を追加します:

config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }

より複雑なプロダクション向けのRedisキャッシュストアは、次のようになります:

cache_servers = %w(redis://cache-01:6379/0 redis://cache-02:6379/0)
config.cache_store = :redis_cache_store, { url: cache_servers,

  connect_timeout:    30,  # デフォルトは20秒です
  read_timeout:       0.2, # デフォルトは1秒です
  write_timeout:      0.2, # デフォルトは1秒です
  reconnect_attempts: 1,   # デフォルトは0です

  error_handler: -> (method:, returning:, exception:) {
    # エラーをSentryに警告として報告する
    Sentry.capture_exception exception, level: 'warning',
      tags: { method: method, returning: returning }
  }
}

2.7 ActiveSupport::Cache::NullStore

ActiveSupport::Cache::NullStoreは、各ウェブリクエストにスコープがあり、リクエストの終了時に格納された値をクリアします。開発環境やテスト環境で使用することを目的としています。Rails.cacheと直接対話するコードがある場合に、キャッシュがコードの変更結果の表示に干渉する場合に非常に便利です。

config.cache_store = :null_store

2.8 カスタムキャッシュストア

ActiveSupport::Cache::Storeを拡張し、適切なメソッドを実装するだけで、独自のカスタムキャッシュストアを作成することができます。これにより、任意の数のキャッシュ技術をRailsアプリケーションに組み込むことができます。

カスタムキャッシュストアを使用するには、キャッシュストアをカスタムクラスの新しいインスタンスに設定するだけです。

config.cache_store = MyCacheStore.new

3 キャッシュキー

キャッシュで使用されるキーは、cache_keyまたはto_paramに応答する任意のオブジェクトである必要があります。カスタムキーを生成する必要がある場合は、クラスにcache_keyメソッドを実装することができます。Active Recordは、クラス名とレコードIDに基づいてキーを生成します。

ハッシュや値の配列をキャッシュキーとして使用することもできます。

# これは有効なキャッシュキーです
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])

Rails.cacheで使用するキーは、実際にストレージエンジンで使用されるキーとは異なる場合があります。ネームスペースに変更が加えられたり、テクノロジーバックエンドの制約に合わせて変更されたりする可能性があります。つまり、Rails.cacheで値を保存してからdalli gemで取り出そうとすることはできません。ただし、memcachedのサイズ制限を超えたり、構文規則を違反したりする心配もする必要はありません。

4 条件付きGETのサポート

条件付きGETは、Webサーバーがブラウザに対して、GETリクエストのレスポンスが前回のリクエスト以降変更されていないため、ブラウザキャッシュから安全に取得できることを伝えるHTTP仕様の機能です。

これらは、HTTP_IF_NONE_MATCHおよびHTTP_IF_MODIFIED_SINCEヘッダーを使用して、一意のコンテンツ識別子とコンテンツが最後に変更されたタイムスタンプを送受信することで機能します。ブラウザがコンテンツ識別子(ETag)または最終変更日時のタイムスタンプがサーバーのバージョンと一致するリクエストを行う場合、サーバーは変更されていないステータスの空のレスポンスを送信するだけで済みます。

最終変更日時とif-none-matchヘッダーを探し、完全なレスポンスを送信するかどうかを決定するのは、サーバー(つまり私たち)の責任です。Railsの条件付きGETサポートを使用すると、これは非常に簡単なタスクです:

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])

    # リクエストが指定されたタイムスタンプとバージョン付きキャッシュキーに基づいて古くなっている場合
    # (つまり、再処理が必要な場合)は、このブロックを実行します
    if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key_with_version)
      respond_to do |wants|
        # ... 通常のレスポンス処理
      end
    end

    # リクエストが新しい場合(つまり、変更されていない場合)は、何もする必要はありません。デフォルトのレンダリングは、前回のstale?呼び出しで使用されたパラメータを使用してこれをチェックし、自動的に:not_modifiedを送信します。以上です、終わりです。
  end
end

オプションハッシュの代わりに、単純にモデルを渡すこともできます。Railsは、updated_atメソッドとcache_key_with_versionメソッドを使用して、last_modifiedetagを設定します。

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])

    if stale?(@product)
      respond_to do |wants|
        # ...通常のレスポンス処理
      end
    end
  end
end

特別なレスポンス処理がなく、デフォルトのレンダリングメカニズムを使用している場合(つまり、respond_toを使用していないか、自分でレンダリングを呼び出していない場合)、fresh_whenには簡単なヘルパーがあります。

class ProductsController < ApplicationController
  # リクエストが新しい場合は自動的に :not_modified を返し、
  # 古い場合はデフォルトのテンプレート(product.*)をレンダリングします。

  def show
    @product = Product.find(params[:id])
    fresh_when last_modified: @product.published_at.utc, etag: @product
  end
end

時には、決して期限切れにならない静的なページなどのレスポンスをキャッシュしたい場合があります。これを実現するために、http_cache_foreverヘルパーを使用することができます。これにより、ブラウザとプロキシがそれを無期限にキャッシュすることができます。

デフォルトでは、キャッシュされたレスポンスはプライベートであり、ユーザーのウェブブラウザにのみキャッシュされます。プロキシがレスポンスをキャッシュできるようにするには、public: trueを設定して、キャッシュされたレスポンスをすべてのユーザーに提供できることを示します。

このヘルパーを使用すると、last_modifiedヘッダーはTime.new(2011, 1, 1).utcに設定され、expiresヘッダーは100年に設定されます。

警告: ブラウザ/プロキシは、ブラウザキャッシュが強制的にクリアされない限り、キャッシュされたレスポンスを無効にすることはできませんので、このメソッドを注意して使用してください。

class HomeController < ApplicationController
  def index
    http_cache_forever(public: true) do
      render
    end
  end
end

4.1 強力な ETag と弱い ETag

Railsはデフォルトで弱いETagを生成します。弱いETagは、意味的に同等のレスポンスが完全に一致しない場合でも、同じETagを持つことができます。これは、レスポンスボディの細微な変更でページを再生成したくない場合に便利です。

弱いETagは、先頭に W/ を付けて強いETagと区別されます。

W/"618bbc92e2d35ea1945008b42799b0e7" → 弱いETag
"618bbc92e2d35ea1945008b42799b0e7" → 強いETag

弱いETagとは異なり、強いETagはレスポンスが完全に同じであり、バイト単位で同一であることを意味します。大きなビデオやPDFファイル内で範囲リクエストを行う場合に便利です。Akamaiのような一部のCDNは、強いETagのみをサポートしています。絶対に強いETagを生成する必要がある場合は、次のように行うことができます。

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
    fresh_when last_modified: @product.published_at.utc, strong_etag: @product
  end
end

また、強いETagをレスポンスに直接設定することもできます。

response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7"

5 開発中のキャッシュ

開発モードでアプリケーションのキャッシュ戦略をテストしたいことは一般的です。Railsは、キャッシュのオン/オフを簡単に切り替えるためのdev:cacheコマンドを提供しています。

$ bin/rails dev:cache
開発モードがキャッシュされています。
$ bin/rails dev:cache
開発モードのキャッシュは無効になっています。

デフォルトでは、開発モードのキャッシュがオフの場合、Railsは:null_storeを使用します。

6 参考文献

フィードバック

このガイドの品質向上にご協力ください。

タイポや事実の誤りを見つけた場合は、ぜひ貢献してください。 開始するには、ドキュメントへの貢献セクションを読んでください。

不完全なコンテンツや最新でない情報も見つかるかもしれません。 メインのドキュメントに不足しているドキュメントを追加してください。 修正済みかどうかは、まずEdge Guidesを確認してください。 スタイルと規約については、Ruby on Rails Guides Guidelinesを確認してください。

修正すべき点を見つけたが、自分で修正できない場合は、 問題を報告してください

そして最後に、Ruby on Railsのドキュメントに関するあらゆる議論は、公式のRuby on Railsフォーラムで大歓迎です。