2009年8月20日木曜日

[Rails][CodeReading] Railsの初期化コードを読む (イントロ&目次)

実践Rails』という本を読んでいます。Ruby/Railsの中身(実装)まで踏み込んで書かれていて、読みごたえあります。

その中の1節
「2.5.2 アプリケーションを初期化するための20の手順」
をたよりに、Railsの初期化コード(rails-version/lib/initializer.rb)を読んでみようという(ありがち王道な)試みです。

Rails のバージョンは 2.3.3です。

前置き

initializer.rb の前に、(一番初めから追ってみたかったので...)まずはエントリポイントとなる script/server を。
# script/server
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/server'

script/serverでは、2つのファイルを require しているだけです。
1つずつ見ていきます。まずは、config/boot.rb。
# config/boot.rb (抜粋)
# Don't change this file!
# Configure your app in config/environment.rb and config/environments/*.rb

RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)

module Rails
class << self
def boot!
unless booted?
preinitialize
pick_boot.run
end
end

def pick_boot
(vendor_rails? ? VendorBoot : GemBoot).new
end
end

class Boot
def run
load_initializer
Rails::Initializer.run(:set_load_path)
end
end

class VendorBoot < Boot
def load_initializer
require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
Rails::Initializer.run(:install_gem_spec_stubs)
Rails::GemDependency.add_frozen_gem_path
end
end

class GemBoot < Boot
def load_initializer
self.class.load_rubygems
load_rails_gem
require 'initializer'
end
end
end

# All that for this:
Rails.boot!

boot.rb は

  • グローバル変数 RAILS_ROOT をセット

  • Railsモジュールにboot!メソッドを追加

  • Rails.boot! の呼び出し


という流れになっています。
肝となる Rails.boot! メソッドでは、pick_boot メソッドで Bootクラスインスタンスが生成され(*1)、Boot.runが呼ばれます。そして、このBoot.runの中でRails::Initializer.runが呼ばれる、という仕組み。

(*1) 実際に生成されるのはBootクラスのサブクラスであるVenderBootクラスもしくはGemBootクラスのインスタンス。vender/railsが存在する場合はVenderBoot, なければGemBootが選択される。

次に、script/server内で2つめにrequireされるcommands/server.rb。こちらはRails本体の中にあります。


# rails/commands/server.rb
~~略~~
# 71行目から
if File.exist?(options[:config])
config = options[:config]
if config =~ /\.ru$/
cfgfile = File.read(config)
if cfgfile[/^#\\(.*)/]
opts.parse!($1.split(/\s+/))
end
inner_app = eval("Rack::Builder.new {( " + cfgfile + "\n )}.to_app", nil, config)
else
require config
inner_app = Object.const_get(File.basename(config, '.rb').capitalize)
end
else
require RAILS_ROOT + "/config/environment"
inner_app = ActionController::Dispatcher.new
end
~~略~~

-c または --config オプションを指定しなければ、デフォルト環境設定ファイルの config/environment.rb がrequireされます。(ここにいたのか environment.rb)
そして、config/environment.rb の中で

# config/environment.rb
RAILS_GEM_VERSION = '2.3.3' unless defined? RAILS_GEM_VERSION

# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config|
# Settings in config/environments/* take precedence over those specified here.
~~略~~
end

Rails::Initializer.runが再び(今度は引数なし/ブロック付きで)呼ばれます。
# Rails::Initializer は初期化処理で2回呼ばれる・・・?


Rails::Initializer

前置きはこのぐらいにして、Rails::Initializer のコードをみてみます。
コメントとか、いろいろはしょって骨格だけ。


# rails/lib/initializer.rb
require 'logger'
require 'set'
require 'pathname'

$LOAD_PATH.unshift File.dirname(__FILE__)
require 'railties_path'
require 'rails/version'
require 'rails/plugin/locator'
require 'rails/plugin/loader'
require 'rails/gem_dependency'
require 'rails/rack'


RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV)

module Rails
class Initializer
def self.run(command = :process, configuration = Configuration.new)
yield configuration if block_given?
initializer = new configuration
initializer.send(command)
initializer
end

def process
Rails.configuration = configuration

check_ruby_version # (1)
install_gem_spec_stubs # (2)
set_load_path # (3)
add_gem_load_paths # (4)
require_frameworks # (5)
set_autoload_paths # (6)
add_plugin_load_paths # (7)
load_environment # (8)
preload_frameworks # (9)
initialize_encoding # (10)
initialize_database # (11)
initialize_cache # (12)
initialize_framework_caches # (13)
initialize_logger # (14)
initialize_framework_logging # (15)
initialize_dependency_mechanism # (16)
initialize_whiny_nils # (17)
initialize_time_zone # (18)
initialize_i18n # (19)
initialize_framework_settings # (20)
initialize_framework_views # (21)
initialize_metal # (22)
add_support_load_paths # (23)
check_for_unbuilt_gems # (24)
load_gems # (25)
load_plugins # (26)
add_gem_load_paths # (27)
load_gems # (28)
check_gem_dependencies # (29)
return unless gems_dependencies_loaded # (30)
load_application_initializers # (31)
after_initialize # (32)
initialize_database_middleware # (33)
prepare_dispatcher # (34)
initialize_routing # (35)
load_observers # (36)
load_view_paths # (37)
load_application_classes # (38)
disable_dependency_loading # (39)
Rails.initialized = true
end

~~略~~

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

~~略~~
end
end


(runメソッドだけ取り出してみます。)

def self.run(command = :process, configuration = Configuration.new)
yield configuration if block_given?
initializer = new configuration
initializer.send(command)
initializer
end

(1行目) メソッドシグネチャ。Initializer.runは引数を2つ(command, configuration)とります。Configurationは、同じRailsモジュール内で定義されているクラス。
(2行目) ブロックを与えると、configurationがyieldされます。
(3行目) Initializerがnewされます。いきなり new とあるのは、self.new の略記らしい。普段Javaを使っていると、new Configuration()に見えてまぎらわしい。(--;;
(4行目) commandで与えられたメソッドを呼び出します。
(5行目) Initializerをreturn(?)

前置きでみた2つのInitializer.runについて見直すと、

(1)
config/boot.rb 内での Initializer.run呼び出しは

Rails::Initializer.run(:set_load_path)

とcommand引数が与えられているため4行目は set_load_path メソッドの呼び出しとなります。

(2)
config/environment.rb 内での Initializer.run呼び出しは

Rails::Initializer.run do |config|
# settings
end

で、command引数が省略されているため4行目は processメソッド(デフォルト)の呼び出しとなります。ブロックが与えられているため、2行目のyieldも実行されます。

肝となるのは、(2)のprocessメソッドを呼び出しているほうになります。

processメソッドはすごくシンプルで、サブメソッドを順々に呼び出しているだけです(呼ばれるメソッド名を眺めるだけでも、なんとなくやっていることがわかる)。


今日はここまで。。。
次から、processメソッドで呼ばれるメソッド (1)~(39) を読んでいきたいと思います。
続く。たぶん。

----

0 件のコメント:

コメントを投稿