`http_cache_forever` に `last_modified:` パラメータを追加し、Active Storage に適用

rails/rails

http_cache_forever ヘルパーが last_modified: キーワード引数を受け取れるようになりました。これにより、固定日時ではなくリソースに関連した日時を Last-Modified ヘッダーに設定できるようになります。

背景

これまで http_cache_foreverLast-Modified ヘッダーを常に固定値の 2011年1月1日 に設定していました。この固定値は、ブラウザが古い Last-Modified を持つレスポンスを確実にキャッシュするための便宜的な値でしたが、リソースごとに実際の更新日時を持つケースには対応できていませんでした。

Active Storage の ProxyController はまさにこの制約を受けていたコンポーネントです。Blob オブジェクトは created_at という実際の生成日時を持っているにもかかわらず、Last-Modified ヘッダーには 2011年1月1日 という意味のない固定値が設定されていました。

技術的な変更

http_cache_forever メソッドのシグネチャに last_modified: nil が追加され、stale? 呼び出しに渡す last_modified が動的に決定されるようになりました。

変更前:

def http_cache_forever(public: false, &block)
  expires_in 100.years, public: public, immutable: true

  yield if stale?(etag: request.fullpath,
                  last_modified: Time.new(2011, 1, 1).utc,
                  public: public)
end

変更後:

def http_cache_forever(public: false, last_modified: nil, &block)
  expires_in 100.years, public: public, immutable: true

  yield if stale?(etag: request.fullpath,
                  last_modified: (last_modified || Time.new(2011, 1, 1)).utc,
                  public: public)
end

last_modifiednil(デフォルト)の場合は従来通り 2011年1月1日 が使われるため、既存の呼び出し元への影響はありません。

Active Storage の ProxyController#show では、この新しいオプションを利用して @blob.created_at を渡すよう変更されています。

# 変更前
http_cache_forever public: true do

# 変changed後
http_cache_forever public: true, last_modified: @blob.created_at do

テストコードでも、last_modified: を渡したケースが追加されており、指定した日時が Last-Modified レスポンスヘッダーに正確に反映されることが検証されています。

設計判断

デフォルト値を変えず、オプト・インで上書きするアプローチが採用されました。nil をデフォルトとし、nil の場合は既存の固定値 2011年1月1日 にフォールバックする (last_modified || Time.new(2011, 1, 1)).utc という実装は、後方互換性を完全に維持しています。

この設計は、「永続キャッシュ」という http_cache_forever の本来の意図を崩さない判断でもあります。last_modified: を渡さない既存のコントローラーは動作が変わらず、実際のリソース日時を持つコントローラーだけが新しい挙動を選択できます。

まとめ

最小限のインターフェース追加で後方互換性を維持しつつ、Active Storage における Last-Modified ヘッダーの精度を改善した変更です。http_cache_forever を使う独自コントローラーでも、リソースに関連した日時が存在する場合は last_modified: を渡すことで、より正確なキャッシュ制御が実現できるようになりました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
86c00060

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→各セクション(各論)→まとめ(結論)の3部構成が明確に適用されています。背景、技術詳細、設計判断の各要素が揃っており、理想的な構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイルパス付きのシンタックスハイライト、PR番号のリンク記法共に正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

RailsのHTTPキャッシュやActive Storageに関する知識を持つエンジニアを対象としており、専門用語のレベルや説明の粒度が適切です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクション、各パラグラフが「総論→各論」の構成になっており、トピックセンテンスが明確です。段落の長さも適切で、非常に読みやすい構造になっています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードは、提供されたDiffの内容と正確に一致しています。変更前後のコード比較が的確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`http_cache_forever`, `Last-Modified`, `stale?`, `ProxyController`などの技術用語が文脈に沿って正しく使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

コード変更の意図(後方互換性の維持)や、Active Storageへの具体的な影響に関する説明は、技術的に正確かつ論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張は、提供されたPRのDescriptionとDiffの内容に基づいており、根拠のない推測や憶測(ハルシネーション)は見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#57388)やキャッシュの固定日付(2011年1月1日)などの数値・固有名詞は正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRの主題を正確に要約しており、変更の核心と主要な適用例(Active Storage)を的確に伝えています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR情報に含まれない外部知識(バージョンのサポート状況やリリース日程など)の追記はなく、事実に忠実です。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「これまで」「変更後」といった時間表現は、変更の前後関係を正しく反映しており、誤解を招く表現はありません。