Railsのコードを読む(1)
ちょっと出来心でRailsのコードを読んでみる。
まずはクライアントからリクエストが飛んでくると、Railsアプリのドキュメントルートである/public以下の.htaccessで、
RewriteEngine On ... RewriteRule ^(.*)$ dispatch.cgi [QSA,L] ...
と記述されてるので、とりあえず処理がdispatch.cgiに移る。
dispatch.cgiは
#!/usr/local/bin/ruby18 require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) require "dispatcher" ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) Dispatcher.dispatch
という感じになっており、cgiを経由してrubyスクリプトが起動する。で、まずはconfig/environment.rbがrequireされる。ここで、File.dirname(__FILE__)でdispatch.cgi自体の相対パスを取得している。
environment.rbはrailsの各種設定をおこなうスクリプトである。文字コードの設定(KCODE)やログの設定をどうするか、などのいろんなアプリの設定が可能。アプリの設定自体もrubyのスクリプトで行っているあたりがJava屋さんにとってはちょっと驚くところ。web.xmlをJavaで書く気分だろうか。で、ここではまず真っ先にconfing/boot.rbが呼び出されている。
boot.rbまでくると、いよいよrubyコードっぽくなってきた。
RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) unless defined?(Rails::Initializer) if File.directory?("#{RAILS_ROOT}/vendor/rails") require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" else require 'rubygems' rails_gem_version = if defined? RAILS_GEM_VERSION RAILS_GEM_VERSION else File.read("#{File.dirname(__FILE__)}/environment.rb") =~ /^[^#]*RAILS_GEM_VERSION\s+=\s+'([\d.]+)'/ $1 end if rails_gem_version rails_gem = Gem.cache.search('rails', "=#{rails_gem_version}.0").sort_by { |g| g.version.version }.last if rails_gem gem "rails", "=#{rails_gem.version.version}" require rails_gem.full_gem_path + '/lib/initializer' else ... Rails::Initializer.run(:set_load_path) end
ここでは、まずRAILS_ROOTという変数にアプリのルートフォルダのパスを設定している。これはアプリ内でもよく使う定数。
次に"rubygems"というライブラリをrequireしている。rubyでは$LOAD_PATHという配列にライブラリを探す場所のパスが格納されており、requireが呼ばれたときはこのパスを見て、requireを要求されたライブラリがないかどうかを探す。Javaのクラスパスと同じようなもんですね。Rubyでは$LOAD_PATH配列を見ればロードパスがわかるし、インタプリタ実行時に明示的に指定してもよい。で私の環境では /usr/lib/ruby/site_ruby/1.8/rubygems.rbを発見。これが読み込まれる(内容は後述)。
次はrails_gem_versionの取得。railsフレームワーク自体のバージョン番号を取得しているわけやね。これも先ほどと同様environment.rbから取得。これはロードするんじゃなくて文字列として読み込んで正規表現でバージョンを引っ掛けている。RAILS_GEM_VERSION = ....の"="の前後にはスペース必須なのか。ふむふむ。
Railsのバージョン番号がわかったので、次はいよいよRailsのロード。Gem.cache.search(..)で、多分キャッシュされたRailsのライブラリコードを探してる。sort_byしてlastしてるのは、つまり1.8.0.0と1.8.0.1が見つかったときに、新しいほうをロードするためかな?この辺は想像。
で、次のgem関数は、なんなんだろう。rails関連のクラスを全部ロードしている、って感じなのかな。これは要調査。その後、/lib/initializerというファイルをロード。
最後に、ロードしたInitializarをrunして終了。Initializarが何をやっているのかは後日。
これでenvironment.rbに戻ると、
Rails::Initializer.run do |config| ... end
が実行され、ロード済みのInitializerによって各種設定が読み込まれる、と。
これでrails自体の準備は完了。dispatch.rbに戻って、dispatherをロード。
dispatherが起動する前に、ADDITIONAL_LOAD_PATHSがどーたらこーたらやっているが、不明。Apache関連のなんかの設定?
そんなわけで最後にdispatherによってリクエストのディスパッチが行われ、いよいよRailsの世界へ。。。。。
いまはRailsをCGIで動かしてるので、アクセスがあるたびにgemやrailsのロードを繰り返しているのだな。gemのcacheはこのへんを考慮していたのかもしれんが、ちょっと重そうだな。
以上、出来心でRailsコードリーディングでした。たぶん続く。