`eager_load: true` 時に ERBTracker が登録される不具合を修正
eager_load: true の環境で load_defaults 8.1 を使用すると、ERB テンプレートの依存関係トラッキングに ERBTracker が登録されてしまう不具合が修正されました。原因は ActionView.render_tracker の設定タイミングにあり、ネストした after_initialize ブロックを専用の initializer に切り出すことで解消されています。
背景
Rails 8.1 では load_defaults 8.1 により config.action_view.render_tracker = :ruby がデフォルトとなり、ERB テンプレートの依存関係トラッキングに RubyTracker が使われる設計になっています(#55194)。しかし eager_load: true を設定した環境では、実際には ERBTracker が登録されるというリグレッションが #57265 として報告されていました。
問題の症状としては、config.action_view.render_tracker と ActionView.render_tracker の値はどちらも :ruby を返すため、設定自体は正しく見えます。しかし ActionView::DependencyTracker 内部の @trackers テーブルを確認すると、ERB ハンドラに対応するクラスが ERBTracker のままになっていました。設定値の読み取りと実際のトラッカー登録が別タイミングで行われていたことが原因です。
技術的な変更
根本原因は、ActionView.render_tracker= の呼び出しがネストした config.after_initialize ブロックの中に配置されていたことです。外側の after_initialize が実行される時点で内側のブロックが登録されますが、その内側ブロックが実際に実行されるのはすべての after_initialize コールバックが完了した後になります。eager_load: true の場合、クラスのロードが after_initialize の完了前に行われるため、DependencyTracker へのトラッカー登録が間に合わない状態が発生していました。
変更前(actionview/lib/action_view/railtie.rb):
config.after_initialize do |app|
config.after_initialize do
ActionView.render_tracker = config.action_view.render_tracker
end
# ...
end
変更後(actionview/lib/action_view/railtie.rb):
initializer "action_view.set_render_tracker" do |app|
ActionView.render_tracker = app.config.action_view.render_tracker
end
専用の initializer "action_view.set_render_tracker" に切り出すことで、このコールバックは after_initialize よりも前のフェーズで実行されるようになり、eager_load によるクラスロードの前にトラッカー登録が完了します。
あわせて actionview/lib/action_view.rb では、singleton_class.attr_accessor :render_tracker が attr_reader と明示的なセッターに分割されました。新しいセッターは値の代入と同時に DependencyTracker へのトラッカー登録を行います。
singleton_class.attr_reader :render_tracker
def self.render_tracker=(value)
@render_tracker = value
case value
when :ruby
DependencyTracker.register_tracker :erb, DependencyTracker::RubyTracker
else
DependencyTracker.register_tracker :erb, DependencyTracker::ERBTracker
end
value
end
self.render_tracker = :regex
これにより、ActionView.render_tracker= を呼び出すだけで DependencyTracker の状態が常に同期されるようになりました。また DependencyTracker が eager_autoload ブロックに追加され、セッター内で参照可能な状態が保証されています。
設計判断
セッターにサイドエフェクトを持たせる設計 が採用されました。render_tracker= の呼び出し元が DependencyTracker.register_tracker を別途呼ぶ必要がなくなり、値の設定とトラッカー登録の一貫性がセッター自身によって保証されます。
attr_accessor を attr_reader + カスタムセッターに置き換えたことで、将来的に :ruby / :regex 以外の値が追加された場合も、セッター内の case 文を拡張するだけで対応できる構造になっています。PR では :ruby 以外はすべて ERBTracker にフォールバックする実装が選ばれており、未知の値に対して安全なデフォルト動作を維持しています。
テスト面では actionview/test/template/dependency_tracker_test.rb に render_tracker の変更が DependencyTracker へ即座に反映されることを確認するユニットテストが、railties/test/application/configuration_test.rb には eager_load: true かつ enable_reloading: false の本番相当環境での統合テストが追加されています。
まとめ
本 PR は初期化フェーズの順序問題とセッターの副作用欠如という2つの独立した問題を同時に解消しています。initializer による実行タイミングの正規化と、セッターへのトラッカー登録ロジックの集約により、eager_load: true 環境での依存関係トラッキングの正確性が担保されました。