Kōhei Yamamoto

validates_length_ofで:maximumオプションを使うときの挙動

Ruby on Rails

ある属性に対するvalidates_length_ofのオプションとして:maximumを設定する場合に、値としてnilを渡したときのバリデーションの挙動 (activemodel 8.1.3.1)。

:maximumだけ設定するとき

:maximumだけで:minimumがないなら、属性値nilをvalidとみなす。これはAPIとして明記されている仕様

  • :maximum - The maximum size of the attribute. Allows nil by default if not used with :minimum.
require "active_model"
class Item
include ActiveModel::Validations
attr_accessor :name
validates_length_of :name, maximum: 10
end
item = Item.new
item.name = "*" * 11
item.validate #=> false
item.name = nil
item.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: false
end
# nilだと、長いわけではないのにtoo_longエラー
item.name = nil
item.validate #=> false
item.errors #=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=name, type=too_long, options={allow_nil: false, count: 10}>]>
# 空文字だと、長さ0の文字列として直感的な結果になる
item.name = ""
item.validate #=> true
item.errors #=> #<ActiveModel::Errors []>

内部的には、値がnilだがskip_nil_check?という条件を満たせず(定義利用箇所)、エラー扱いとなる処理に行ってしまうからのようだった。

この挙動はAPIドキュメントには明記されておらず、実装依存のように見えるし、どうなると正解なのかもちょっとわかりにくい。テストには該当ケースがあるが、エラー内容には言及がなかった。空文字の挙動に揃えるべき?実用上は、別途validates_presence_ofで弾くべきではあるとは思う。