developmentを実現したいのでコードを読んでみる#1
Railsのdevelopmentモードのように特定のディレクトリ以下のソースコードを適切なタイミングで読み直す機能を作りたいのですが、実際Railsって何やっているのか分からんので調べます。
cache_classes
railsアプリの config/environments/development.rb には大抵
config.cache_classes = false
と書いてあります。クラスのキャッシュを無効にするって意味っすね。
この設定がどこで使われているのか、railsの設定はrailtiesに書いてあるのでgrepしてみました。
-*- mode: grep; default-directory: "~/.rvm/gems/ruby-1.9.2-head@tengine_console/gems/railties-3.1.3/lib/rails/" -*- Grep started at Sun Jan 8 20:35:42 grep -nri cache_classes . ./application/bootstrap.rb:64: ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load ./application/configuration.rb:9: :cache_classes, :cache_store, :consider_all_requests_local, ./application/configuration.rb:95: self.cache_classes = true ./application/finisher.rb:49: if config.cache_classes && !$rails_rake_task ./application/finisher.rb:70: if config.cache_classes && !config.dependency_loading ./application.rb:22: # "allow_concurrency", "cache_classes", "consider_all_requests_local", "filter_parameters", ./application.rb:168: middleware.use ::ActionDispatch::Reloader unless config.cache_classes ./generators/rails/app/templates/config/environments/development.rb.tt:7: config.cache_classes = false ./generators/rails/app/templates/config/environments/production.rb.tt:5: config.cache_classes = true ./generators/rails/app/templates/config/environments/test.rb.tt:8: config.cache_classes = true ./railtie/configuration.rb:39: # Third configurable block to run. Does not run if config.cache_classes
実はloadが使われる
developmentモードではActiveSupport::Dependenciesはrequireではなく、loadを使ってロードします。
ActiveSupport::Dependenciesが動く場合ってことは、const_missingあたりから命名規則にしたがってファイルをロードするあたりの話っすね、たぶん。
./application/bootstrap.rb:64: ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load
middleware.use ::ActionDispatch::Reloader
./application.rb:168: middleware.use ::ActionDispatch::Reloader unless config.cache_classes
一番大事そうなのはココ。developmentモードではミドルウェアがActionDispatch::Reloaderを使うそうです。
middlewareってRackとかの話だよね?これまでちゃんと調べたことなかったので、middlewareを調べましょう!
middleware
まず、ここに登場しているmiddlewareは何かと言えば、ここに書いてある。
https://github.com/rails/rails/blob/3-1-stable/railties/lib/rails/application.rb#L146
def default_middleware_stack ActionDispatch::MiddlewareStack.new.tap do |middleware| if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache require "action_dispatch/http/rack_cache" middleware.use ::Rack::Cache, rack_cache end #... end end
ActionDispatch::MiddlewareStack.new.tapに渡されるブロックの引数でした。
ActionDispatch::MiddlewareStack
これは何ぞ?と検索してみる。ActionDispatchだからactionpack以下にあるはず・・・
https://github.com/rails/rails/blob/3-1-stable/actionpack/lib/action_dispatch/middleware/stack.rb
ココですね。
ActionDispatch::MiddlewareStackクラスの定義の中に、Middlewareクラスの定義があって、ActionDispatch::MiddlewareStackクラスの具体的な記述は
https://github.com/rails/rails/blob/3-1-stable/actionpack/lib/action_dispatch/middleware/stack.rb#L53
以降に書かれています。
include Enumerableとかしてるし、initializeメソッドで
@middlewares = []
とかやっているので、前述のMiddlewareクラスのオブジェクトを複数個持ってなんかする奴なんでしょうな。
具体的な使われ方として、
middleware.use ::ActionDispatch::Reloader
が意味するところをまずは知りたいんだけど、useメソッドの定義を読むと引数とブロックを、前述のMiddleware.newの引数に渡しちゃってmiddlewareを生成してそれをmiddlewaresに追加してるってことっすね。
def use(*args, &block) middleware = self.class::Middleware.new(*args, &block) middlewares.push(middleware) end
ActionDispatch::MiddlewareStack::Middleware
じゃあその前述のMiddlewareを知っておきたいところなんだけど、
https://github.com/rails/rails/blob/3-1-stable/actionpack/lib/action_dispatch/middleware/stack.rb#L6
def initialize(klass_or_name, *args, &block) @klass = nil if klass_or_name.respond_to?(:name) @klass = klass_or_name @name = @klass.name else @name = klass_or_name.to_s end @classcache = ActiveSupport::Dependencies::Reference @args, @block = args, block end
引数の最初にClassかクラスの名前を期待していて、Classが指定された場合は@klassに代入されるけど、そうじゃない場合は@klassはnilのまま。
それ以外はそのまま@argsに代入される。こいつはどこで使われるかって言うと、
def build(app) klass.new(app, *args, &block) end
buildメソッドで指定されたklass.newの引数として、このメソッドの引数appとともに@argsの内容が渡されるんだけど、klassメソッドにはこう書いてある。
def klass @klass || classcache[@name] end
名前を指定した場合には、classcacheから検索するようになっています。
classcacheはinitializeで指定されているActiveSupport::Dependencies::Referenceですね。
middleware.use ::ActionDispatch::Reloader
もう一度考えてみると、ActionDispatch::MiddlewareStackのインスタンスであるmiddlewareに::ActionDispatch::Reloaderをクラスを指定してuseさせているので、
ActionDispatch::MiddlewareStack::Middleware.new(::ActionDispatch::Reloader)
で生成されたものがmiddlewareには記憶されている。
あとは、どこかでこれのbuildメソッドが呼び出されるタイミングがあるはずなんだけど、
実は ActionDispatch::MiddlewareStack#build で ActionDispatch::MiddlewareStack::Middleware#build が呼び出されます。
https://github.com/rails/rails/blob/3-1-stable/actionpack/lib/action_dispatch/middleware/stack.rb#L109
def build(app = nil, &block) app ||= block raise "MiddlewareStack#build requires an app" unless app middlewares.reverse.inject(app) { |a, e| e.build(a) } end
こいつはどこから呼び出されるのか?これはRails::Engine#appから呼び出されます。
Rails::Engine
railtiesをActionDispatch::MiddlewareStackでgrepしてみると、以下の2つが見つかります。
-*- mode: grep; default-directory: "~/.rvm/gems/ruby-1.9.2-head@tengine_console/gems/railties-3.1.3/lib/" -*- Grep started at Sun Jan 8 22:52:59 grep -nri ActionDispatch::MiddlewareStack . ./rails/application.rb:146: ActionDispatch::MiddlewareStack.new.tap do |middleware| ./rails/engine.rb:606: ActionDispatch::MiddlewareStack.new
それぞれ Rails::Appliation#default_middleware_stack と Rails::Engine#default_middleware_stack から呼び出されています。
Rails::Appliation? そうです。railsアプリのconfig/application.erbに記述されるアレです。
module Blog class Application < Rails::Application
で、この Rails::Application は Rails::Engine を継承しているわけですね。
https://github.com/rails/rails/blob/3-1-stable/railties/lib/rails/application.rb#L36
じゃあこの Rails::Engine はというと、 Rails::Railtie を継承しています。
https://github.com/rails/rails/blob/3-1-stable/railties/lib/rails/engine.rb#L333
Rails::Railtie は何も継承していません。
https://github.com/rails/rails/blob/3-1-stable/railties/lib/rails/railtie.rb#L113
まとめるとこういう継承をしているわけですね。
Rails::Railtie <|---- Rails::Engine <|---- Rails::Application
Rails::Applicationのインスタンスがいつ生成されるのかが知りたくなるわけですが、これを追っかけるのは大変!と思っていたら強い味方発見。
http://guides.rubyonrails.org/initialization.html
すばらしい!
続きはまた明日。