Hatena::ブログ(Diary)

akimatter このページをアンテナに追加 RSSフィード

2012-01-08

developmentを実現したいのでコードを読んでみる#1

| 23:30 |  developmentを実現したいのでコードを読んでみる#1を含むブックマーク  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

https://github.com/rails/rails/blob/3-1-stable/actionpack/lib/action_dispatch/middleware/stack.rb#L104


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

https://github.com/rails/rails/blob/master/railties/guides/code/getting_started/config/application.rb#L12


で、この Rails::ApplicationRails::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

すばらしい!


続きはまた明日

トラックバック - http://d.hatena.ne.jp/akm/20120108
最近読んだ本
  • 情熱プログラマー ソフトウェア開発者の幸せな生き方
  • 禁煙セラピー[セラピーシリーズ]
  • 入門git
  • 入門Git
  • もやしもん(8) (イブニングKC)
  • JRuby 徹底入門
  • 入門Subversion―Windows/Linux対応
  • Ship It! ソフトウェアプロジェクト 成功のための達人式ガイドブック
  • プログラミングRuby 第2版 言語編
  • プログラミングRuby 第2版 ライブラリ編