InheritableOptions の比較演算子が非 Hash 値で NoMethodError を起こすバグを修正

rails/rails

ActiveSupport::InheritableOptions#== がハッシュでないオブジェクトと比較した際に NoMethodError を発生させていた問題を解消し、== が常に真偽値を返すようにしました。

背景

InheritableOptions#== の実装が to_h == other.to_h に固定されていた ため、StringInteger など #to_h を持たないオブジェクトと比較すると例外がスローされました。この実装は 81d0a29 コミットで導入されたもので、親オプションのマージ結果をハッシュ比較で評価する意図でしたが、非ハッシュ引数を想定していませんでした。

例外はArray#include?やArray#uniq、case式など、#== を内部で呼び出す多数のコア機能で顕在化 します。たとえば [options].include?("x")NoMethodError になるケースが報告され、開発者が意図しないクラッシュに直面していました。

さらに nil との比較で true が返っていた ことも問題でした。nil.to_h が空ハッシュを返すため、空の InheritableOptionsnil と等価と判定され、本来期待すべき false ではなく true になる不整合が生じていました。

技術的な変更

InheritableOptions#== の実装を other.is_a?(Hash) && to_h == other.to_h に変更し、ハッシュ以外のオブジェクトに対しては即座に false を返すようにしました。この guard は Hash#== の振る舞いを忠実に再現し、nil など #to_h を持つがハッシュではないオブジェクトとの比較で誤った true を防ぎます。

@@ -105,7 +105,7 @@ def to_h
     end

     def ==(other)
-      to_h == other.to_h
+      other.is_a?(Hash) && to_h == other.to_h
     end

     def inspect

テストスイートにも test_inheritable_options_equalitytest_inheritable_options_equality_with_non_hash_returns_false を追加し、ハッシュ比較と非ハッシュ比較の両方で期待通りの結果が得られることを確認しました。これにより ordered_options_test.rb 全体が 30 件のテストで 0 失敗となり、修正の正当性が自動的に保証されます。

設計判断

is_a?(Hash) を用いた型チェックは respond_to?(:to_h) よりも限定的 です。nil#to_h を実装しますがハッシュではないため、respond_to? 判定だけでは空 InheritableOptionsnil と等価になる問題が残ります。is_a?(Hash) に絞ることで、HashOrderedOptionsInheritableOptions(マージ結果はハッシュ)との比較は許容しつつ、その他すべての型に対しては false を返す明確な境界を設定しました。

この変更は 後方互換性を維持 しながら、OrderedOptions が継承する Hash#== と同一の契約を InheritableOptions にも適用するという設計方針を示しています。既存コードが options == other_options の形で使用されていても、非ハッシュ引数に対して例外が発生しなくなるため、実運用上の安全性が大幅に向上します。

まとめ

InheritableOptions#== が非ハッシュオブジェクトとの比較で例外を投げていたバグを、ハッシュ型チェックに置き換えることで修正しました。これにより Array#include? などの標準的なコレクション操作が安全に動作し、nil との不適切な等価判定も解消されます。テスト追加により変更の正当性が検証され、Rails の設定系オブジェクトの比較契約が一貫しました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
44cbf09b

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

リード文→背景→技術的変更→設計判断→まとめの3部構成が明確に示されており、全ての必須要素が揃っています。

カスタムMarkdown構文 ✓ PASS

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

コードブロックはdiffハイライトで正しく記述されており、PRへのリンクも `[PR #57621](URL)` の形式で正しくリンク化されています。

対象読者への適合性 ✓ PASS

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

対象はRailsエンジニアであり、専門用語や実装詳細が適切に説明されているため、読者層に適合しています。

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

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

各セクションは総論・各論・結論の段落構成があり、トピックセンテンスで始まり、1段落1トピック・6文未満に収められており、空行で区切られています。

Diff内容との照合 ✓ PASS

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

記事内のdiffブロックは提供されたDiffと完全に一致しており、変更箇所が正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

InheritableOptions、Hash、is_a?、to_h などの用語はPRと一致し、誤用は見られません。

説明の技術的正確性 ✓ PASS

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

技術的な主張はPRの説明とコード変更に基づいており、因果関係も論理的です。

事実の突合 ✓ PASS

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

記事の全ての事実—実装の問題点、修正内容、テスト追加、設計判断—がPR情報で裏付けられ、ハルシネーションはありません。

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

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

PR番号 #57621、コミットハッシュ 81d0a29 など数値・固有名詞は正確です。

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

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

記事タイトルはPRタイトルの内容を日本語で的確に表現しており、一致しています。

外部知識の正確性 ✓ PASS

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

Railsのバージョンやリリーススケジュール等、PRに記載されていない外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、PRの記述と一致しています。