Rails comparison of Date with nil failed
日付範囲の入力欄の実装でI18n対応がうまくいかず原因を調査したところ、
バリデーション時に、Rails内部でエラーが起きていることが分かりました。
どのようにI18nの修正を行なったのかメモを残します。
不具合の再現
1:ディレクトリ指定でGemインストール
bundle config --local path vendor/bundle
2:スキャフォールド実施
bundle exec rails g scaffold Sample started_on:date ended_on:date
3:バリデーションの追加
class Sample < ApplicationRecord
validates :ended_on, comparison: { greater_than: :started_on },
allow_blank: true
end
4:migate up
bundle exec rails db:migrate:up VERSION=XXXXX
started_onよりもended_onの値を小さくして保存を行う。
「started_onは、ended_onよりも大きい」に近いメッセージが表示されることを期待していたが、実際は「Ended on comparison of Date with nil failed」と表示された。
5:rails console
record = Sample.new(ended_on: Time.current)
record.valid? # => false
record.errors.full_messages # => ["Ended on comparison of Date with nil failed"]
原因を調べた流れ
full_messageを調べることにした。
full_messagesメソッドの位置を調べた。
activemodel-7.0.8.7/lib/active_model/error.rb:160
messageメソッドの位置を調べた。
activemodel-7.0.8.7/lib/active_model/error.rb:131
raw_typeメソッドの位置を調べた。
activemodel-7.0.8.7/lib/active_model/error.rb:126
コメントを元にerrors.addメソッドを調べることにした。
addメソッドの位置を調べた。
activemodel-7.0.8.7/lib/active_model/errors.rb:317
addメソッドの第二引数にtypeとしてcomparison of Date with nil failedが渡ってきている。
callerを読んで呼び出し元を調べた。
activemodel-7.0.8.7/lib/active_model/validations/comparison.rb:29
validate_eachメソッドの中身を読んだ。
Dateのインスタンス.public_send(:>, nil)を呼び出しraiseしていた。
それをrescueで拾っていた。
comparisonオプションの引数を属性にする場合、
ifオプションで比較対象が存在する時に絞らないとI18nが動作しないことが分かった。
修正して動作確認した
バリデーションを修正
validates :ended_on, comparison: { greater_than: :started_on, if: -> { started_on? } }, allow_blank: true
rails console
record = Sample.new(ended_on: Time.current)
record.valid? # => true
record = Sample.new(started_on: Time.current.tomorrow, ended_on: Time.current)
record.valid? # => false
record.errors.full_messages # => ["Ended on は2025-11-23より大きくしてください"]
gemの変更を取り消した
bundle pristine model
bundle pristine i18n
Discussion