`NumericalityValidator`の`:in`オプションでProcとSymbolをサポート

rails/rails

NumericalityValidator:inオプションにProcとSymbolを渡せるようになり、バリデーション範囲を動的に決定できるようになりました。

背景

これまで NumericalityValidator:inオプションは、Rangeオブジェクトのみを受け付けていました。範囲がレコードの状態や他の属性値に依存する場合でも、静的な範囲しか指定できず、動的な範囲バリデーションを実現するには独自のカスタムバリデータを実装する必要がありました。

:presence:lengthなど他のバリデータではすでにProcやSymbolによる動的オプションが広くサポートされており、:inオプションでの非サポートは一貫性の欠如でもありました。本PRはこのギャップを埋めています。

技術的な変更

check_validity!validate_eachの2箇所に変更が加えられ、起動時の型チェックとバリデーション実行時の動的解決が分離されました。

check_validity!の変更(起動時チェック):

変更前は:inの値がRange以外の場合に即座にArgumentErrorを発生させていました。

# 変更前
unless value.is_a?(Range)
  raise ArgumentError, ":#{option} must be a range"
end

変更後はProcSymbolも受け付けるよう条件が拡張されています。

# 変更後
unless value.is_a?(Range) || value.is_a?(Proc) || value.is_a?(Symbol)
  raise ArgumentError, ":#{option} must be a range, a symbol or a proc"
end

validate_eachの変更(バリデーション実行時):

バリデーション実行時に resolve_value を呼び出して動的解決し、解決後の値がRangeであるかを検証するガード節が追加されています。

# 変更後(RANGE_CHECKSブランチ内)
elif RANGE_CHECKS.include?(option)
  option_value = resolve_value(record, option_value)
  unless option_value.is_a?(Range)
    raise ArgumentError, ":#{option} must return a range"
  end
  unless value.public_send(RANGE_CHECKS[option], option_value)
    record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
  end

resolve_valueはActive Modelが既に持つ汎用的なメソッドで、ProcであればcallSymbolであればレコードに対してsendを行います。これにより、バリデーション実行のたびにレコードの最新の状態を参照した動的な範囲評価が実現されます。

設計判断

型チェックを「起動時」と「実行時」の2段階に分けた点が本PRの重要な設計判断です。

check_validity!は定義時(クラスロード時)に呼ばれ、設定値の型がRangeProcSymbolのいずれでもない場合は早期にエラーを通知します。一方、ProcやSymbolが返す値の型チェックはvalidate_each内で実行時に行い、Range以外を返した場合には「:in must return a range」という明確なメッセージでArgumentErrorを発生させます。この2段階構造により、静的な誤設定は起動時に、動的な誤りはバリデーション実行時に、それぞれ適切なタイミングで検出されます。

既存のresolve_valueメソッドを再利用したことで、追加ロジックを最小限に抑えつつ、他のオプションと一貫した実装になっています。

まとめ

本PRにより、:inオプションへのProcとSymbolのサポートが加わり、他のバリデータオプションとの設計上の一貫性が確立されました。静的な範囲では表現できなかった「レコードの状態に依存した数値範囲バリデーション」を、カスタムバリデータを書くことなく宣言的に記述できるようになります。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
429dfb38

この記事は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リンク記法の正確性

ファイル名付きシンタックスハイライトやPR番号のリンク記法など、カスタムMarkdown構文がガイドライン通りに正しく使用されています。

対象読者への適合性 ✓ PASS

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

Railsのバリデーションに関する専門的な内容でありながら、過度な初心者向けの説明がなく、対象読者である専門知識を持つエンジニアに適したレベルで書かれています。

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

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

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

Diff内容との照合 ✓ PASS

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

Diffから引用されたコードは正確であり、変更前後の比較も分かりやすく提示されています。ファイルパスも正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`NumericalityValidator`, `check_validity!`, `resolve_value` といった技術用語が文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

起動時と実行時のチェックの分離や、`resolve_value`の役割など、変更内容に関する技術的な説明は正確かつ論理的です。

事実の突合 ✓ PASS

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

記事の主張はすべてPR情報やDiffで裏付けられています。「設計判断」セクションはコードの分析に基づく妥当な洞察であり、ハルシネーションは見られません。

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

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

PR番号(#46262)が正確に記載・リンクされています。

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

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

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

外部知識の正確性 ✓ PASS

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

PRに記載のないLTSやEOLなどの外部知識の持ち込みはなく、事実に基づいた記述がされています。

時間表現の正確性 ✓ PASS

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

「これまで」と「できるようになった」といった表現が適切に使われており、変更による時間的な前後関係が正しく伝わります。