ActiveRecord の primary_key 定義で class_attribute の定義場所を修正
ActiveRecord の primary_key 定義に利用される _primary_key_definition を、誤って inherited フックで定義していた箇所を included フックへ移動し、子クラスや孫クラスでの不要な再定義を防止した変更です。
背景
_primary_key_definition が inherited で定義されると、サブクラスが生成されるたびに同属性が再定義される という問題が指摘されました。これは、サブクラスごとに class_attribute の内部状態が再初期化され、期待した継承関係が壊れる原因となります。関連 PR #57775 で primary_key を class attribute にしたことに伴い、正しい定義タイミングが求められるようになりました。
この問題は、抽象クラスや STI(シングルテーブル継承)を利用する際に、親クラスで設定した primary_key が子クラスへ正しく継承されないケースとして顕在化していました。したがって、定義場所の見直しは ActiveRecord の継承モデル全体の一貫性を保つ上で不可欠です。
技術的な変更
class_attribute :_primary_key_definition の定義を included ブロックに移動し、inherited フックからは削除 しました。これにより、コンテナがモジュールに混入された時点で属性が一度だけ定義され、以降のサブクラスは自動的に同属性を継承します。
変更前(抜粋):
def inherited(base)
super
base.class_eval do
class_attribute :_primary_key_definition, instance_accessor: false
@attributes_builder = nil
end
end
変更後(抜粋):
included do
class_attribute :_primary_key_definition, instance_accessor: false
end
テストコードも同様に更新され、primary_key を設定した親クラスと子クラスの両方で _primary_key_definition.name が一致 することを明示的に検証しています。
k = Class.new(ActiveRecord::Base)
subclass = Class.new(k)
k.primary_key = "foo"
assert_equal "foo", k._primary_key_definition&.name
assert_equal "foo", subclass._primary_key_definition&.name
この修正により、class_attribute の再定義が排除され、属性の継承が期待通りに機能するようになりました。
設計判断
モジュールがインクルードされた時点で class_attribute を定義する方針 が採用されました。class_attribute はその性質上、モジュールが混入されたクラスの階層全体で共有されるべきであり、inherited フックでの定義は不必要かつ副作用を招く実装です。
この判断は、後方互換性を保ちつつ最小限のコード変更で問題を根本的に解決 することを目的としています。included ブロックは一度だけ実行されるため、既存の primary_key 設定ロジックに影響を与えることなく、安全に属性定義を統一できます。
まとめ
本 PR は、_primary_key_definition の class_attribute 定義箇所を inherited から included へ移すことで、サブクラス間での不要な再定義を防止し、ActiveRecord の継承モデルにおける属性共有を正しく機能させました。テストの更新により、設定が正しく継承されることが自動的に保証されています。