#contents
 
 *はじめに [#hf57bbe9]
 
 [[Ruby on Rails/サーバ起動時の処理を読む]]では、WEBrickサーバが起動するまでを読みました。それでは、
 
  http://localhost:3000/accountbook/
 
 というhttpリクエストを受け取った場合にどのような処理が行われるかを見ていくことにしましょう。
 
 *DispatcherServlet($GEM_HOME/gems/lib/rails/webrick_server.rb) [#vc188c69]
 
 WEBrickサーブレットはサーバからserviceメソッドを呼ばれます。serviceメソッドでは以下の処理が行われています。
 
 +handle_fileメソッドを呼んで通常ファイルを処理する
 +通常ファイルとして処理できなかった場合はhandle_dispatchメソッドを呼ぶ
 
 handle_dispatchメソッドの前半部分は以下のようになっています。
 
  def handle_dispatch(req, res, origin = nil) #:nodoc:
    data = StringIO.new
    Dispatcher.dispatch(
      CGI.new("query", create_env_table(req, origin), StringIO.new(req.body || "")), 
      ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, 
      data
    )
 
 *Dispatcher($GEM_HOME/gems/rails/lib/dispatcher.rb) [#q7aea856]
 
 Dispatcher.dispatchメソッドです。条件分岐や例外処理を省略すると以下の処理が実行されます。
 
  request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi)
  prepare_application
  controller = ActionController::Routing::Routes.recognize(request)
  controller.process(request, response).out(output)
  reset_after_dispatch
 
 ActionController::CgiRequest, CgiResponseはそれぞれ、ActionController::AbstractRequest, AbstractResponseから派生しているようです。CgiRequest, CgiResponseは$GEM_HOME/gems/lib/actionpack/lib/action_controller/cgi_process.rb、AbstractRequestは同じディレクトリのrequest.rb、AbstractResponseは同じディレクトリのresponse.rbにそれぞれ定義されています。
 
 **Dispatcher.prepare_application [#v9ffb7f6]
 
 prepare_applicationメソッドです。
 
  def prepare_application
    if Dependencies.load?
      ActionController::Routing::Routes.reload
      self.preparation_callbacks_run = false
    end
  
    prepare_breakpoint
    require_dependency 'application' unless Object.const_defined?(:ApplicationController)
    ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord)
    run_preparation_callbacks
  end
 
 Dependencies.load?がtrueの場合は毎回ルーティング処理をやり直しているようです。Dependencies.load?メソッドはスクリプト読み込みの方法がloadの場合trueです。アプリケーションディレクトリにあるconfig/environments/development.rbを見ると以下のようになっています。ちなみに、test.rbやproduction.rbはtrueになっているのでリクエストごとに再読込は行われません。
 
  # In the development environment your application's code is reloaded on
  # every request.  This slows down response time but is perfect for development
  # since you don't have to restart the webserver when you make code changes.
  config.cache_classes = false
 
 読み込み方法がいつ設定されるかというとRails::Initialize#processメソッドから呼ばれるinitialize_dependency_mechanismメソッドで設定されます。
 
  def initialize_dependency_mechanism
    Dependencies.mechanism = configuration.cache_classes ? :require : :load
  end
 
 次にブレークポイントの設定を行っていますがとりあえず無視することにします。
 
 次にapplication.rbが読み込まれています。ApplicationControllerはアプリケーションディレクトリのapp/controllers/application.rbに定義されているクラスです。require_dependencyメソッドは$GEM_HOME/gems/activesupport/lib/active_support/dependencies.rbに定義されており、ファイルを読み込むことで新しく定義された定数の検出などいろいろ興味深いのですが長くなるので止めます(説明するのがめんどくさいとも言う:-))
 
 次のActiveRecord::Base.verify_active_connections!は、まだ接続がないはずなので何もしないはずです。
 
 コールバックも登録してないはずなので無視しましょう。
 
 *ActionController::Routing::Routes.recognize ($GEM_HOME/gems/action_pack/lib/action_controller/routing.rb) [#yb5f76e2]
 
 それではリクエストからコントローラを引き当てているらしいActionController::Routing::Routes.recognizeに移ります。ActionController::Routing::Routesは前回見たようにActionController::Routing::RouteSetオブジェクトです。
 
  def recognize(request)
    params = recognize_path(request.path, extract_request_environment(request))
    request.path_parameters = params.with_indifferent_access
    "#{params[:controller].camelize}Controller".constantize
  end
 
 request.pathは要求されたパスでhttp://localhost:3000/accountbook/にアクセスしたとしたら"/accountbook/"となります。またextract_request_environmentメソッドはリクエスト情報からリクエストメソッドだけを取り出しているようです(プラグインとかのオーバーライドを想定しているようです)。
 
 recongnize_pathメソッドです。前回設定したroutes(ActionController::Routing::Routeオブジェクトの配列)からリクエストパスに反応するものを検索しています。
 
  def recognize_path(path, environment={})
    path = CGI.unescape(path)
    routes.each do |route|
      result = route.recognize(path, environment) and return result
    end
    raise RoutingError, "no route found to match #{path.inspect} with #{environment.inspect}"
  end
 
 ActionController::Routing::Route#rocognizeメソッドです。無限再帰?
 
  def recognize(path, environment={})
    write_recognition
    recognize path, environment
  end
 
 **ActionController::Routing::Route#write_recognition [#d5114376]
 
 write_recognitionメソッドに進みましょう。
 
  def write_recognition
    # Create an if structure to extract the params from a match if it occurs.
    body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
    body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
  
    # Build the method declaration and compile it
    method_decl = "def recognize(path, env={})\n#{body}\nend"
    instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
    method_decl
  end
 
 どうやらrecognizeメソッドは動的に書かれているようです。動的に書かれるrecognizeメソッドは以下のようになります。
 
  def recognize(path, env={})
    if (match = #{Regexp.new(recognition_pattern).inspect}.match(path))
      params = parameter_shell.dup
      #{recognition_extraction * "\n"}
      params 
    end
  end
 
 ***ActionController::Routing::Route#recognition_pattern [#i12adb5e]
 
 recognition_patternメソッドは以下のようになっています。
 
  def recognition_pattern(wrap = true)
    pattern = ''
    segments.reverse_each do |segment|
      pattern = segment.build_pattern pattern
    end
    wrap ? ("\\A" + pattern + "\\Z") : pattern
  end
 
 前回を参照すると、segmentsには以下の順序でオブジェクトが格納されています。
 
 +DividerSegment('/'), is_optional == true
 +ControllerSegment(:controller), is_optional == false
 +DividerSegment('/'), is_optional == true
 +DynamicSegment(:action), is_optional == true
 +DividerSegment('/'), is_optional == true
 +DynamicSegment(:id), is_optional == true
 +DividerSegment('/'), is_optional == true
 
 DividerSegment(の親クラスのStaticSegment)のbuild_patternメソッドは以下のようになっています。
 
  def build_pattern(pattern)
    escaped = Regexp.escape(value)
    if optional? && ! pattern.empty?
      "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
    elsif optional?
      Regexp.optionalize escaped
    else
      escaped + pattern
    end
  end
 
 DynamicSegmentのbuild_patternメソッドです。ControllerSegmentも同じものが使われます。
 
  def build_pattern(pattern)
    chunk = regexp_chunk
    chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0
    pattern = "#{chunk}#{pattern}"
    optional? ? Regexp.optionalize(pattern) : pattern
  end
 
  def regexp_chunk
    regexp ? "(#{regexp.source})" : "([^#{Routing::SEPARATORS.join}]+)"
  end
 
 ControllerSegmentではregexp_chunkメソッドがオーバーライドされています。Routing.possible_controllersメソッドはアプリケーションディレクトリにあるapp/controllers内に「何とか_controller.rb」があるとしたら「何とか」が返ってくることになります。
 
  def regexp_chunk
    possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
    "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
  end
 
 順を追って正規表現を構築してみましょう。
 
 +(?:/)?
 +(?:([^/;.,?]+)(?:/)?)?
 +(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?)
 +(?:([^/;.,?]+)(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?))?
 +(?:(?:/)?\Z|/([^/;.,?]+)(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?))
 +(?i-:(accountbook))(?:(?:/)?\Z|/([^/;.,?]+)(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?))
 +(?:(?:/)?\Z|/(?i-:(accountbook))(?:(?:/)?\Z|/([^/;.,?]+)(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?))
 
 ん〜、見てると気分悪くなりますが、確認してみましょう。
 
 :http://localhost:3000/accountbook/|/(?i-:(accountbook))(?:(?:/)?\Z)にマッチ
 :http://localhost:3000/accountbook/show/1|/(?i-:(accountbook))(?:/([^/;.,?]+)(?:/([^/;.,?]+)))にマッチ
 
 合っているようです。
 
 ***ActionController::Routing::Route#recognition_extraction [#c343ccf8]
 
 次に、recognition_extractionメソッドです。
 
  def recognition_extraction
    next_capture = 1
    extraction = segments.collect do |segment|
      x = segment.match_extraction next_capture
      next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
      x
    end
    extraction.compact
  end
 
 各segmentについてどうなるかを見ていきましょう。
 
 +match_extractionの結果はnil, next_capture => 1
 +match_extractionの結果は"params[:controller] = match[1].downcase if match[1]", next_capture => 2
 +match_extractionの結果はnil, next_capture => 2
 +match_extractionの結果は"params[:action] = match[2] if match[2]", next_capture => 3
 +match_extractionの結果はnil, next_capture => 3
 +match_extractionの結果は"params[:id] = match[3] if match[3]", next_capture => 4
 +match_extractionの結果はnil, next_capture => 4
 
 以上のことから動的に書かれたrecognizeメソッドは以下のようになります。
 
  def recognize(path, env={})
    if (match = /\A(?:(?:/)?\Z|/((?i-:(accountbook)))(?:(?:/)?\Z|/([^/;.,?]+)(?:(?:/)?\Z|/([^/;.,?]+)(?:/)?))\Z/.match(path))
      params = parameter_shell.dup
      params[:controller] = match[1].downcase if match[1]
      params[:action] = match[2] if match[2]
      params[:id] = match[3] if match[3]
      params 
    end
  end
 
 魔法ですね:-)
 
 **Dependencies.load_missing_constant ($GEM_HOME/gems/activesupport/lib/activesupport/dependencies.rb) [#n9af90c6]
 
 結果、ActionController::Routing::Routes.recognizeメソッドの戻り値としてユーザ定義のAccountbookControllerが返されます。
 
 ・・・ん?AccountbookControllerっていつの間に読み込まれたのでしょうか?今まで見てきた中でaccountbook_controller.rbを読み込んでいるところは見かけなかったのですが。
 
 dependencies.rbが怪しいと思ったのですがどう呼ばれているかさっぱりわからないので実際の読み込みを行っているload_fileメソッドに罠(?)を仕掛けてみました。
 
    def load_file(path, const_paths = loadable_constants_for_path(path))
  p path
  p caller
 
 で引っかかったのが、
 
  "script/../config/../app/controllers/accountbook_controller.rb"
  [".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:94:in `require_or_load'",
   ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:250:in `load_missing_constant'",
   ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:454:in `const_missing'",
   ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:466:in `const_missing'",
   ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/inflector.rb:250:in `constantize'",
   ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/core_ext/string/inflections.rb:148:in `constantize'",
   ".../ruby-1.8.5-p12/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/routing.rb:1284:in `recognize'"
 
 いつの間にかconst_missingなんてできたんですねぇ。ともかく、const_missingの仕組みとRubyのクラス名⇔ファイル名マッピング規則により、AccountbookControllerが要求されたときに自動的にaccountbook_controller.rbが読み込まれているようです。
 
 *ActionController::Base.scaffold ($GEM_HOME/gems/actionpack/lib/action_controller/scaffolding.rb) [#zf8fcb17]
 
 一番単純なRailsアプリケーションは以下のようになっています。
 
 app/controllers/application.rb
  class ApplicationController < ActionController::Base
    # Pick a unique cookie name to distinguish our session data from others'
    session :session_key => '_accountbook_session_id'
  end
 
 app/controllers/accountbook_controller.rb
  class AccountbookController < ApplicationController
    scaffold :outgo
  end
 
 クラス定義中でscaffoldメソッドを使用するとhttp://localhost:3000/accountbook/にアクセスすることでoutgosテーブルの内容が表示されるようになります。ActionController::Baseに移る前にscaffoldメソッドについて見ていきましょう。
 
 まず前半部分
 
  def scaffold(model_id, options = {})
    options.assert_valid_keys(:class_name, :suffix)
  
    singular_name = model_id.to_s
    class_name    = options[:class_name] || singular_name.camelize
    plural_name   = singular_name.pluralize
    suffix        = options[:suffix] ? "_#{singular_name}" : ""
 
 optionsは指定しないので、
 
 :singular_name|"outgo"
 :class_name|"Outgo"
 :plural_name|"outgos"
 :suffix|""
 
 ということになります。次に、options[:suffix]は定義していないのでindexメソッドが定義されます。
 
  def index
    list
  end
 
 その後、各種メソッドが定義されています。長いのでlistメソッドおよび関連メソッドだけ載せます。
 
  def list
    @outgo_pages, @outgos = paginate :outgos, :per_page => 10
    render_scaffold "list"
  end
 
  def render_scaffold(action=nil)
    action ||= caller_method_name(caller)
    # logger.info ("testing template:" + "#{self.class.controller_path}/#{action}") if logger
  
    if template_exists?("#{self.class.controller_path}/#{action}")
      render :action => action
    else
      @scaffold_class = Outgo
      @scaffold_singular_name, @scaffold_plural_name = "outgo", "outgos"
      @scaffold_suffix = ""
      add_instance_variables_to_assigns
  
      @template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/$/, "")), false))
  
      if !active_layout.nil?
        render :file => active_layout, :use_full_path => true
      else
        render :file => scaffold_path('layout')
      end
    end
  end
 
  def scaffold_path(template_name)
    File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
  end
 
 以上をふまえた上でActionController::Base.processメソッドを見ていくことにしましょう。
 
 *ActionController::Base ($GEM_HOME/gems/actionpack/lib/action_controller/base.rb)[#m26d529e]
 
 まず、ActionController::Base.processメソッドです。オブジェクトを生成して同名のインスタンスメソッドを呼び出しています。
 
  def process(request, response) #:nodoc:
    new.process(request, response)
  end
 
 インスタンスメソッドの方のprocessです。
 
  def process(request, response, method = :perform_action, *arguments) #:nodoc:
    initialize_template_class(response)
    assign_shortcuts(request, response)
    initialize_current_url
    assign_names
    forget_variables_added_to_assigns
  
    log_processing
    send(method, *arguments)
  
    assign_default_content_type_and_charset
    response
  ensure
    process_cleanup
  end
 
 **ActionController::Base#initialize_template_class [#o8ab9079]
 
 まず、initialize_template_classメソッドです。
 
  def initialize_template_class(response)
    raise "You must assign a template class through ActionController.template_class= before processing a request" unless @@template_class
  
    response.template = self.class.view_class.new(self.class.view_root, {}, self)
    response.redirected_to = nil
    @performed_render = @performed_redirect = false
  end
 
 @@template_classはいつ設定されたんだ?と思ったところ、トップレベルのaction_controller.rbが読み込まれたときに設定されていました。
 
  ActionController::Base.template_class = ActionView::Base
 
 action_controller.rbを見たついでに、ActionControllerおよびActionView, ActiveRecordは様々なメソッドを別ファイルに定義しておいてBaseクラスにincludeするというスタイルのようです。%%追いにくいったらありゃしない%%
 
 view_classメソッドです。
 
  def self.view_class
    @view_class ||=
      # create a new class based on the default template class and include helper methods
      returning Class.new(ActionView::Base) do |view_class|
        view_class.send(:include, master_helper_module)
      end
    end
 
 master_helper_moduleはhelper.rbで定義されています。ここではまだアプリケーションディレクトリにあるapp/helpers/*_helper.rbが取り込まれるわけではなさそうです。
 
 次に、view_rootメソッドですがtemplate_rootメソッドを参照しています。これまたどこで設定されたんだろう?とgrepをかけたところ、Rails::Initializer#initialize_framework_viewsメソッドで設定されていました。
 
  def initialize_framework_views
    for framework in ([ :action_controller, :action_mailer ] & configuration.frameworks)
      framework.to_s.camelize.constantize.const_get("Base").template_root ||= configuration.view_path
    end
  end
 
 view_pathはアプリケーションディレクトリ/app/viewsです。
 
 **ActionController::Base#assign_names [#i61cc11e]
 
 processメソッドに戻って、次のassign_shotcutsメソッドではその名の通りrequest, responseの各メンバーに対してのショートカットアクセス方法を設定しています。
 
 次のinitialize_current_urlメソッドではUrlRewriterオブジェクトを生成しています。
 
 次のassign_namesメソッドはまじめに見ましょう。先ほどコントローラを引き当てたときに設定されたアクションを設定しています。って、http://localhost:3000/accountbook/とアクセスするはずなのでparams['action']はnilです。結果、@action_nameとして'index'が設定されることになります。
 
  def assign_names
    @action_name = (params['action'] || 'index')
  end
 
 **ActionController::Base#perform_action [#gd09901d]
 
 さて、perform_actionメソッドです。いろいろ条件分岐していますが一番上だけ見ればいいはずです。
 
  def perform_action
    if self.class.action_methods.include?(action_name)
      send(action_name)
      render unless performed?
 
 action_methodsはActionController::Baseのサブクラスで定義されたメソッドです。action_nameが'index'でscaffoldメソッドによりAccountbookControllerにはindexメソッドが定義されているのでindexメソッドが呼ばれることになります。
 
 **ActionView::Base.paginate ($GEM_HOME/gems/actionpack/lib/action_controller/pagination.rb) [#z0f273d9]
 
 さて、indexメソッドですがlistメソッドを呼んでいるだけなのでlistメソッドを再掲します。
 
  def list
    @outgo_pages, @outgos = paginate :outgos, :per_page => 10
    render_scaffold "list"
  end
 
 paginateメソッドに進みましょう。定義されているファイルはセクションのところに書いてある通りです。
 
  def paginate(collection_id, options={})
    Pagination.validate_options!(collection_id, options, true)
    paginator_and_collection_for(collection_id, options)
  end
 
 validate_options!メソッドはoptionsへのデフォルト値のマージ、collecion_idから生成される単数系名、クラス名を設定しています。Paginationが変更されるわけではないので!ではないようなという気がします。
 
 次に、paginator_and_collection_forメソッドです。
 
  def paginator_and_collection_for(collection_id, options) #:nodoc:
    klass = options[:class_name].constantize
    page  = params[options[:parameter]]
    count = count_collection_for_pagination(klass, options)
    paginator = Paginator.new(self, count, options[:per_page], page)
    collection = find_collection_for_pagination(klass, options, paginator)
    
    return paginator, collection 
  end
 
 options[:parameter]は'page'なので、リクエスト中のpage=nを取得することになりますが今回はnilです。
 
 count_collection_for_paginationメソッドです。どうやらここからはActiveRecordに舞台が移るようです。
 
  def count_collection_for_pagination(model, options)
    model.count(:conditions => options[:conditions],
                :joins => options[:join] || options[:joins],
                :include => options[:include],
                :select => options[:count])
  end
 
 *ActiveRecord::Base [#r1f7821f]
 
 **ActiveRecord::Base.count ($GEM_HOME/gems/activerecord/lib/active_record/calcuration.rb) [#l1049f95]
 
 countメソッドです。
 
  def count(*args)
    calculate(:count, *construct_count_options_from_legacy_args(*args))
  end
 
 続いてcalculateメソッドです。
 
  def calculate(operation, column_name, options = {})
    validate_calculation_options(operation, options)
    column_name     = options[:select] if options[:select]
    column_name     = '*' if column_name == :all
    column          = column_for column_name
    catch :invalid_query do
      if options[:group]
        return execute_grouped_calculation(operation, column_name, column, options)
      else
        return execute_simple_calculation(operation, column_name, column, options)
      end
    end
    0
  end
 
 column_nameは'*'になります。そのため、columnは空配列です。column_forメソッド中でcolumnsメソッドが呼ばれてテーブルの列情報を取得しているようですが後で見ることにします。options[:group]は指定していないのでexecute_simple_calculationメソッドに進みます。それにしてもcatchが使われてるのって始めてみました:-)
 
  def execute_simple_calculation(operation, column_name, column, options) #:nodoc:
    value = connection.select_value(construct_calculation_sql(operation, column_name, options))
    type_cast_calculated_value(value, column, operation)
  end
 
 construct_calculation_sqlメソッドは淡々とSQLを構築しています。それではデータベースとの通信部分に移りましょう。
 
 **ActiveRecord::Base.connection ($GEM_HOME/gems/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb) [#y32ae497]
 
 connectionメソッドは前回も見たconnection_specification.rbに定義されています。
 
  def connection
    if @active_connection_name && (conn = active_connections[@active_connection_name])
      conn
    else
      # retrieve_connection sets the cache key.
      conn = retrieve_connection
      active_connections[@active_connection_name] = conn
      end
    end
  end
 
 実際にデータベースと通信する必要があるまで接続しないという処理が行われています。続いてretrieve_connectionメソッドです。
 
  def self.retrieve_connection #:nodoc:
    # Name is nil if establish_connection hasn't been called for
    # some class along the inheritance chain up to AR::Base yet.
    if name = active_connection_name
      if conn = active_connections[name]
        # Verify the connection.
        conn.verify!(@@verification_timeout)
      elsif spec = @@defined_connections[name]
        # Activate this connection specification.
        klass = name.constantize
        klass.connection = spec
        conn = active_connections[name]
      end
    end
  
    conn or raise ConnectionNotEstablished
  end
 
 接続はまだないのでelsifの方が実行されます。どうにも混乱するのですが、nameはActiveRecord::Baseです。なので、すぐ下にあるconnection=メソッドが呼ばれます(本当はquery_cache.rbでActiveRecord::Base.connection=が再定義されてそっち経由で呼ばれているようですが紛らわしいので)。
 
  def self.connection=(spec) #:nodoc:
    if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
      active_connections[name] = spec
    elsif spec.kind_of?(ConnectionSpecification)
      config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency)
      self.connection = self.send(spec.adapter_method, config)
    elsif spec.nil?
      raise ConnectionNotEstablished
    else
      establish_connection spec
    end
  end
 
 specはConnectionSpecificationのためadapter_methodが呼ばれることになります。これで実際のDBMSとの接続が行われます。その結果をconnectionに代入して・・・読む身となっては:-<な書き方ですね。上のactive_connections[name] = specが実行されるので無限再帰ではありません。
 
 **ActiveRecord::Base.columns ($GEM_HOME/gems/activerecord/lib/active_record/base.rb) [#k995b100]
 
 それでは予告通りcolumnsメソッドです。
 
  def columns
    unless @columns
      @columns = connection.columns(table_name, "#{name} Columns")
      @columns.each {|column| column.primary = column.name == primary_key}
    end
    @columns
  end
 
 table_nameメソッドに進みましょう。
 
  def table_name
    reset_table_name
  end
 
  def reset_table_name #:nodoc:
    base = base_class
  
    name =
      # STI subclasses always use their superclass' table.
      unless self == base
        base.table_name
      else
        # Nested classes are prefixed with singular parent table name.
        if parent < ActiveRecord::Base && !parent.abstract_class?
          contained = parent.table_name
          contained = contained.singularize if parent.pluralize_table_names
          contained << '_'
        end
        name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
      end
  
    set_table_name(name)
    name
  end
 
 base_classメソッドはActiveRecord::Baseを直接継承しているクラスを返します。今回の場合はOutgoです。次に、parentはObjectなはずなのでifの中身は実行されずundecorated_table_nameメソッドに処理が移ります。
 
  def undecorated_table_name(class_name = base_class.name)
    table_name = Inflector.underscore(Inflector.demodulize(class_name))
    table_name = Inflector.pluralize(table_name) if pluralize_table_names
    table_name
  end
 
 Inflectorの各メソッドは$GEM_HOME/gems/activesupport/lib/active_support/inflector.rbを参照してください。ともかくこれでクラスOutgoからデータを取得するテーブル名outgosが取得できました。
 
 *ActionView::Base [#k6ced6da]
 
 今までのところで表示するための情報は取得できた気がするので次は実際に表示している部分を見ることにしましょう。以下はscaffoldメソッドによって自動生成されるメソッドです。しばらく間が開いているので念のために書いておくとAccountbookControllerクラスに定義されています。
 
  def render_scaffold(action=nil)
    if template_exists?("#{self.class.controller_path}/#{action}")
      render :action => action
    else
      @scaffold_class = Outgo
      @scaffold_singular_name, @scaffold_plural_name = "outgo", "outgos"
      @scaffold_suffix = ""
      add_instance_variables_to_assigns
  
      @template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/$/, "")), false))
  
      if !active_layout.nil?
        render :file => active_layout, :use_full_path => true
      else
        render :file => scaffold_path('layout')
      end
    end
  end
 
  def scaffold_path(template_name)
    File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
  end
 
 まず初めのif。アプリケーションディレクトリ/app/views/listなんとかはないのでelseに進みます。なんとかと言っているのはちゃんとわけがあるので興味がある方はご覧ください。
 
 その後いろいろ設定を行い、デフォルトのテンプレートを使ってページの生成を行っています。なお、active_layoutはnilなのでelseの方が実行されます。
 
 **ActionView::Base#render_file ($GEM_HOME/gems/actionpack/lib/action_view/base.rb) [#l6d4ef5b]
 
 それではrender_fileメソッドです。渡される引数を考慮すると以下の部分が実行されます。local_assignsは第3引数でデフォルトは空ハッシュです。
 
  template_file_name = template_path
  template_extension = template_path.split('.').last
  template_source = nil # Don't read the source until we know that it is required
  render_template(template_extension, template_source, template_file_name, local_assigns)
 
 render_templateメソッドです。
 
  def render_template(template_extension, template, file_path = nil, local_assigns = {}) #:nodoc:
    if handler = @@template_handlers[template_extension]
      template ||= read_template_file(file_path, template_extension) # Make sure that a lazyily-read template is loaded.
      delegate_render(handler, template, local_assigns)
    else
      compile_and_render_template(template_extension, template, file_path, local_assigns)
    end
  end
 
 template_handlerを登録した覚えはないのでelseのcompile_and_render_templateメソッドに進みましょう。
 
  def compile_and_render_template(extension, template = nil, file_path = nil, local_assigns = {}) #:nodoc:
    # convert string keys to symbols if requested
    local_assigns = local_assigns.symbolize_keys if @@local_assigns_support_string_keys
  
    # compile the given template, if necessary
    if compile_template?(template, file_path, local_assigns)
      template ||= read_template_file(file_path, extension)
      compile_template(extension, template, file_path, local_assigns)
    end
  
    # Get the method name for this template and run it
    method_name = @@method_names[file_path || template]
    evaluate_assigns
  
    send(method_name, local_assigns) do |*name|
      instance_variable_get "@content_for_#{name.first || 'layout'}"
    end
  end
 
 初回ということでcompile_templateメソッドに進みます。長いので通らないところとか例外処理とか省略。
 
  def compile_template(extension, template, file_name, local_assigns)
    render_symbol = assign_method_name(extension, template, file_name)
    render_source = create_template_source(extension, template, render_symbol, local_assigns.keys)
  
    line_offset = @@template_args[render_symbol].size
  
    CompiledTemplates.module_eval(render_source, file_name, -line_offset)
  
    @@compile_time[render_symbol] = Time.now
  end
 
 assign_method_nameメソッドはファイル名などからキーを生成しています。
 
 次のcreate_template_sourceメソッドは・・・また変なことをやらかしてますね。例によって通らないところ省略です。
 
  def create_template_source(extension, template, render_symbol, locals)
    body = ERB.new(template, nil, @@erb_trim_mode).src
  
    @@template_args[render_symbol] ||= {}
    locals_keys = @@template_args[render_symbol].keys | locals
    @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h }
  
    locals_code = ""
    locals_keys.each do |key|
      locals_code << "#{key} = local_assigns[:#{key}]\n"
    end
  
    "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend"
  end
 
 ふうむ、上のメソッドで何でsendなんてしてるんだろうと思ったらまた動的にメソッド書いてるんですね。ともかくこれでページが書かれました。
 
 *おわりに [#db682088]
 
 今回はRuby on Railsのうち、リクエストが来たときにどのような処理が行われるかを見ていきました。感想としては、
 
 -これぐらいの規模になると、ある設定がどこでされてるのかが把握しづらくなる
 -オンデマンド設定が加わってくると一層わかりづらくなる
 -動的に書かれるコードが多い
 
 といったところです。
 
 それではみなさんもよいコードリーディングを。
 

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS