非整数サブレンジでクラッシュしていた Range#include? と Range#=== を安全に修正

rails/rails

ActiveSupport が提供する Range#include?Range#=== の拡張が、排他末端かつ整数以外の要素を持つサブレンジに対して TypeError を発生させていた問題を、Ruby 標準の Range#cover? へ委譲することで根本的に解消しました。この変更により、時間窓や浮動小数点範囲の比較が例外なく正しい真偽値を返すようになります。

背景

ActiveSupport は元々、引数が Range である場合に独自ロジックで包含関係を判定していました。そのロジックは value.max を参照してサブレンジの最大要素を取得し、排他末端かつ整数でないケースでは Range#max が列挙を試みて TypeError を投げるため、(1.0..10.0).include?(2.0...5.0) などの呼び出しがクラッシュしていました。対象となったのは Float, Time, DateTime, TimeWithZone, BigDecimal といった非整数型で、時間ウィンドウや許容帯の表現に頻出するパターンでした。Rails は Ruby 2.6 以降を必須としているため、標準メソッド Range#cover? が引数に Range を受け取る機能を備えている点が修正の鍵となります。

技術的な変更

拡張ロジックは activesupport/lib/active_support/core_ext/range/compare_range.rb に実装されており、===include? の両メソッドで Range 引数時に value.max を計算していました。以下は変更前の抜粋です。

if value.is_a?(::Range)
  # 省略...
  value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
  super(value.first) && (self.end.nil? || value_max.public_send(operator, last))
else
  super
end

修正後は value_max に依存せず、Ruby 標準の cover? に委譲します。

if value.is_a?(::Range)
  cover?(value)
else
  super
end

このシンプルな置き換えにより、排他末端・非整数サブレンジでも例外が発生せず、既存のスカラー比較ロジックはそのまま維持されます。テストも activesupport/test/core_ext/range_ext_test.rb に新規ケースが追加され、FloatTime 系列での正常動作が検証されています。

設計判断

Rails が前提としている Ruby バージョンが Range#cover? の拡張をサポートしている点を利用し、独自の範囲比較ロジックを排除した点が最大の設計判断です。元の実装は整数サブレンジだけを正しく扱うために多数の条件分岐と value.max 呼び出しを組み合わせていましたが、可読性と保守性の観点から標準メソッドへの委譲へと統一しました。スカラー引数に対する else super 部分はそのまま残し、既存の Range#include? / Range#=== の振る舞いは変更していません。結果として、後方互換性を保ちつつ、バグの根源である Range#max の列挙失敗を回避できました。

まとめ

この PR は、ActiveSupport が提供する Range#include?Range#=== のサブレンジ比較を Ruby 標準の Range#cover? に置き換えることで、非整数型サブレンジで発生していた TypeError を根本的に解消しました。コード量は大幅に削減され、可読性と保守性が向上すると同時に、Rails が対象とする Ruby バージョン上での正確な真偽値判定が保証されます。

記事メタデータ

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

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

リード文が要旨を簡潔に示し、背景・技術的変更・設計判断・まとめの各セクションが揃っており、総論→各論→結論の構成が明確です。

カスタムMarkdown構文 ✓ PASS

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

コードブロックは `ruby:ファイルパス` の形式で正しく記述され、PRリンクも `PR #57622` の形で正しいURLが付与されています。

対象読者への適合性 ✓ PASS

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

内容はActiveSupport・Range の内部実装に関する専門的な説明で、エンジニア向けに適切です。初心者向けの余計な解説はありません。

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

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

各セクションは総論パラグラフ→各論パラグラフ→結論パラグラフで構成され、段落はトピックセンテンスで始まり 1 段落 1 トピック、長さも適切です。

Diff内容との照合 ✓ PASS

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

記事のコードブロックは Diff の削除部分と追加部分を正しく抜粋・置換しており、ファイルパスも一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`Range#include?`, `Range#===`, `Range#cover?`, `exclude_end?` などの用語は PR と一致しており、誤用はありません。

説明の技術的正確性 ✓ PASS

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

技術的説明は PR の記述と整合しており、原因・解決策・影響が正確に述べられています。

事実の突合 ✓ PASS

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

記事内の全ての事実は PR のタイトル、説明、Diff、テスト変更で裏付けられており、捏造や推測はありません。

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

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

PR 番号 #57622 が正しく記載されており、他の数値や固有名詞の誤りは見られません。

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

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

記事タイトルは PR の趣旨(非整数サブレンジでの例外発生を修正)を正確に反映しています。

外部知識の正確性 ✓ PASS

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

Rails が Ruby 2.6 以降を必須としている点以外に、PR に記載されていない外部情報は含まれていません。

時間表現の正確性 ✓ PASS

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

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