スプーキーズの中の人。

スプーキーズの中の人が徒然なるままに、垂れ流します。

Railsの解読(1)

Railsで開発するのはもう一年たちました。 しかし、Railsの仕組みはよく分からないのです。最近LighttpdのFastCgiでRailsを動かすときに、迷いました。

週末家でいらいらして、Railsのソースコードを読んで見ました。

みんな知っているように、RailsはActiveRecord, ActiveSupport, ActionControllerとActionViewによって構成されています。ActiveRecordはO/R Mappingで、ActiveSupportはrubyを拡張するライブラリです。 Railsの中心にあるのはやはりActionControllerです。

一つのRequstを送る際に、Railsはどういう流れで処理するのかに注目しつつ、ソースコードを解読しました。

Mongrelにしても、FastCgiにしても、最初はCgiRequestを作って、Dispatcher.dispatchを呼び出します。 Dispatcher.dispatchは主に下の二行です。

controller = ActionController::Routing::Routes.recognize(request)
controller.process(request, response).out(output)

↓ Routingはroutes.rbにしたがって、urlを解析して、controllerのクラスを返します。 

def self.process(request, response)
  new.process(request, response)
end

def process(request, response, method = :perform_action, *arguments) #:nodoc:
  initialize_template_class(response)  #ActionViewのクラスの取る
  assign_shortcuts(request, response)  #params, session, requestなどのショートカットを設定する
  initialize_current_url
  assign_names  #@action_nameの書き込み
  forget_variables_added_to_assigns

  log_processing
  send(method, *arguments)  #!!perform_actionの実行

  assign_default_content_type_and_charset
  response
ensure
  process_cleanup
end

def perform_action
  if self.class.action_methods.include?(action_name)
    #Actionの呼び出し
    send(action_name)
     #Actionの中でrenderとredirect_toが実行されなかったら、デフォルトのテンプレートを表示する
     render unless performed?
  elsif respond_to? :method_missing
    #Actionがない場合、method_missingを呼び出す
    send(:method_missing, action_name)
     render unless performed?
  elsif template_exists? && template_public?
    render
  else
    raise UnknownAction, "No action responded to #{action_name}", caller
  end
end

これでRailsの流れをちょっと捕まえました。

しかし、そんなに単純ではありません。 様々な処理が見えていません! FilterでもBenchmarkでも、Sessionでも。 いったいどこにあるのか? 答えはここです。(benchmark.rb)

module Benchmarking #:nodoc:
  def self.included(base)
    base.extend(ClassMethods)
    base.class_eval do
      alias_method_chain :perform_action, :benchmark
      alias_method_chain :render, :benchmark
    end
  end
  #省略
end

alias_method_chain :perform_action, :benchmarkをやると、下の二行と同じです。

alias_method :perform_action_without_benchmark, :perform_action
alias_method :perform_action, :perform_action_with_benchmark

これでperform_actionはテンプレートメソッドみたいに、名前変えずにbenchmarkの動作が組み込まれました。むしろ、rubyでのAOPとみなされるでしょう。 さらに、ちょっと考えてみると、perform_actionに対して、alias_method_chainは何回適応しても、うまくいけます。すっごいです!

「ModelやControllerはいつロードされましたのか?」ともう一つの疑問を抱いてきました。 答えはdependencies.rbです。DependenciesはObjectのconst_missingとrequireをオーバーライドしています。それで毎度Controllerなどをリフレクションでインスタンス化する際に(const_missingが呼び出される)、Railsのネーミングルールによって特定のフォルダーから読み込んで、ロードするのです。

これでRailsの仕組みをおおざっぱに理解できるでしょうか。 何か間違ったら、皆さんぜひ指摘してください。