Ruby 1.9.2p0 で requireとrequire_relativeについて調べてみた
先日のこのエントリの続き。
http://d.hatena.ne.jp/tagomoris/20100924/1285322426
Ruby内部のコードを追っかけるのは時間かかるのでとりあえず置いておいて、1.9.2になってLOAD_PATHの仕様変更(カレントディレクトリが除外された)にともなって動作が変わった require と、追加された require_relative の実際の動作について追ってみた。手がかりは前のエントリにもらったコメント。
まず大前提
eval の第3引数にファイルパスを渡すことで、評価される文字列の中に __FILE__ (など)がある場合は第3引数(および第4引数の行数)を展開してくれる。
これがわかっておりませんでした。rackupすると最終的にconfig.ruをevalしてるのは以下のコード。
/* rack-1.2.1/lib/rack/builder.rb */ module Rack /* snip */ class Builder def self.parse_file(config, opts = Server::Options.new) options = {} if config =~ /\.ru$/ cfgfile = ::File.read(config) if cfgfile[/^#\\(.*)/] && opts options = opts.parse! $1.split(/\s+/) end cfgfile.sub!(/^__END__\n.*/, '') app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", TOPLEVEL_BINDING, config else require config app = Object.const_get(::File.basename(config, '.rb').capitalize) end return app, options end
ということで、evalするときにconfig.ruのパスが渡ってるので、ファイル内に __FILE__ 書いといたら正常に config.ru のパスに展開されるわけですね。これならポータブルなやりかたと言える。
require_relativeは?
で、じゃあ eval に第3引数を渡してるなら require_relative が使えてもおかしくない気がするんですけど。いちおう追試してみましたが、やっぱりダメでした。こんな感じ。
dhcp79:hoge tagomoris$ irb19 irb(main):001:0> data = IO.read('/Users/tagomoris/tmp/hoge/evalscript.rb') => "require_relative 'required_module'\n" irb(main):002:0> data => "require_relative 'required_module'\n" irb(main):003:0> returned = eval data, TOPLEVEL_BINDING, '/Users/tagomoris/tmp/hoge/evalscript.rb' LoadError: cannot infer basepath from /Users/tagomoris/tmp/hoge/evalscript.rb:1:in `require_relative' from /Users/tagomoris/tmp/hoge/evalscript.rb:1:in `<main>' from (irb):3:in `eval' from (irb):3 from /usr/local/bin/irb19:12:in `<main>' irb(main):004:0>
ruby-dev MLをひと通り漁ってみたけど誰も何も言ってないなー。投稿してみようかなあ。でもどうなんだろうな。
requireのやりかた
前のエントリにコメントで require './app' でいいじゃない、というのがありました。
いやそれって '.' からの相対パス*1なんだからダメでしょ……と思ったら、なんと 1.9.2p0 でも通ってしまった。これってバグなんじゃねーの? リファレンスの記述でも、はっきりと $: ($LOAD_PATHね)から探す、と書いてあるし。
ちなみにカレントディレクトリに app.rb がないと動かないので、rackup コマンドだけならともかくアプリケーションサーバにデプロイすると動かないんじゃないかと思います。だから実際に今の用途としては使えないかな。
で __FILE__ が使えるんでこれを使いましょう、ということですが、せっかく require_relative で全部書き直したのにアプリケーションのディレクトリを LOAD_PATHに入れるのはなー、というのが個人的な意見。ちなみにその場合のコードは前のエントリで id:aquarla に書いてもらったこちらのコード。
config.ru 内で読み込むファイルが多いとLOAD_PATHに入れないとやってられないような気もするけど、普通はアプリケーション本体のファイルひとつだけだと思うので、それだったらrequire用の絶対パスを生成してもまあいいんじゃないでしょうか、ということでこっちにしてみた。
app_dir = File.expand_path(File.dirname(__FILE__)) require app_dir + '/app' run Sinatra::Application
これならどこに持っていっても動く。やれやれ。
さて、requireとrequire_relativeの動作についてはどうしようかな……。なんとなく他にもハマる人はいそうな気がするんだけど。