Railsのバリデーションについて。
バリデーションの階層
ActiveRecord::Validations::ClassMethods の一番上にバリデーション方法の階層について書いてある。
高レベルな順に、
- validates_presence_of :firstname みたいなやつ。modelのクラスレベルで呼ぶ。
- validates_each
- クラスメソッドの validate, validate_on_create, validate_on_update を呼ぶ。
- インスタンスメソッドの validate, validate_on_create, validate_on_update を定義する。
たいていのモデルは一番上のを使えば十分だ、とか書いてある。こんなので済んだためしがないけど、使い方が良くないんだろうか。
validate_..._ofで複数の値が連動するバリデーションは書けるのか?
クラスメソッド
ActiveRecord::Validations::ClassMethods modelのクラスレベルで呼ぶメソッド。
- validates_presence_of :first_name
- validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
- validates_inclusion_of :age, :in => 0..99
- validates_length_of :first_name, :maximum=>30
validates_uniquness_of
SQLを発行して、テーブルに重複した値を持つデータが無いかを確認する。
- validates_uniquness_of :email
- 同じemailは2つ無い。
- validates_uniquness_of :email, :scope => :name
- 1つのnameに対して、同じemailは2つ無い。
- validates_uniquness_of :name, :scope => :email
- 1つのemailに対して、同じnameは2つ無い。emailとnameの組が重複しないというのと同じ。上と同じ。
- validates_uniquness_of :email, :scope => [:name, :birthday]
- あるname, birthdayの組に対して、同じemailは2つない。
- SELECT `xxx`.id FROM `xxx` WHERE (`xxx`.email = 'abc@example.com' AND `xxx`.name = 'koseki') LIMIT 1; みたいなSQLを実行して確認していた。
データの一意性を保証したいときはテーブルにuniqueインデックスを追加する。
- add_index :xxx, [:email, :name], :unique => true
validate_uniqueness_ofを使うと、unique制約の違反で例外が出るよりも前にエラーを検知できる。
Mysql::Error: Duplicate entry 'abc@example.com' for key 2:...
みたいな例外を処理するのと、1回多くSELECTするの、どっちがいいか。あるいは両方か。
Active Record currently provides no way to distinguish unique index constraint errors from other types of database errors, so you will have to parse the (database-specific) exception message to detect such a case.
なので例外は自力でパースしなければならない。validates_uniqueness_ofを設定しても例外が発生する可能性はある。
選択肢は、
- validates_uniqueness_ofを使いつつ、例外もチェックする。厳密。
- メリットは無いかも。前もってSELECTしないとロールバックが頻繁に発生してしまう、みたいな可能性はあるか。
- 例外だけチェックする。SELECTしないので効率がいい。
- validates_uniqueness_ofだけを使って、例外は500エラーにしてしまう。コードがシンプルになる。
重複が起こりうる頻度と、どのくらい丁寧にエラー処理するかで決める。
Errors
Rails 2.2 〜
add(attribute, message = nil, options = {})
If message is a Symbol, it will be translated, using the appropriate scope (see translate_error).
http://api.rubyonrails.org/classes/ActiveRecord/Errors.html
errors.add(:credit_card_number, :invalid)ってすると:invalidが「適切なスコープ」 でエラーメッセージに「翻訳/変換」される。
see translate_errorっていうのはどこ見たらいいのかわからない……。
この記事がわかりやすくて良さそう。
Rails 2.1 までは、Rails 自体が国際化の標準的なやり方を示していませんでしたので、Globalize や Ruby-GetText-Package など様々な方法が提案されてきました。
国際化(i18n)の第一歩 - 『基礎 Ruby on Rails』の asagao を Rails 2.2 に対応させる - Ruby on Rails with OIAX
いいなーRails 2.2。
Rails 1.2.x
でも今触ってるのは1.2系 + GetText。GetTextからエラーメッセージをもらうのはどうやるんだったかなー。
errors.add(:first_name) みたいに第二引数のmessageを省略すると、「****は不正な値 です。」って出してくれたので、そのままにした。対象がプルダウンだし。
参考・未整理
この記事は書きかけの項目です。答えを親切に教えてく れる協力者を求めています。