[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)を呼び出していることがわかった。


とソースの追跡の仕方の参考になれば幸いです。