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アプリのフレームワークを作れちゃうんだろうなぁ、と思います。知っていて損はないライブラリだと思いました。