Sexy Validationで独自のValidationを
いつのまにか、Rails3+1.9.2というモテコンビで仕事をやっております。で、Rails3から入った新機能として、SexyValidationというのができたので、それを使ってみました。
Sexy Validationとは、いままでのRailsのvalidationの書き方とは違い、Modelに対してのvalidationがスッキリ書けるようになりました。例えば、titleカラムを持つPostというModelがあるときに、title要素は必須なんだというvalidationを書こうとすると、以下のようになります。
class Post < ActiveRecord::Base validates :title, :presence => true end
今迄の
validates_presence_of :name
よりわかりやすいのではないでしょうか。
独自のvalidationをしたくなったらどうすればいいのでしょうか?
その場合、ActiveModel::EachValidatorを継承したクラスを作り、each_validatorメソッドをオーバーライドします。文字列にあらかじめ設定されたwordが含まれていないか確認するvalidationを定義してみましょう。
class NgWordValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors[attribute] << 'include ng word' if value =~ /NG WORD/ end end
引数valueには、validateメソッドの第一引数のカラムの値が渡ってきます。validationを失敗とするときは、record.errors[]にエラーメッセージをいれます。attributeはvlidationの名前が入ってきます。
validationを利用する側では、以下のように設定できます。
class Post < ActiveRecord::Base validates :title, :presence => true, :ng_word => true end
簡単ですね。実際にどうなるか確認してみましょう。
> post = Post.new => #<Post id: nil, title: nil, created_at: nil, updated_at: nil> > post.valid? => false > post.errors => {:title=>["can't be blank"]} > post.title = "NG WORD TITLE" => "NG WORD TITLE" > post.valid? => false > post.errors => {:title=>["include ng word"]} > post.title = "TITLE" => "TITLE" > post.valid? => true
正しくvalidationできていることがわかります。
今回は、内部でエラーとする文字列を保持していましたが、利用する側から渡したい場合は、trueとしているとこにハッシュを渡します。
class Post < ActiveRecord::Base validates :title, :presence => true, :ng_word => {ng:"NG WORD"} end
validate_each側では、渡されたハッシュはoptionsに入ってわたってきます。
class NgWordValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors[attribute] << 'include ng word' if value =~ /#{options[:ng]}/ end end
作成したValidatorは全てのActiveModelで使用することができます。例えば、bodyカラムを持つCommentモデルを作成して、以下のようにvalidationを設定すれば、先程のng_word validatorが使用できます。
class Comment < ActiveRecord::Base validates :body, :ng_word => {ng:"XXXX"} end
> comment = Comment.new => #<Comment id: nil, body: nil, created_at: nil, updated_at: nil> > comment.body = "NG WORD" => "NG WORD" > comment.valid? => true > comment.body = "XXXX" => "XXXX" > comment.valid? => false
とても、簡単なのでどんどん利用していきたいです。目下の悩みどころは、このValidatorのクラスをどこに配置するべきかがわかっていないので、有識者の方に教えてもらいたいです。(libにいれたのですが、勝手にrequireされなくて。。)