前回、jRubyでコンパイルしてCLASSPATHを通せば、*.classと*.rbを透過的にrequireできることがわかった。
でも、Railsでは*.classをrequireしてくれないので、Railsではどういう風にクラスをロードしているのかを調べてみました。
まずは、models/user.rb をコンパイル後、削除。
> cd app/models/
> jrubyc user.rb
> mv user.rb user.rb_bak
> ls
user.class user.rb_bak
Userをロードしてみると失敗する。
> export CLASSPATH=./app/models
> jruby script/console
>> User
NameError: uninitialized constant User
from /usr/java/jruby-1.1b1/lib/ruby/gems
/1.8/gems/activesupport-1.4.2/lib/active_support
/dependencies.rb:272:in `load_missing_constant'
:
この missing_constantメソッドが、動的クラスローダーのようです。初めてそのクラスにアクセスするときに、ロードしてキャッシュする。ということをしている。
クラスローダーの動き
この active_support/dependencies.rb のソースを辿ってみると・・・
■流れの抜粋
# 最初のクラスロード
def load_missing_constant(from_mod, const_name)
# ファイルを探す。
file_path = search_for_file(path_suffix)
# あれば、load か require
if file_path
require_or_load file_path
end
end
# クラスのソースファイルを探す。
def search_for_file(path_suffix)
# .rb をがっつりくっつけて、
path_suffix = path_suffix + '.rb' \
unless path_suffix.ends_with? '.rb'
# 存在チェックしている。
end
def require_or_load(file_name, const_path = nil)
# .rbがついてることが前提。
file_name = $1 if file_name =~ /^(.*)\.rb$/
expanded = File.expand_path(file_name)
# キャッシュからとるか、
return if loaded.include?(expanded)
# loadするか、
if load?
# require するか、
else
result = require file_name
end
end
という風に、クラスの拡張子が .rbでなければならないという制限がありました。
classをrequireするために
ちょっと(かなり)いたずらに改造してみる。
■require_or_loadでは、*.class なら、単純に requireする。
def require_or_load(file_name, const_path = nil)
if file_name.ends_with? '.class'
return java_class_require file_name
end
:
end
def java_class_require(file_name)
file_name = File.basename(file_name)
file_name = $1 if file_name =~ /^(.*)\.class$/
clazz = require file_name
history << clazz
clazz
end
■*.class で search_for_fileがtrueを返す。
def search_for_file(path_suffix)
#path_suffix = path_suffix + '.rb' unless path_suffix.ends_with? '.rb'
path_suffix = path_suffix + '.class' unless path_suffix.ends_with? '.class'
:
end
と、修正すると、動きます。
> jruby script/console
>> User
=> User
>> User.new
=> <#User ...>
とくにsearch_for_fileの仕様がこのままである限り、jrubyでコンパイルしてもRailsにはロードできないはず。
さて、これからどうするか。比較的簡単に実現できそうですが、そこまでしてやるか?というところで、思考停止。
過程で見つけたTips
■ consoleで、ソースの変更を反映するには、
> ruby script/console
>> reload!
いままで、その都度console再起動してた。
■ スコープ外でloggerを使いたい。
class SomeLib
def logger
::ActionController::Base.logger
end
def your_method
logger.debug('aa')
end
end
■jrubyc が生成するpackage
前回は、
> jrubyc app/**/*.rb
としましたが、これだと、java packageのパスがついてしまうので、(たぶん)よくない。jadしてみたら、こうなってました。
package app.models;
class User{
}