`in_order_of` で範囲外整数を渡した際の `RangeError` を修正

rails/rails

ActiveRecord::QueryMethods#in_order_of に範囲外の整数を渡すと発生していた ActiveModel::RangeError を修正し、Enumerable 版と同様に無効な値を無視する挙動に統一しました。

背景

#44745 として報告されたこのバグでは、4バイト整数の上限を超えた値を in_order_of に渡すと例外が発生していました。9999999999999 のような大きな整数はもちろん、文字列として渡した "9999999999999" でも同様に ActiveModel::RangeError: 9999999999999 is out of range for ActiveModel::Type::Integer with limit 4 bytes が発生します。

この問題の根本原因は #43322 の変更にあります。#43322 はenum(文字列)を整数カラムと比較できるよう type_cast_for_database を導入しましたが、この実装はサポート外の値も一律でキャストしようとするため、範囲外整数では例外を投げてしまっていました。一方、Enumerablein_order_of は範囲外の値を単に無視するため、ARとEnumerableで挙動が異なるという一貫性の問題も存在していました。

技術的な変更

query_methods.rbin_order_of メソッドで、type_cast_for_database の一律呼び出しを caster.serializable? による条件付きシリアライズに置き換えました。

変更前:

values = values.map { |value| model.type_caster.type_cast_for_database(column, value) }
arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)

変更後:

if column.is_a?(Arel::Nodes::SqlLiteral)
  arel_column = column
else
  arel_column = order_column(column.to_s)

  caster = arel_column.type_caster
  values = values.map do |value|
    caster.serialize(value) if caster.serializable?(value)
  end
end

caster.serializable?(value)false を返す値は nil にマップされます。これにより、範囲外の整数や変換不能な値は ORDER BY CASE 式の対象から実質的に除外されます。この手法はすでに Arel::Nodes::HomogeneousInArel::Visitors::ToSQL でも採用されており、コードベース内での一貫したアプローチです。

あわせて field_ordered_values_test.rb にテストが追加されました。Post.type_for_attribute(:id).send(:max_value) で上限値を取得し、それを含む配列を渡しても例外が発生せず、範囲内の有効なIDのみ返ることを検証しています。

設計判断

serializable? による事前チェックを挟んでキャストをスキップする方式が採用されました。

SqlLiteral カラムの場合はキャスト処理をそもそも行わないよう条件分岐も整理されています。変更前は三項演算子で arel_column の生成とvaluesのマップを同じ行に並べており、SqlLiteral の場合でも type_cast_for_database が呼ばれる実装になっていました。変更後はブロックを分離することで、SqlLiteral 時はキャストをスキップするという意図が明示的になっています。

無効な値をエラーにするのではなく無視する設計は WHERE id IN (...) の挙動(Post.where(id: [1, 9999999999999]) は範囲外を無視する)とも整合しており、Rails全体で一貫したセマンティクスを持たせる意図が読み取れます。

まとめ

この修正は in_order_of のARとEnumerable実装間の挙動の差異を解消し、無効な値を無視するという一貫したセマンティクスを実現します。serializable? チェックの導入により例外を防ぎながら、既存の #43322 で実現したenum文字列のキャスト機能も維持されています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
4251bf51

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

「総論→各論→結論」の構成が明確で、リード文、背景、技術詳細、設計判断、まとめの各要素が適切に配置されています。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライトやGitHubのIssue/PRへのリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

ActiveRecordの内部実装に関するトピックであり、専門的な用語が適切に使用されているため、対象読者であるエンジニアに適した内容です。

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

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

各セクション、各パラグラフが「総論→各論」の構造で書かれており、トピックセンテンスが明確です。段落の長さも適切で非常に読みやすいです。

Diff内容との照合 ✓ PASS

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

記事で引用されているコードは、提供されたDiff情報と完全に一致しており、正確に内容を反映しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

ActiveRecordやArelに関する技術用語が文脈に応じて正確に使用されています。

説明の技術的正確性 ✓ PASS

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

コード変更の技術的な説明(`serializable?`の役割など)が論理的かつ正確です。

事実の突合 ⚠ WARNING

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

大半の主張はPR情報で裏付けられていますが、「設計判断」セクションにおける `WHERE id IN (...)` の挙動との比較は、PRに明記されていない推測に基づいています。

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

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

PR番号、Issue番号、コード例内の数値など、すべての数値・固有名詞が正確です。

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

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

記事のタイトルはPRのタイトルと主題を正確に反映しています。

外部知識の正確性 ✓ PASS

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

バージョン情報やリリース予定など、PR情報に基づかない検証不可能な外部知識の持ち込みはありません。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲や不正確な使用は見られません。