validates_length_ofで:maximumオプションを使うときの挙動
ある属性に対するvalidates_length_ofのオプションとして:maximumを設定する場合に、値としてnilを渡したときのバリデーションの挙動 (activemodel 8.1.3.1)。
:maximumだけ設定するとき
:maximumだけで:minimumがないなら、属性値nilをvalidとみなす。これはAPIとして明記されている仕様。
:maximum- The maximum size of the attribute. Allowsnilby default if not used with:minimum.
require "active_model"
class Item include ActiveModel::Validations
attr_accessor :name
validates_length_of :name, maximum: 10end
item = Item.newitem.name = "*" * 11item.validate #=> false
item.name = nilitem.validate #=> true内部的には、nilが入っているvalueに対してvalue.to_s.lengthとして0を得る。その0と:maximumの値とを比較するので、必ずvalidになる(該当箇所)。
:maximumに追加でallow_nilを設定するとき
そもそも:allow_nilとは、allow_nil: trueで属性値がnilのときにバリデーションをスキップするためのもの。
allow_nil: falseの状態でnilを渡すとinvalidになるようなバリデーションは多い。しかし、allow_nilのふるまい自体は、あくまでもバリデーションをスキップするかどうか、いわばskip_when_nilというものである。
allow_nil: trueのとき
属性値がnilならバリデーションをスキップするので、結果的にvalidになる。
allow_nil: falseのとき
属性値がnilでもバリデーションをスキップしない。そして、長さに関して:maximumしかオプションが設定されていないので、最初に説明したケースと同様、結果はvalidだと感じるかもしれないが、実際はinvalidになる。しかも、エラーメッセージの種別は:too_longになってしまう。
class Item include ActiveModel::Validations
attr_accessor :name
validates_length_of :name, maximum: 10, allow_nil: falseend
# nilだと、長いわけではないのにtoo_longエラーitem.name = nilitem.validate #=> falseitem.errors #=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=name, type=too_long, options={allow_nil: false, count: 10}>]>
# 空文字だと、長さ0の文字列として直感的な結果になるitem.name = ""item.validate #=> trueitem.errors #=> #<ActiveModel::Errors []>内部的には、値がnilだがskip_nil_check?という条件を満たせず(定義、利用箇所)、エラー扱いとなる処理に行ってしまうからのようだった。
この挙動はAPIドキュメントには明記されておらず、実装依存のように見えるし、どうなると正解なのかもちょっとわかりにくい。テストには該当ケースがあるが、エラー内容には言及がなかった。空文字の挙動に揃えるべき?実用上は、別途validates_presence_ofで弾くべきではあるとは思う。