ActiveSupport::InheritableOptions の `to_h` がネストでもハッシュを返すよう修正

rails/rails

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

この差分により、@parentInheritableOptions であってもその 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

テストは optionsnested の両方で同一の Hash が得られることを確認し、実装変更の正当性を保証します。このように最小限のコード修正とテスト追加で問題を解決できました。

設計判断

今回の修正は、to_h の内部ロジックを親に対して再帰的に適用するだけというシンプルなアプローチです。メソッドシグネチャや公開 API は一切変更せず、既存の呼び出し側コードに影響を与えない点が重要な設計判断となります。これにより、下位互換性を保ちつつバグを根本的に解消しました。

代替案としては新しい設定キーやオプションを導入して明示的にハッシュ化を指示する方法が考えられましたが、API の肥大化や既存コードの修正負荷が増大するリスクがありました。そのため、最小侵入かつ期待通りの振る舞いを保証できる現在の実装変更が最適と判断されました。

結果として、ActiveSupport::InheritableOptions の継承機構は深い階層でも一貫したハッシュ表現を提供し、プラグインやエンジンが安全に設定をコピーできるようになりました。

まとめ

ActiveSupport::InheritableOptions#to_h が親が同クラスである場合でも正しくハッシュを返すよう修正されたことで、設定継承の信頼性が向上しました。変更は一行の実装差分とテスト追加に留まり、既存コードへの影響はなく、Rails の内部設定機構全体に対する安全性が高まりました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
108cd1a7

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的変更、設計判断(任意)およびまとめがすべて揃っており、総論→各論→結論の構成が明確です。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックのファイル名付きハイライトは正しい形式ですが、PRリンクの記法が「[PR #57743](URL)」となっており、要求された「[#57743](URL)」の形式になっていません。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Rails の内部実装に詳しいエンジニア向けの内容で、過度な基礎説明はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論・結論の構成になり、段落はトピックセンテンスで始まり 1 段落 1 トピック、文数も 6 文以内に収まっています。空行で区切られています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードブロックは Diff の変更内容と完全に一致し、ファイルパスも正しいです。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

用語は PR の記述と一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的説明は PR の背景と変更点に合致し、論理的に正しいです。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

全ての主張は PR の情報・Diff に裏付けられており、推測や根拠のない記述はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号 #57743 などの数値は正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR タイトルの内容を正確に日本語化しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

外部知識(バージョンサポート状況等)は一切記載されていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の記述はなく、矛盾もありません。