`http_cache_forever` に `last_modified:` パラメータを追加し、Active Storage に適用
http_cache_forever ヘルパーが last_modified: キーワード引数を受け取れるようになりました。これにより、固定日時ではなくリソースに関連した日時を Last-Modified ヘッダーに設定できるようになります。
背景
これまで http_cache_forever は Last-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_modified が nil(デフォルト)の場合は従来通り 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: を渡すことで、より正確なキャッシュ制御が実現できるようになりました。