ActiveModel::Attributesの遅延読み込みを修正
ActiveModel::Attributesモジュールの不適切な即座読み込みが修正されました。この変更により、rails app:updateコマンドなどの実行時間が短縮され、アプリケーションの起動時間も改善されます。
背景
autoload(遅延読み込み)は、実際に参照されるまでモジュールの読み込みを遅延させる仕組みです。しかし、ActiveModel::Attributesでは、autoloadを定義していたにもかかわらず、内部のNormalizationモジュールが親モジュールを参照することで、意図せず即座に読み込まれていました。#56824で報告されたように、この問題はrails app:updateの実行時間を3秒以上増加させる原因となっていました。
app:updateコマンドはアプリケーションの更新時だけでなく、Railsのテストスイート内のapp_generator_testsでも頻繁に実行されます。この遅延が積み重なることで、開発体験とCI実行時間の両方に影響していました。
技術的な変更
activemodel/lib/active_model.rbからAttributesモジュールの定義を削除し、activemodel/lib/active_model/attributes.rb内に移動することで、真の遅延読み込みを実現しました。
変更前:
module ActiveModel
autoload :Attributes
module Attributes
extend ActiveSupport::Autoload
autoload :Normalization
end
end
変更後:
module ActiveModel
autoload :Attributes
end
module ActiveModel
module Attributes
extend ActiveSupport::Autoload
autoload :Normalization
extend ActiveSupport::Concern
# ...
end
end
変更前は、トップレベルでmodule Attributesを定義していたため、Normalizationのautoloadが親モジュールのAttributes定数を参照し、結果として即座に読み込まれていました。変更後は、Attributesモジュールの実体がattributes.rb内に定義されており、autoload :Attributesが実際に参照されるまでattributes.rb全体が読み込まれません。
設計判断
autoloadの定義位置を実モジュール内に移動する方式が採用されました。
Rubyのautoloadは、定数が参照された時点で指定されたファイルを読み込みます。しかし、autoloadを定義する時点でモジュール本体を定義すると、その内部のautoload定義が評価される過程で親モジュールの定数参照が発生し、遅延読み込みが無効化されます。この問題を解決するために、autoloadの定義はトップレベルに残し、モジュールの実体とその内部のautoload定義は別ファイルに分離する設計が選ばれました。
この方式により、ActiveModel::Attributesを使用しないコードパスでは、attributes.rbが一切読み込まれなくなります。rails app:updateのような、属性機能を必要としない処理での起動時間が改善されます。
本PRは、autoloadの定義位置を調整することで、意図しない即座読み込みを防ぎました。モジュール構造を変えることなく、ファイル間の依存関係を整理するだけで、起動時間の大幅な改善を実現しています。