Rails のモジュールオートロード機能


Rails では require しなくても、初めてあるクラスを使ったとき、その定義をファイルを自動的にロードしてそのクラスを使えるようにしてくれるという便利な機能がある。それゆえ、たとえばコントローラのなかでモデルクラスを参照しても Ruby インタプリタから怒られないわけだ。


その仕組みの概容は、舞波氏が約2年も前に、くまくまー日記の中で明らかにしている。それを読んでください・・・ではつまらないので、自分の勉強結果を記したいと思う。基本原理は変わらないようだが、舞波氏の書いた LoadingModule はすでに廃れてしまっている。というわけで、Rails 1.2.3 では何をやっているかということを書きたい。
(もちろん内容は間違っているところもあると思うのでやさしく突っ込みをいれてくれるとうれしいです)

原理的には, active_support/dependencies.rb で、

1. const_missing が発生
2. クラス名→ファイル名変換 #Inflector.underscore(name)
3. ロードパスから対象ファイルを検索
(くまくまー日記より引用)

という流れはいまも同じである。

次のサンプルプログラムについて見てみよう。

▼ モジュールをオートロード
require 'rubygems'
require 'active_support'
Dependencies.load_paths = [File.expand_path(".")]

class Loading
  AutoloadedClass
end
(loading.rb)
▼ オートロードされるモジュール
class AutoloadedClass
  puts "AutoloadedClass loaded!"
end
(autoloaded_class.rb)

として、それぞれを loading.rb, autoloaded_class.rb と言う名前で同じフォルダに保存する。loading.rb を実行すると、

AutoloadedClass loaded!

と表示されるはずだ。


オートロードのパスは Dependencies.load_paths というモジュール変数で指定する。ここでは、単に ActiveSupport を使っただけなので、Dependencies.load_paths は空だった。script/console でRails の 実行環境を読み込むと、

% ~/dev/radrails/TicketMill$ script/console
Loading development environment.
>> Dependencies.load_paths
=> ["script/../config/../config/../test/mocks/development", "script/../config/../config/../app/controllers/", "script/../config/../config/../app", "script/../config/../config/../app/models", "script/../config/../config/../app/controllers", "script/../config/../config/../app/helpers", "script/../config/../config/../components", "script/../config/../config/../config", "script/../config/../config/../lib", "script/../config/../config/../vendor", "/usr/local/lib/ruby/gems/1.8/gems/rails-1.2.3/builtin/rails_info/", "script/../config/../config/../vendor/plugins/active_heart/lib", "script/../config/../config/../vendor/plugins/file_column/lib"]

とか表示される。../ がうざいので、見やすく加工すると、こんな感じ。

=> ["test/mocks/development", "app/controllers", "app", "app/models", "app/controllers", "app/helpers", "components", "config", "lib", "vendor", "/usr/local/lib/ruby/gems/1.8/gems/rails-1.2.3/builtin/rails_info", "vendor/plugins/active_heart/lib", "vendor/plugins/file_column/lib"]

app/controllers がなぜか2回出てくるが、これが基本的にはモジュールをオートロードするときの検索順になっている。
(Dependencies#search_for_file 参照)


A::B のようなモジュール名の扱いとか、詳しいことは、Dependencies#load_missing_constant を参照。これがオートロード機能のコアなメソッドである。50行弱のコードで、ひたすら地道に処理しているだけである。要は、

1. CamelCaseA::CamelCaseB というクラス名を camel_case_a/camel_case_b.rb という underscore 化されたパスに変換して、ロードパスからファイルを検索する。
2. ファイルがあったらロード。
3. ファイルがなければ、CamelCaseA のスーパークラスを探す。

ということをしている。


今日はこの辺で。みなさんごきげんよう