#contents
 
 *はじめに [#p9a961bc]
 
 Ruby on Railsでは通常以下のようにアプリケーションを作成・実行します。なお、以下の例は私が実験として作った家計簿アプリケーションを用います。
 
 +$ rails accountbook
 +$ ruby script/generate model Outgo
 +$ ruby script/generate controller accountbook
 +app/controllers/accountbook_controller.rbに「scaffold :outgo」追加
 +$ ruby script/server
 
 これで、http://localhost:3000/accountbook/にアクセスするとoutgosテーブルが表示されます。それではサーバの起動処理を見ていくことにしましょう。
 
 なお、今回対象としたバージョンは1.2.3です。
 
 *<アプリケーションディレクトリ>/config/boot.rb [#if85eb6b]
 
 ではサーバを起動するためのscript/serverを見てみましょう。以下の3行だけです。
 
  #!/usr/bin/env ruby
  require File.dirname(__FILE__) + '/../config/boot'
  require 'commands/server'
 
 というわけでconfig/boot.rbに移ります。
 
 まずRAILS_ROOTとしてアプリケーションのルートディレクトリを設定しています。
 
 次に、デフォルトではvendor/railsはないのでRubyGems経由でrailsをロードすることになります。また、config/environment.rbにRAILS_GEM_VERSIONが書かれているのでバージョン限定でrailsをロードすることになります。
 
 最後に以下の一行が実行されています。
 
  Rails::Initializer.run(:set_load_path)
 
 *Rails::Initializer($GEM_HOME/gems/rails/lib/initializer.rb) [#ld97e27a]
 
 Ruby::Initializer.runメソッドを見てみましょう。
 
  def self.run(command = :process, configuration = Configuration.new)
    yield configuration if block_given?
    initializer = new configuration
    initializer.send(command)
    initializer
  end
 
 ブロックは付けていないので、ただset_load_pathメソッドが呼ばれるだけということになります。
 
  def set_load_path
    load_paths = configuration.load_paths + configuration.framework_paths
    load_paths.reverse_each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) }
    $LOAD_PATH.uniq!
  end
 
 やっていることは直感的だと思うので、実際にどのように動くかを見てみましょう。
 
 まず、configuration.load_pathsはRails::Configuration#default_pathsで設定される各ディレクトリです。ここにapp/controllers等が含まれています。次のframework_pathsですが、RAILS_FRAMEWORK_ROOTは定義されていないはずなので#{RAILS_ROOT}/vendor/railsの各ディレクトリ(activerecordなど)になります。これらは存在しないので$LOAD_PATHに追加されないはず。
 
 *$GEM_HOME/gems/rails/lib/commands/server.rb [#ya70cdb4]
 
 README通り、Mongrelやlighttpdが使えるか調べています。ちらちらとactivesupportが使われています。末端まで追おうとすると大変です。RubyGemsも対応してるソースコードブラウザってありましたっけ?
 
 Mongrelもlighttpdも入れていないのでcommands/servers/webrick.rbに移りましょう。
 
 *$GEM_HOME/gems/rails/lib/commands/servers/webrick.rb [#dfcde8be]
 
 まずオプション解析が行われてますがそこは無視します。
 
 アプリケーションディレクトリのconfig/environment.rbを読み込むことにより再びRails::Initializer.runが実行されています。今度は引数なし、ブロックありです。
 
 Rails::Initializer#processメソッドによる初期化の中で興味深い&重要なものとして、initialize_databaseメソッドとinitialize_routingメソッドがあります。それぞれ見ていきましょう。
 
 **Rails::Initializer#initialize_database [#bb339a7d]
 
 initialize_databaseメソッドです。
 
  def initialize_database
    return unless configuration.frameworks.include?(:active_record)
    ActiveRecord::Base.configurations = configuration.database_configuration
    ActiveRecord::Base.establish_connection
  end
 
 Rails::Configuration#database_configurationメソッドです。database_configuration_fileは通常config/database.ymlです。
 
  def database_configuration
    YAML::load(ERB.new(IO.read(database_configuration_file)).result)
  end
 
 ***ActiveRecord::Base.establish_connection ($GEM_HOME/gems/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb) [#ued49413]
 
 ActiveRecord::Base.establish_connectionメソッドです。引数によって以下のように何回も再帰的に呼び出されます。なお、既存の接続はないはずなので無視しています。
 
 +nilでRAILS_ENVが指定されているので、RAILS_ENV(Stringオブジェクト)を引数にestablish_connectionを再帰呼び出し
 +configurations[spec.to_s]の戻り値としてデータベース接続設定が格納されたHashオブジェクトが返るのでそれを引数にしてestablish_connectionメソッドを再帰呼び出し
 +adapter_methodで指定されるメソッドが定義されているので、ConnectionSpecficationオブジェクトを作成してestablish_connectionメソッドを再帰呼び出し
 +設定をクラス変数に格納
 
 adapter_methodで指定されるメソッドは$GEM_HOME/gems/activerecord/lib/active_record.rbがrequireされたとき(Rails::Initializer#require_frameworksメソッド)に定義されています。
 
  unless defined?(RAILS_CONNECTION_ADAPTERS)
    RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase frontbase )
  end
  
  RAILS_CONNECTION_ADAPTERS.each do |adapter|
    require "active_record/connection_adapters/" + adapter + "_adapter"
  end
 
 名前はestablish_coonnectionですが、実際の接続はまだ行っていないようです。
 
 **Rails::Initializer#initialize_routing [#j6fca8f1]
 
 initialize_routingです。
 
  def initialize_routing
    return unless configuration.frameworks.include?(:action_controller)
    ActionController::Routing.controller_paths = configuration.controller_paths
    ActionController::Routing::Routes.reload
  end
 
 ***ActionController::Routing::Routes.reload ($GEM_HOME/gems/actionpack/lib/action_controller/routing.rb) [#ee6a78f4]
 
 ActionController::Routing::RoutesはActionController::Routing::RouteSetオブジェクトです。reloadメソッドはload!メソッドのエイリアスなのでload!メソッドを見てみましょう。
 
  def load!
    Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
    clear!
    load_routes!
    named_routes.install
  end
 
 初めの2行は前の設定を破棄しているようなので無視しましょう。というわけでload_routes!メソッドです。
 
  def load_routes!
    if defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes
      load File.join("#{RAILS_ROOT}/config/routes.rb")
    else
      add_route ":controller/:action/:id"
    end
  end
 
 ifの条件式が成り立つのでアプリケーションディレクトリにあるconfig/routes.rbが読み込まれます。config/routes.rbではActionController::Routing::Routes.drawメソッドがブロック付きで呼ばれています。特定のURLがアクセスされたときにどんな振る舞いを行うかはconfig/routes.rbで定義するようです。
 
 drawメソッドです。こちらでもclear!やnamed_routes.installが呼び出されてますね。
 
  def draw
    clear!
    yield Mapper.new(self)
    named_routes.install
  end
 
 ***ActionController::Routing::RouteBuilder [#y0f718a2]
 
 yieldによってconfig/routes.rbに書かれているブロックに戻ってくると、Mapper#connectメソッドが呼ばれています。見覚えのあるURLに反応しそうな以下の呼び出しを追ってみることにしましょう。
 
  map.connect ':controller/:action/:id'
 
 Mapper#connectメソッドです。
 
  def connect(path, options = {})
    @set.add_route(path, options)
  end
 
 @setは先ほどから見ているActionController::Routing::Routesオブジェクトです。RouteSet#add_routeメソッドに進みましょう。
 
  def add_route(path, options = {})
    route = builder.build(path, options)
    routes << route
    route
  end
 
 builderメソッドはRouteBuilderオブジェクトを返します。RouteBuilder#buildメソッドに進みます。初めと最後を除くとこうなっています。
 
  segments = segments_for_route_path(path)
  defaults, requirements, conditions = divide_route_options(segments, options)
  requirements = assign_route_options(segments, defaults, requirements)
  
  route = Route.new
  route.segments = segments
  route.requirements = requirements
  route.conditions = conditions
  
  if !route.significant_keys.include?(:action) && !route.requirements[:action]
    route.requirements[:action] = "index"
    route.significant_keys << :action
  end
 
 segments_for_route_pathメソッドは渡された文字列を複数のセグメントに分割しています。"/:controller/:action/:id/"(先頭と末尾の'/'はbuildメソッドの初めで追加されます)の場合、以下のようになります。
 
 -DividerSegment('/'), is_optional == true
 -ControllerSegment(:controller), is_optional == false
 -DividerSegment('/'), is_optional == true
 -DynamicSegment(:action), is_optional == false
 -DividerSegment('/'), is_optional == true
 -DynamicSegment(:id), is_optional == false
 -DividerSegment('/'), is_optional == true
 
 buildメソッドに戻って、divide_route_optionsメソッドはoptionsが空ハッシュなのでdefaults, requirements, conditionsのそれぞれは空ハッシュになるはずです。
 
 次にassign_route_optionsメソッドが呼ばれていますがdefaultsとrequirementsは空ハッシュなので前半は無視です。とすると、以下の2メソッドが呼ばれます。
 
  assign_default_route_options(segments)
  ensure_required_segments(segments)
 
 assign_default_route_optionsメソッドでは、:actionおよび:idのSegmentオブジェクトをいじっています。これで、
 
  http://localhost:3000/accountbook/
 
 とだけ指定した場合に一覧画面が表示されるということになるようです。
 
 buildメソッドに戻るとRouteオブジェクトが構築され、route中に:actionが含まれているのでifの中身は実行されないはずです。
 
 ***ActionController::Routing::RouteSet::NamedRouteCollection [#qa2ac68a]
 
 以上でルーティング情報が構築されたのでActionController::Routing::Routesオブジェクトのnamed_routes.installメソッドです。named_routesの実体はNamedRouteCollectionオブジェクトです。
 
  def install(destinations = [ActionController::Base, ActionView::Base])
    Array(destinations).each { |dest| dest.send :include, @module }
  end
 
 ActionController::BaseとActionView::Baseにメソッドを追加しているようですが、今までに見たところで@moduleを操作しているところはなかったはず。
 
 *DispatchServlet($GEM_HOME/gems/rails/lib/webrick_server.rb) [#e4e697d8]
 
 $GEM_HOME/rails/lib/commands/servers/webrick.rbはDispatchServlet.dispatchメソッドを呼び出して終わりです。
 
 dispatchメソッドでは、WEBrick::HTTPServerオブジェクトを作成し、
 
  server.mount('/', DispatchServlet, options)
 
 とすることで全てのリクエストを自分で処理することになっています。下のinitializeメソッドを見てみるとWEBrick::HTTPServlet::FileHandlerオブジェクトを作成しているので通常のファイル取得リクエストはそちらに流していると想像できます。
 
 *おわりに [#yf98cadf]
 
 今回はRuby on Rallsのうち、サーバ起動時にどのような処理が行われるかを見ていきました。感想としては、
 
 -RubyGemsが使われているとクラスやメソッドが定義されているファイルを探すのが大変
 -activesupportが使われているともっと大変
 
 といったところです:-)
 
 それではみなさんもよいコードリーディングを。
 

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