RubyWaves における AutoCode について
RubyWavesは結構面白いですよね。そんなわけで目玉機能のひとつであるAutoCodeがどのようなものか調べてみました。
まず最初に断っておくと、AutoCodeは普通にRubyのライブラリなので、RubyWaves以外にも使えます。なので、これは素晴しい!と思ったら誰でも使えるわけです。偉い!
さて、AutoCodeは次のようなライブラリです。README(version 0.9.2 のもの)から引用します。
Autocode makes it relatively easy to automatically (re)load or even generate classes and modules on the fly. You can use it like this: require 'autocode' module Application extend Autoload autoload true directories :configurations, :models, :views, :controllers end This will attempt to load code dynamically from the given directories, using the module name to determine which directory to look in. Thus, Application::CustomerModel could load the file models/customer_model.rb.
というわけで、クラスやモジュールを自動的にロードしたりリロードしたりしてくれるものですね。例では Application::CustomerModel を参照したら自動的に models/customer_model.rb を読み込んでくれるというわけで、Rails的なModelのロードが可能というわけですね。また、Application.reloadとすると、以上のようにしてロードされたクラスやモジュールを再読み込みしてくれます。これはとっても便利ですね!
心配性な人は「既に読み込んだクラスとかモジュールの影響が残ったりしないの?」と思うかも知れません。でも大丈夫、AutoCodeはちゃんとその辺も考えてくれています。
Rather than simply call load on various files, it actually first calls remove_const on all the elements that are intended to be reloaded. This completely wipes any constants or associated method definitions from memory, provided there are no references to them hanging around.
http://rubywaves.com/architecture
以上にあるように、リロードする前にはきちんと remove_const してくれます。例えば先程の例で言えば、Application::CustomerModelをリロードする時にはまず Application.remove_const(:CustomerModel) のようにするわけですね。これなら Application::CustomerModel に結びついているモジュールが消去されるので、リロードする時に問題になることはありません。
...と書くと薔薇色なのですが、そうした利点を十分に享受するためにはあまり明示的には文書化されていない規約に従う必要があります。つまり、なんでもかんでもロードしたりリロードしたり出来るわけではありません。この点についてまとめてみたいと思います。
規約:クラス/モジュール名とファイル名の対応関係
まず、クラス/モジュール名とファイル名の対応関係は次のようである必要があります。
fname = ( cname.to_s.gsub(/([a-z\d])([A-Z])/){ "#{$1}_#{$2.downcase}"} + '.rb' ).downcase
つまり先程の例のようにクラス/モジュール名が CustomerModel であれば customer_model.rb です。これはActiveSupportのInflector.underscoreの挙動と良く似ていますが、AutoCodeでは例えばクラス/モジュール名が HTTPServer のような場合にはファイル名は httpserver.rb となります。Inflector.underscore だと http_server なんですよね。ちょっと違います。
規約:ファイル名はディレクト間において唯一に
READMEにある例のように、AutoCodeではどのディレクトリからファイルを探すのかを指定する必要があります。
module Application extend Autoload autoload true directories :configurations, :models, :views, :controllers end
というわけで、この場合には ./configurations, ./models, ./views, ./controllers からファイルを探すことになります。その探し方は以下のようになっています(autoload.rb:53行目より)。
dir = ( @directories ||= ['.'] ).find do |dir| File.exist?( dir / fname ) end if ( dir && load( dir / fname ) && c = const_get( cname ) ) ( @reloadable ||= [] ) << cname; return c else # 以下略
つまり、単純にディレクト名+ファイル名で存在するかどうかをチェックして、とりあえずあったらロードしてみて、うまいこと const_get できたら成功、というわけです。このようになっているため、当たり前ですが、同名のファイルがこれらの複数のディレクトリに存在していては困ります。
規約:ファイルには唯一のクラス/モジュールを定義すること
またファイルにはちゃんとクラス/モジュールが定義されている必要があって、またそのクラス/モジュールのみが唯一定義されている必要があります。例えばApplication::CustomerModelに対応するファイルcustomer_model.rbが以下のようなものであると困ります。
HOGE = "hoge" # 余計なもの class Application::CustomerModel # これはファイル名に対応 # ... end class Application::CustomerModel2 # 余計なもの # ... end
この場合、ロードする時は良いのですが、リロードする時にHOGEとApplication::CustomerModel2について警告が出るものと思います。
まとめ
簡単に言えばActiveSupportの一部を切り出したものと思えば良いのでしょうか。RubyWavesではAutoCodeとLiveConsoleを組み合わせて、稼働中のサーバにパッチを当てちゃおうと目論んでいるようです。とても野心的で素敵だと思います。そもそも私がAutoCodeに興味を持ったのはRamazeさんのSourceReloadが定数のリロードで困るからなんかヒントないのかなぁと思ったからなのですが、残念ながらRamazeさんはAutoCodeのような規約がとっても似合わないと思いますので参考にはなりませんでした。しかし AutoCode, Rack, Sequel あたりを組み合わせるととっても簡単にWebアプリのフレームワークを作れちゃうんだろうなぁ、と思います。知っていて損はないライブラリだと思いました。