Ruby 4.1 のエラーメッセージ変更に対応した比較バリデーションのテスト修正
Ruby 4.1.0dev での比較エラーメッセージの文言変更により壊れていた CI テストを、正規表現マッチングに切り替えることで修正しました。
背景
Rails Nightly CI が Ruby 4.1.0dev 環境で test_validates_comparison_of_incomparables に失敗するようになりました。原因は Ruby 本体のエラーメッセージ変更にあり、Rails 側のテストが特定の文字列に完全一致していたことで、その変更を検知できなくなっていました。
問題のある比較(Integer と String の比較)を実行したとき、Ruby 4.1.0dev では以下のようにメッセージの末尾に詳細な理由が付加されるようになっています。
# Ruby ~4.0
"comparison of Integer with String failed"
# Ruby 4.1.0dev
"comparison of Integer with String failed: coercion was not possible"
このメッセージ変更は ruby/ruby#16321 で導入されました。同 PR では Array#sort などで nil が比較値として返った際に「comparison of String with String failed」という誤解を招くメッセージが出る問題を改善するため、rb_cmperr に理由文字列を付加する仕組みが追加されています。これにより「coercion was not possible」や「comparator returned nil」といった追加情報が末尾に付くようになりました。
技術的な変更
変更は activemodel/test/cases/validations/comparison_validation_test.rb の 2 箇所のみで、完全一致を正規表現の前方一致に置き換えています。
変更前:
assert_invalid_values([12], "comparison of Integer with String failed")
変更後:
assert_invalid_values([12], /\Acomparison of Integer with String failed/)
あわせて、assert_invalid_values ヘルパー内部のアサーションも assert_equal から assert_match に変更されています。
変更前:
assert_equal error, topic.errors[:approved].first if error
変更後:
assert_match error, topic.errors[:approved].first if error
assert_match は引数に文字列を渡すと include? 相当の部分一致、正規表現を渡すと =~ によるマッチを行います。正規表現 /\Acomparison of Integer with String failed/ の \A は文字列先頭を意味するため、メッセージの書き出しが正しい形式であることを検証しつつ、末尾の追加情報には非依存になります。
設計判断
エラーメッセージの検証を「先頭一致」に緩和する方針 が採用されました。
完全一致から単なる部分一致(include?)に緩和することもできますが、正規表現の \A アンカーを使うことで「正しいエラーの種別を表すプレフィックスで始まる」という意味のある制約を維持しています。また、assert_invalid_values ヘルパーが error 引数として文字列と正規表現の両方を受け取れるよう assert_match に統一されており、将来のテスト追加時にもいずれの型でも渡せる柔軟性が生まれています。
まとめ
この修正は Ruby 本体の改善(エラーメッセージへの理由付加)を Ruby バージョン間で吸収するための最小限の対応です。正規表現の \A アンカーによる前方一致を採用することで、テストの意図(Integer と String の比較失敗を検出すること)を損なわずに Ruby 4.1 以降への追従が実現されています。