DataMapper を使う (Valication)
http://d.hatena.ne.jp/KrdLab/20090503/1241331627 の補足.
今回は Validation についてです.
一部のサンプルは公式サイトから拝借しています.
http://datamapper.org/doku.php?id=docs:validations
はじめに
Validation を使うには,まず require する.
require 'dm-validations'
Validation 指定
- validates_present
- 空でないかどうか
- validates_absent
- 空かどうか
- validates_is_accepted
- ?
- validates_is_confirmed
- ?
- validates_format
- 指定されたフォーマットに従うかどうか
- validates_length
- 指定された条件の長さを満たすか
- validates_with_method
- validation 用のメソッドを指定する
- validates_with_block
- validation 用のブロックを指定する
- validates_is_number
- 数値,もしくは数値として解釈できるかどうか
- validates_is_unique
- ユニークな値かどうか
- validates_within
- ?
doc: http://datamapper.rubyforge.org/dm-more/
使うときは,以下のように指定する (Manual).
validates_length :name validates_length [:name, :description] # 複数の場合
プロパティ定義にそのまま記述することもできる (Auto).
ショートカットのような位置づけ.
# 暗黙的に validates_present を生成する :nullable => false # 暗黙的に validates_length を生成する :length => 20 :length => (1..20) # cant be null :length => (0..20) # can be null # :size is a synonym to :length # 暗黙的に validates_format を生成する :format => :email_address # e-mail 形式の場合はこれを指定すればよい :format => /\w+_\w+/ :format => lambda {|str| str } :format => Proc.new { |str| str }
Manual と Auto の例は以下の通り.
require 'dm-validations' class Account include DataMapper::Resource property :name, String # manual validation validates_length :name, :max => 20 # auto-validation property :content, Text, :length => (100..500) end
ちなみに,このコードで validation に引っかかる使い方をしても,エラーメッセージは表示されない (明示的に取得する必要がある).
当然,DB にデータも格納されないまま終了する.
Validating
デフォルトでは save/create/update したときに validation される.
任意のタイミングで validation したいときは,valid? メソッドを使用する.
true を返せば有効,false の場合は無効であるとわかる.
all_valid? メソッドを使うと,自身だけでなく,関連するオブジェクトを再帰的に調べ,1 つでも valid? が false を返せば all_valid? も false を返す.
Working with Validation Errors
validator がエラーを発見すると,ValidationErrors が作成される.
これは errors メソッドを通して取得できる.
class Dummy2 include DataMapper::Resource property :id, Serial property :name, String property :value, String validates_length :name, :max => 10 end d = Dummy2.new d.name = '1234567890a' if d.save then p 'success' else d.errors.each do |e| p e end end
ってやると,こんな感じに出る.
["Name must be less than 10 characters long"]
Error Messages
:message オプションを指定することで,エラーメッセージを指定することができる.
validates_is_unique :title, :scope => :section_id, :message => "There's already a page of that title in this section"
上の例では,同じ section_id を持つ場合に限り,:title の unique を検証する.
検証に失敗すれば :message で指定した内容をエラーメッセージとして出力する.
例えば,以下のような動作になる.
class Tuple include DataMapper::Resource property :id, Serial property :key, String property :value, String validates_is_unique :key, :scope => :value, :message => "There's already (key, value)." end t = Tuple.new(:key => 'aaa', :value => '111') t.errors.each {|e| p e } if not t.save t = Tuple.new(:key => 'aaa', :value => '111') t.errors.each {|e| p e } if not t.save # ここで引っかかる t = Tuple.new(:key => 'aaa', :value => '222') t.errors.each {|e| p e } if not t.save t = Tuple.new(:key => 'bbb', :value => '111') t.errors.each {|e| p e } if not t.save
+----+------+-------+ | id | key | value | +----+------+-------+ | 1 | aaa | 111 | | 2 | aaa | 222 | | 3 | bbb | 111 | +----+------+-------+
:messages を指定することで,複数のメッセージを設定できる.
property :email, String, :nullable => false, :unique => true, :format => :email_address, :messages => { :presence => "We need your email address.", # nullable に引っかかったとき :is_unique => "We already have that email.", # unique に引っかかったとき :format => "Doesn't look like an email address to me ..." # format に引っかかったとき }
TODO まだ調べてない => @resource.errors.add(:title, "Doesn't mention DataMapper")
Custom Validations
validates_with_block や validates_with_method を用いることで,validation の内容を定義することができる.
有効な場合は true を,無効な場合は false を返す.
false の場合に [false, "エラーメッセージ"] の様に返すこともできる.
class Custom include DataMapper::Resource property :id, Serial property :title,String property :body, Text validates_with_method :check_body def check_body if self.body.index('krdlab').nil? then return [false, "Not contains 'krdlab'"] else return true end end end c = Custom.new(:title => 'TITLE', :body => "krdlab's dialy") c.errors.each {|e| p e } if not c.save c = Custom.new(:title => 'TITLE', :body => "hoge's dialy") c.errors.each {|e| p e } if not c.save # ここで引っかかる
["Not contains 'krdlab'"]
save したり,valid? を呼ぶと実行される.
個々のプロパティに設定することもできる.
validates_with_method :check_body
を
validates_with_method :body, :method => :check_body
とする.
Conditional Validations
validations は常に走らせなければならないわけではない.
例えば,新規作成時のレコードと,更新時のレコードでは,チェックする内容が異なるかもしれない.
このような,条件に応じて検証を行わせたい場合は,:if や :unless を用いる.
:if や :unless には,メソッド名か Proc を指定する.
メソッドや Proc が true を返すと,validation を実行する.
メソッドの場合は引数なし,Proc の場合は検証しようとしている Resource そのものが引数として渡る.
class Ticket include DataMapper::Resource property :id, Serial property :title, String, :nullable => false property :description, Text property :commit, String property :status, Enum[:new, :open, :invalid, :complete] validates_present :commit, :if => Proc.new {|t| t.status == :complete } end
:title は常に検証される.
:commit は :status が :complete の場合に検証される.
:change_summary に対して,以下のような検証条件を設定することもできる.
validates_length :change_summary, :min => 10, :unless => :new_record?
新規レコードでない場合は,10 文字未満のサマリを認めない,という意味になる.
Contextual Validations
適用する validation のグループを作成し,valid? や save に指定することができる.
サンプルをみた方がわかりやすい.
以下の例では draft 版と publish 版とで,validation の内容を変えている.
class Article include DataMapper::Resource property :id, Serial property :title, String property :sidebar_picture_url, String property :body, Text property :published, Boolean # validations validates_present :title, :when => [:draft, :publish] validates_present :sidebar_picture_url, :when => [ :publish] validates_present :body, :when => [:draft, :publish] validates_length :body, :minimum => 1000, :when => [ :publish] validates_absent :published, :when => [:draft] end article = Article.new # 呼び出し方法の紹介 ---- article.valid?(:draft) #=> false # title と body がない. article.valid_for_publish? #=> false # title, sidevar_picture_url, body がない. # ちなみに valid_for_publish? は valid?(:publish) と等価. # draft は通るよ ---- article.title = "DataMapper is awesome because ..." article.body = "Well, where to begin ..." article.valid?(:draft) #=> true # title と body があるので OK article.valid?(:publish) #=> false # body が短すぎる. article.save(:draft) #=> true # draft/publish 両方通るよ ---- article.sidebar_picture_url = "http://www.greatpictures.com/flower.jpg" article.body = "1000 文字以上の文章..." article.valid?(:draft) #=> true. article.valid?(:publish) #=> true. # :publish の内容を満たしたので. # publish は通るよ ---- article.published = true article.save(:draft) #=> false # published が true なので. article.save(:publish) #=> true
Setting Properties Before Validation
validation が適用される前に,プロパティに対して値を設定することができる.
デフォルト値設定は参照されるプロパティ自身の値を設定するのに対し,ここで紹介する方法は参照されるプロパティ以外のプロパティに対して値を設定することができる.
以下の例では,valid? メソッドを hook し,permalink に値を設定している.
class Article include DataMapper::Resource property :id, Serial property :title, String, :nullable => false property :permalink, String, :nullable => false # 詳細は http://datamapper.org/doku.php?id=docs:hooks before :valid?, :set_permalink def set_permalink(context = :default) self.permalink = title.gsub(/\s+/,'-') end end
こんな感じになる.
a = Article2.new a.title = 'This is a title' a.save
+----+-----------------+-----------------+ | id | title | permalink | +----+-----------------+-----------------+ | 1 | This is a title | This-is-a-title | +----+-----------------+-----------------+