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を省略すると、「****は不正な値 です。」って出してくれたので、そのままにした。対象がプルダウンだし。

参考・未整理

この記事は書きかけの項目です。答えを親切に教えてく れる協力者を求めています。