Ruby 4.0のdelegator警告を回避するためinspectメソッドの実装を修正
Ruby 4.0で導入されたKernel#instance_variables_to_inspectメソッドにより、DelegateClassのサブクラスでinspectを呼び出すと警告が発生する問題が修正されました。この変更は、RailsがRuby 4.0と互換性を保ちながら、既存のinspect実装の意図を維持するものです。
背景
Ruby 4.0では、Kernel#inspectが内部でrespond_to?(:instance_variables_to_inspect, true)を呼び出すようになりました。このメソッドはKernelにprivateメソッドとして新たに追加されたものです。
RailsにはDelegateClassのサブクラスでdefine_method(:inspect, Kernel.instance_method(:inspect))を使ってinspectメソッドをオーバーライドしているクラスが2つ存在します:
ActiveModel::Attributes::Normalization::NormalizedValueTypeActiveRecord::Type::Serialized
これらのクラスでinspectを呼び出すと、instance_variables_to_inspectへのrespond_to?呼び出しがdelegatorの委譲チェーンに入り、Delegator#respond_to_missing?がprivateメソッドを検出して以下の警告を発生させます:
warning: delegator does not forward private method #instance_variables_to_inspect
#56756で報告されたこの問題は、Ruby 4.0へのアップグレード時にテストの実行に影響を与えるものでした。
技術的な変更
影響を受ける2つのクラスに、Kernel#instance_variablesにバインドされたprivate instance_variables_to_inspectメソッドが追加されました。
activemodel/lib/active_model/attributes/normalization.rbへの追加:
private
# Prevent Ruby 4.0 "delegator does not forward private method" warning.
# Kernel#inspect calls instance_variables_to_inspect which, without this,
# triggers Delegator#respond_to_missing? for a private method.
define_method(:instance_variables_to_inspect, Kernel.instance_method(:instance_variables))
activerecord/lib/active_record/type/serialized.rbへの追加:
private
# Prevent Ruby 4.0 "delegator does not forward private method" warning.
# Kernel#inspect calls instance_variables_to_inspect which, without this,
# triggers Delegator#respond_to_missing? for a private method.
define_method(:instance_variables_to_inspect, Kernel.instance_method(:instance_variables))
instance_variables_to_inspectをdelegatorクラス自身に直接定義することで、respond_to?呼び出しは委譲チェーンに入ることなくメソッドを発見します。メソッドはKernel#instance_variablesにバインドされているため、delegator自身のインスタンス変数を返します。これは、define_method(:inspect, Kernel.instance_method(:inspect))がdelegator自身を検査する意図であることと一貫しています。
メソッド呼び出しチェーンの変化
修正前:
-
Kernel#inspect(delegatorにバインド) respond_to?(:instance_variables_to_inspect, true)- ActiveSupportの
respond_to_missing?—include_privateなしで委譲、falseを返す -
super→ RubyのDelegator#respond_to_missing?— privateメソッドを検出 → 警告発生
修正後:
-
Kernel#inspect(delegatorにバインド) -
respond_to?(:instance_variables_to_inspect, true)→true(直接定義されている) -
instance_variables_to_inspectを呼び出し → delegator自身のインスタンス変数を返す - 委譲なし、警告なし
Ruby 3.x以前では、Kernel#inspectがinstance_variables_to_inspectを呼び出さないため、このメソッドは定義されても使用されません。したがって、この変更は後方互換性を保ちます。
設計判断
instance_variables_to_inspectをラップされたオブジェクトに委譲するのではなく、Kernel#instance_variablesにバインドする設計が採用されました。
この判断は、既存のdefine_method(:inspect, Kernel.instance_method(:inspect))の意図を尊重したものです。この実装は、ラップされたオブジェクトではなくdelegator自身を検査するためのものであり、instance_variables_to_inspectもdelegator自身のインスタンス変数を返すべきです。
ラップされたオブジェクトのインスタンス変数を返すようにすると、inspectの出力が意図しない情報を含むことになり、元の実装の目的と矛盾します。Kernel#instance_variablesへのバインドは、最小限の変更で既存の動作を維持しながら、Ruby 4.0の新しい内部実装に対応するものです。
本PRは、Ruby 4.0のKernel#inspect実装の変更に対応しつつ、Railsの既存のinspect実装の意図を維持した修正です。privateメソッドをdelegatorクラス自身に定義することで委譲チェーンを回避し、警告を防ぎながら後方互換性を保っています。