Railsのvalidatesメソッドについて調べてみた
railsでは下記のように書くことでバリデーションを行える(コード参照元)。
class Person < ApplicationRecord
validates :name, presence: true
end
このvalidatesメソッドがどのように実装されているか気になったので調べてみた。
どこで定義されているのか
ApplicationRecord
ActiveRecord::Baseを継承しているクラスであり、rails newで自動生成される。
ActiveRecord::Base
ActiveRecord::Validationsをincludeしている。
ActiveRecord::Validations
ActiveModel::Validationsをincludeしている。
ActiveModel::Validations
validatesメソッドが実装されているファイルを読み込んでいる。
ActiveModel::Validations::ClassMethods#validates
validatesメソッドが定義されている。
ActiveModel::Validations::ClassMethods#validatesの中身を読む
下記の場合を考える(コード参照元)。
class Order < ApplicationRecord
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
payment_type == "card"
end
end
1. validatesメソッドの引数を確認する
railsはアスタリスクを付けることで引数を配列に指定できるので、attributes = [:name, {:presence=>true, :if=>:paid_with_card?}]となる。
extract_options!は下記のようになっている(このメソッドを使用している理由はRailsガイドが参考になる)。
これにより、
defaults = {:presence=>true, :if=>:paid_with_card?}
また、 により、
defaults = {:if=>:paid_with_card?}
validations = {:presence=>true}
2. validationのクラスを取得する
key = "PresenceValidator"となり、const_getメソッドにより、
validator = ActiveModel::Validations::PresenceValidatorとなる(クラスが定義されるときに、PresenceValidatorという名前で、ActiveModel::Validations::PresenceValidatorを値として追加しているため)。
今回はoptionsがtrueのため、
により、
validates_with(ActiveModel::Validations::PresenceValidator, {:if=>:paid_with_card?, :attributes=>[:name]})
ActiveModel::Validations::ClassMethods#validates_withの中身を読む
1. validates_withメソッドの引数を確認する
args = [ActiveModel::Validations::PresenceValidator, {:if=>:paid_with_card?, :attributes=>[:name]}]となり、blockにはnilが入る。
self = Orderなので、
options = {:if=>:paid_with_card?, :attributes=>[:name], :class=>Order}
args = [ActiveModel::Validations::PresenceValidator]
2. validatorのインスタンスを作成する
今回はActiveModel::Validations::PresenceValidatorのインスタンスを作成する。
3. validatorのインスタンスを使ってvalidateメソッドを呼び出す
ActiveModel::Validations::PresenceValidatorはActiveModel::EachValidatorを継承しているので、validator.attributes = [:name]となる。
_validatorsはクラス属性として定義されており、_validators[:name] = [ActiveModel::Validations::PresenceValidator.new]となる。
validateメソッドは次で呼び出される。
validate(ActiveModel::Validations::PresenceValidator.new, {:if=>:paid_with_card?, :attributes=>[:name], :class=>Order})
ActiveModel::Validations::ClassMethods#validateの中身を読む
1. validateメソッドの引数を確認する
args = [ActiveModel::Validations::PresenceValidator.new, {:if=>:paid_with_card?, :attributes=>[:name], :class=>Order}]となり、blockにはnilが入る。
extract_options!により、
options = {:if=>:paid_with_card?, :attributes=>[:name], :class=>Order}
args = [ActiveModel::Validations::PresenceValidator.new]
2. set_callbackメソッドを呼び出す
args.all?(Symbol)はfalseとなるので、ここは呼び出されない。
options.key?(:on)はfalseとなるので、ここは呼び出されない。
set_callbackメソッドは次で呼び出される。
set_callback(:validate, ActiveModel::Validations::PresenceValidator.new, {:if=>:paid_with_card?, :attributes=>[:name], :class=>Order})
まとめ
Railsのvalidatesメソッドはコールバックを利用していることがわかった。
別記事でコールバックの仕組みを調べる。
Discussion