Validationの動き
こうかくと、name がチェックされるわけだが、
class Hoge < ActiceRecord::Base validates_presence_of :name, :message => "を入力しようよ。" end
どうしてこんな動きがなるのか気になる。
バリデーションの登録の流れ
まずは、ActiceRecord::Base は、actice_record/base.rb の中
module ActiveRecord ・・・ class Base ・・・ end ・・・ end
このBaseクラスが、actice_record.rb の中で拡張されている。
ActiceRecord::Base.class_eval do ・・・ include ActiveRecord::Validations ・・・ end
上の、include ActiceRecord::Validations が呼ばれると。
actice_record/validations.rb の以下の部分に処理が移る。
module ActiceRecord module Validations def self.included(base) base.extend ClassMethods base.class_eval do alias_method_chain :save, :validation alias_method_chain :save!, :validation alias_method_chain :update_attribute, :validation_skipping end end end end
この部分が最初どうなってるかよくわからなかったんだけど、
Base -include-> Validations -extend-> Validations::ClassMethods
となっていて、
Validationsの、def ... end が Base クラスのインスタンスメソッドになり、
Validations::ClassMethodsの、def ... end が Base クラスのクラスメソッドになる。
ということらしい。
Rubyのinclude、extend ってなんか覚えられん。
このやり方って定石なのかな。
ともあれ。
このClassMethodsの中に、validates_presence_ofやら、validates_format_of が定義されていて、
ActiveRecord::Baseを継承したクラスの中で、呼び出せるようになっている。
module Validations module ClassMethods ・・・ def validates_presence_of(*attr_names) configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save } configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) # can't use validates_each here, because it cannot cope with nonexistent attributes, # while errors.add_on_empty can attr_names.each do |attr_name| send(validation_method(configuration[:on])) do |record| unless configuration[:if] and not evaluate_condition(configuration[:if], record) record.errors.add_on_blank(attr_name,configuration[:message]) end end end end ・・・ end end
この中でポイントは以下の部分。
send(validation_method(configuration[:on])) do |record| unless configuration[:if] and not evaluate_condition(configuration[:if], record) record.errors.add_on_blank(attr_name,configuration[:message]) end end
configrationは、validates_* の最後にわたすHashのこと。
validation_method(configuration[:on]) で、:onの値によって :validate/:validate_on_create/:validate_on_update のいずれかが返る。
ここで返ったシンボルの名前のメソッドが、最後のブロックを引数にして呼び出される。
module ClassMethods def validate(*methods, &block) methods << block if block_given? write_inheritable_set(:validate, methods) end def validate_on_create(*methods, &block) methods << block if block_given? write_inheritable_set(:validate_on_create, methods) end def validate_on_update(*methods, &block) methods << block if block_given? write_inheritable_set(:validate_on_update, methods) end end
先の send で渡されたブロックは、これらのメソッドに渡されて、それぞれのメソッドごとのキー名(:validate/:validate_on_create/:validate_on_update)の名前でクラス変数として定義されているHashの中に保存される。
大体こんな感じ。
バリデーションの呼び出し。
バリデーションの呼び出しの最初の入り口はここから。
module Validations def save_with_validation(perform_validation = true) if perform_validation && valid? || !perform_validation save_without_validation else false end end def save_with_validation! if valid? save_without_validation! else raise RecordInvalid.new(self) end end end
なぜここからかというと、Validationsモジュールの self.included っでこんなことをしている。
module Validations def self.included(base) # :nodoc: base.extend ClassMethods base.class_eval do alias_method_chain :save, :validation alias_method_chain :save!, :validation alias_method_chain :update_attribute, :validation_skipping end end end
ポイントは、alias_method_chain の部分。
何をしているかというと、こういうことをしている。
alias_method :save_without_validation, :save alias_method :save, :save_with_validation
詳しくはここをみるとよくわかる。
http://wota.jp/ac/?date=20060503#p03
ようは、Baseクラスのsaveメソッドを、save_without_validation でも呼び出せるように退避してから、saveメソッドの呼び出しを、:save_with_validation に変更している。
これによって、ActiceRecord::Baseを継承したクラスで、saveメソッドを呼び出すと、save_with_validation が呼び出されるようになる。
で、save_with_validation の中で呼び出されている save_without_validation が本来のベースとなっている saveメソッドをあらわす。
save_with_validation で呼び出されている valid?メソッドの中は、こんな感じになっている。
まぁ、そのまんまですな。
module Validations def valid? errors.clear run_validations(:validate) validate if new_record? run_validations(:validate_on_create) validate_on_create else run_validations(:validate_on_update) validate_on_update end errors.empty? end end
この中のrun_validations が validates_presense_of などで登録したバリデータを呼び出している部分で、引数のシンボルは登録したときのキー。
run_validationsの中身は以下のとおり、
module Validations private def run_validations(validation_method) validations = self.class.read_inheritable_attribute(validation_method.to_sym) if validations.nil? then return end validations.each do |validation| if validation.is_a?(Symbol) self.send(validation) elsif validation.is_a?(String) eval(validation, binding) elsif validation_block?(validation) validation.call(self) elsif validation_class?(validation, validation_method) validation.send(validation_method, self) else raise( ActiveRecordError, "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " + "class implementing a static validation method" ) end end end end
この中の、
validations = self.class.read_inheritable_attribute(validation_method.to_sym)
で、先に登録したバリデーションの定義を取り出している。
run_validationsの残りでは、定義の配列をeachでまわして、それぞれ呼び出しているだけ。
ここを見ると、バリデータとして、シンボル、文字列、ブロック、クラスを定義可能なことがわかる。