[rails][code_reading] ActiveRecord::Base#saveメソッドのvalidationのかかる順番
ActiveRecordのsaveメソッド時のvalidationがどういう順番で働いているのかを調べた。
まず普通にActiveRecord::Base内を「def save」で検索
#ファイル /usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb 1971行目-1973行目 def save create_or_update end
内部的にcreate_or_updateを使っているらしい。ということで「def create_or_update」を検索
#ファイル /usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb 2236行目-2240行目 def create_or_update raise ReadOnlyRecord if readonly? result = new_record? ? create : update result != false end
new_recordだった場合createを使いnew_recordじゃなかった場合updateということか。
とりあえず「def create」で検索してcreateメソッドを読んでみる
#ファイル /usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb 2257行目-2277行目 def create if self.id.nil? && connection.prefetch_primary_key?(self.class.table_na\ me) self.id = connection.next_sequence_value(self.class.sequence_name) end quoted_attributes = attributes_with_quotes statement = if quoted_attributes.empty? connection.empty_insert_statement(self.class.table_name) else "INSERT INTO #{self.class.quoted_table_name} " + "(#{quoted_column_names.join(', ')}) " + "VALUES(#{quoted_attributes.values.join(', ')})" end self.id = connection.insert(statement, "#{self.class.name} Create", self.class.primary_key, self.id, self.class.sequence_name) @new_record = false id end
なんかいきなりSQLを作っててvalidationをかけているようなコードは見当たらない。
ということでどっかでメソッドをオーバーライドしてるのだろう。
ということで次にそれっぽい名前のActiveRecord::Validationsを見てみる
後からメソッドにたいして何かしらの処理を加えたりする場合シンボルでメソッド名を渡す場合が多いので 「:save 」を検索してみる
# ファイル /usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/validations.rb 275行目-282行目 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
だいぶそれらしきモノがあった。
module.include というメソッドをリファレンスマニュアル- Modulクラスで調べると
selfがincludeされたときに対象のクラスまたはモジュールを引数にインタプリタから呼び出されます。
とある。っつーことでincludeされたら呼び出されるものだってのがわかったのでこれでアタリだろう。
つまりここでActiveRecord::Baseにincludeしたらalias_method_chainでsaveメソッドにvalidationを追加してる模様。
alias_method_chainをgrepで探してみるとactive supportでModuleクラスを拡張して作っている。
# ファイル: /usr/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/core_ext/module/aliasing.rb 23行目-42行目 def alias_method_chain(target, feature) # Strip out punctuation on predicates or bang methods since # e.g. target?_without_feature is not a valid method name. aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 yield(aliased_target, punctuation) if block_given? with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuati\ on}", "#{aliased_target}_without_#{feature}#{punctuation}" alias_method without_method, target alias_method target, with_method case when public_method_defined?(without_method) public target when protected_method_defined?(without_method) protected target when private_method_defined?(without_method) private target end end
細かい部分は端折って必要な部分だけ説明すると
# ファイル /usr/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/core_ext/module/aliasing.rb 29行目-32行目 with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuati\ on}", "#{aliased_target}_without_#{feature}#{punctuation}" alias_method without_method, target alias_method target, with_method
の部分でtargetをwith_methodにすり替えてオリジナルのtargetをwithout_methodにしている。
さっきの
# ファイル /usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/validations.rb 278行目 alias_method_chain :save, :validation
でいうとオリジナルのsaveメソッドsave_without_validationというメソッドにしてsaveメソッドををsave_with_validationメソッドにすり替えていることになる。
ということでsave_with_validationメソッドを探す。
# ファイル /usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/validations.rb 932行目-938行目 def save_with_validation(perform_validation = true) if perform_validation && valid? || !perform_validation save_without_validation else false end end
と定義されているのでperform_validation -> valid?を呼んでからsaveメソッド(save_without_validation)を呼び出していることがわかった。
とソースの追跡の仕方の参考になれば幸いです。