validates_presence_of は何をしているのか?

ActiveRecord 1.15.3の話です。

いきなりですがソースを追ってみます。

/usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/validations.rb

 1| def validates_presence_of(*attr_names)
 2|   configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save }
 3|   configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
 4|
 5|   # can't use validates_each here, because it cannot cope with nonexistent attributes,
 6|   # while errors.add_on_empty can
 7|   attr_names.each do |attr_name|
 8|     send(validation_method(configuration[:on])) do |record|
 9|       unless configuration[:if] and not evaluate_condition(configuration[:if], record)
10|         record.errors.add_on_blank(attr_name,configuration[:message])
11|       end
12|     end
13|   end
14|  end

2行目でオプションの初期設定している。まぁこれはこれでいいでしょう。
3行目で validates_presence_of の引数の最後がHashだったら2行目で設定したオプションの値を上書きしている。


5,6行目のコメントは

validates_eachだと存在しないアトリビュートを上手く扱えないのでerros.add_on_emptyが使えている間はここでvalidates_eachを使いません。

って書いてあるのかな?


で、7行目からが本番。
3行目でオプションのHashはpopされてattr_namesからはなくなっているので、attr_namesは純粋にvalidates_presence_ofの対象となるアトリビュート名のみになっている。
で、これをeachで回す。


回して何をするかというと validation_method(configuration[:on]) の戻り値を send している。
/usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/validations.rb

def validation_method(on)
  case on
    when :save   then :validate
    when :create then :validate_on_create
    when :update then :validate_on_update
  end
end

validates_presence_of の :on に従って validate, validate_on_create, validate_on_update のいづれかが send される事になりますね。


validate, validate_on_create, validate_on_updateは
/usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/validations.rb

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

と、いった感じなので :if の条件に合致した時に record.errors.add_on_blank(attr_name,configuration[:message]) が発動する感じです。


/usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/validations.rb

def add_on_blank(attributes, msg = @@default_error_messages[:blank])
  for attr in [attributes].flatten
    value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
    add(attr, msg) if value.blank?
  end
end

add_on_blankはこうなっているので渡された@base.attr_nameがあるかどうかチェックして、あるようであれば@base.sttr_nameの戻り値、なければ@base[attr_name]の戻り値を取得して、それに対してblank?でチェック。trueであればエラーを追加している感じですね。


このチェックに blank? を使っているので boolean を取るアトリビュートに対して validates_presence_of を使うと false.blank? # => ture なので vaidates_presence_of が使えない所以ですね。