LoginSignup
18
10

More than 1 year has passed since last update.

Rails 7 の便利な ComparisonValidator でちとハマった

Last updated at Posted at 2022-05-11

Ruby on Rails で日付の前後関係に関するバリデーションをどうやって書こうか調べていたら,Rails 7 で,ComparisonValidator なるものが導入されたことを知った。

この記事は,ComparisonValidator の簡単な紹介と,私がハマった落とし穴について。

概要

ComparisonValidator は,モデルオブジェクトの属性値を他の値と比較するバリデーター。

Rails 7 より前でも,数値に関しては NumericalityValidator を使って

validates :amount, numericality: { greater_than: 100 }

みたいなことができていた。この例は,amount の値が 100 よりも大きいことを要請する。

これを,数値以外の比較可能な値にまで広げよう,ということらしい。
文字列でも日付でもいいわけだ。

日付を固定値と比較するバリデーションは,たとえば

validates :start_date,
  comparison: { greater_than_or_equal_to: Date.new(1900, 1, 1) }

のように書ける。

また,他の属性値との比較は,属性名をシンボルで与えて,

validates :end_date,
  comparison: { greater_than_or_equal_to: :start_date }

というように書ける。この例は,終了日(end_date)が開始日(start_date)以降でなければならない,というバリデーションだ。

おお! これこそまさに求めていた機能。
Rails 6 までに無かったのが意外だったが。

Proc を与える場合の注意

ここまではよかったのだが,この先で一つの落とし穴にハマった。

やりたかったことは,「終了日(end_date)が本日以降でなければならない」というバリデーション。

これを

validates :end_date,
  comparison: { greater_than_or_equal_to: Date.today }

と書いてはダメなことは分かっていた。
Date.today の値はバリデーションを行うタイミングで求める必要がある。
しかし,上記のコードだと,モデルクラスを作る段階で式 Date.today を評価してしまうので,正しいバリデーションにならない。

どうすればいいかというと,Date.today の値を返すような Proc オブジェクトを与えればいい。その Proc オブジェクトは,バリデーションを行うタイミングで評価される。

こういうことは,私が ComparisonValidator を知ることになったどっかのサイト(失念)に書かれていて,サンプルコードも載っていた。

こんなふうに書けばよい(?):

validates :end_date,
  comparison: { greater_than_or_equal_to: -> { Date.today } }

Proc オブジェクトを,いわゆるラムダ記法 -> { } で与えている。スマートだね。

た,確か,どっかのサイトにこういうサンプルが載ってたんだ。し,信じてくれ。

ところが,これでバリデーションをかけると,エラーメッセージが

"wrong number of arguments (given 1, expected 0)"

になる。えっ,ちょっ・・・,それ,どういうこと? バグってね?

エラーメッセージの i18n がおかしいのかとか,いろいろ調べて数時間を溶かした。

結論としては,Proc オブジェクトの arity(引数の個数)の問題であった。
与えた Proc オブジェクトが呼ばれるとき,モデルオブジェクトが渡されるのだ。
そのモデルオブジェクトを使うかどうかは自由なのだが,-> { } で生成した Proc オブジェクトはラムダなやつなので,引数の不一致を許さない。
これが

wrong number of arguments (given 1, expected 0)

の原因であるらしい。

解決策は,

validates :end_date,
  comparison: { greater_than_or_equal_to: Proc.new{ Date.today } }

のように〈非ラムダな Proc オブジェクト〉を渡してやるか,

validates :end_date,
  comparison: { greater_than_or_equal_to: -> x { Date.today } }

のようにダミーの引数(ブロックパラメーター)を持たせてやるか。
ダミーの引数は x とかよりも _ のほうがよいかもしれない。

18
10
10

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
10