2012年8月26日日曜日

ActiveRecordのQueryingを読む

ActiveRecordのQueryingを読み始めるとdelegaeというメソッドがあることに気付く。
module ActiveRecord
  module Querying
    delete :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
なんやねん、これは?ということで active_support/core_ext/module/delegationを見に行く。
  def delegate(*methods)
    options = methods.pop
    unless options.is_a?(Hash) && to = options[:to]
      raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
    end
    prefix, to, allow_nil = options[:prefix], options[:to], options[:allow_nil]

    if prefix == true && to.to_s =~ /^[^a-z_]/
      raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
    end

    method_prefix =
      if prefix
        "#{prefix == true ? to : prefix}_"
      else
        ''
      end

    file, line = caller.first.split(':', 2)
    line = line.to_i

    methods.each do |method|
      method = method.to_s

      if allow_nil
        module_eval(<<-EOS, file, line - 2)
          def #{method_prefix}#{method}(*args, &block)        # def customer_name(*args, &block)
            if #{to} || #{to}.respond_to?(:#{method})         #   if client || client.respond_to?(:name)
              #{to}.__send__(:#{method}, *args, &block)       #     client.__send__(:name, *args, &block)
            end                                               #   end
          end                                                 # end
        EOS
      else
        exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")

        module_eval(<<-EOS, file, line - 1)
          def #{method_prefix}#{method}(*args, &block)        # def customer_name(*args, &block)
            #{to}.__send__(:#{method}, *args, &block)         #   client.__send__(:name, *args, &block)
          rescue NoMethodError                                # rescue NoMethodError
            if #{to}.nil?                                     #   if client.nil?
              #{exception}                                    #     # add helpful message to the exception
            else                                              #   else
              raise                                           #     raise
            end                                               #   end
          end                                                 # end
        EOS
      end
    end
  end
  1. 引数から最後の値を取り出す。 
  2. オプションがHashかどうかチェック、options[:to]をtoに代入してnilかどうかチェック。Hashじゃなかったり、toがnilなら例外。代入と評価を一緒にやっている。
  3. prefixの先頭が半角英字と_以外ならば例外。
  4.  prefixが真でprefixがtrueならtoの中身と_を、そうでないならprefixと_をmethod_prefixに代入
  5. callerでファイルの実行場所を取得して、:で分割して実行ファイル名と実行ラインをとる。 
  6. allow_nilオプションがある場合とない場合で場合分け
  7. allow_nilがある場合。module_evalでメソッドを追加
    メソッド名がmethod_prefix+method
    ifでrespond_to?を使ってメソッドがある場合のみ
    method_prefix+method.__send__でメソッドを実行する。
    メソッドがない場合はnilを返す。
  8. allow_nilがない場合。
    method_prefix+method.__send__でメソッドを実行する。
    メソッドがない場合は例外を投げる。

0 件のコメント: