ActiveSupport::InheritableOptions の `to_h` がネストでもハッシュを返すよう修正
ActiveSupport::InheritableOptions#to_h が親オブジェクトに別の InheritableOptions を保持している場合に、ハッシュではなくオブジェクトを返していた不具合を解消しました。これにより、設定の継承チェーン全体を安全にハッシュ化できるようになりました。
背景
ActiveSupport::InheritableOptions はオプションを継承可能なハッシュとして Rails の内部で広く利用され、設定のコピーやマージに頻繁に用いられます。to_h メソッドはそのオブジェクトを純粋な Hash に変換するための公開 API です。従来の実装では、親が別の InheritableOptions インスタンスである場合、ハッシュ化が行われずにオブジェクト自体が返ってしまうという問題がありました。
この問題は、inheritable_copy によって作成されたオブジェクトをさらにコピーしたときに顕在化します。具体例として、options = ActiveSupport::InheritableOptions.new(one: "first") に対し nested = options.inheritable_copy を行い、nested.to_h を呼び出すと期待した { one: "first" } ではなく #<ActiveSupport::InheritableOptions …> が返っていました。結果として、設定のマージやシリアライズが失敗し、予期しない型エラーが発生するリスクがありました。
このバグは、親子関係が二層以上になるケースでのみ表面化するため、見過ごされやすく、特にプラグインやエンジンが設定を継承する場面で影響が大きいと指摘されていました。修正により、すべての継承レベルで一貫した Hash 表現が保証されます。
技術的な変更
InheritableOptions#to_h の実装を、親オブジェクトの to_h を再帰的に呼び出す形へ変更しました。この変更は 1 行の差分で済んでおり、既存のインターフェースや振る舞いに影響を与えません。具体的には、@parent.merge(self) から @parent.to_h.merge(self) へ置き換えられています。
変更前:
def to_h
@parent.merge(self)
end
変更後:
def to_h
@parent.to_h.merge(self)
end
この差分により、@parent が InheritableOptions であってもその to_h が呼び出され、最終的に普通の Hash が合成されます。加えて、activesupport/test/ordered_options_test.rb に新しいテスト test_nested_inheritable_options_to_h が追加され、ネストされたオブジェクトが正しくハッシュ化されることを自動的に検証します。
追加されたテスト:
def test_nested_inheritable_options_to_h
options = ActiveSupport::InheritableOptions.new(one: "first value")
options[:two] = "second value"
nested = options.inheritable_copy
assert_equal({ one: "first value", two: "second value" }, nested.to_h)
end
テストは options と nested の両方で同一の Hash が得られることを確認し、実装変更の正当性を保証します。このように最小限のコード修正とテスト追加で問題を解決できました。
設計判断
今回の修正は、to_h の内部ロジックを親に対して再帰的に適用するだけというシンプルなアプローチです。メソッドシグネチャや公開 API は一切変更せず、既存の呼び出し側コードに影響を与えない点が重要な設計判断となります。これにより、下位互換性を保ちつつバグを根本的に解消しました。
代替案としては新しい設定キーやオプションを導入して明示的にハッシュ化を指示する方法が考えられましたが、API の肥大化や既存コードの修正負荷が増大するリスクがありました。そのため、最小侵入かつ期待通りの振る舞いを保証できる現在の実装変更が最適と判断されました。
結果として、ActiveSupport::InheritableOptions の継承機構は深い階層でも一貫したハッシュ表現を提供し、プラグインやエンジンが安全に設定をコピーできるようになりました。
まとめ
ActiveSupport::InheritableOptions#to_h が親が同クラスである場合でも正しくハッシュを返すよう修正されたことで、設定継承の信頼性が向上しました。変更は一行の実装差分とテスト追加に留まり、既存コードへの影響はなく、Rails の内部設定機構全体に対する安全性が高まりました。