require
とrequire_dependency
と Ruby 1.8.7 と Ruby 1.9.2
先日、Redmineの Version 1.2.2 もリリースされたので、プラグインを書くときに気になった、require
とrequire_dependency
の違いおよびRuby のバージョンによるrequire
の挙動の違いについて書こうと思います。
とは言っても、よく書かれている「カレントディレクトリがロードパスに含まれなくなった」ということではありません。
なお、これは Rails 2.3.11, 3.1.0 を見て書いています。また、Redmine は Version 1.2.2 を見ながら書いています。
require
とrequire_dependency
Rails のコードを見たことがある人は分かると思いますが、Rails のコードではrequire
の代わりにrequire_dependency
が使われていることに気付くと思います。
それぞれ簡単にまとめると以下のようになります。
require
- Ruby の組込み関数。正確には、
Kernel
モジュールのメソッド。 - 引数で渡された .rb ファイルなどを読み込む。ただし、既に読み込み済みのファイルは読み込まない。
- 読み込まれたモジュールは
$"
,$LOADED_FEATURES
に格納されて管理される。 require_dependency
- Rails が定義した独自のメソッド。
activesupport/lib/active_support/dependencies.rb
で定義されている。 - 引数で渡された .rb ファイルなどを読み込む。ただし、既に読み込み済みのファイルは読み込まない。development モードで実行している場合は
load
を使って読み込まれる。production モードで実行している場合は、require
を使って読み込まれる。 - これは、development モードで実行している場合は、動的にファイルを何度も読み込む必要があるため。
Redmine プラグインで注意すること
結論から書くと、Redmine プラグインの中では、Redmine のモデル、ビュー、コントローラー、ヘルパーを読み込むには、require_dependency
を使うべしということです。
ここで、require
を使うと失敗します。
上の説明で書いたように、production モードで実行するならどちらでも結局require
が呼ばれるので変わらない、と思うかもしれませんが、そうではありません。
以下で順に説明します。
Ruby 1.8.7 と Ruby 1.9.2 でのrequire
の挙動の違い
まず、require
の挙動をもう少し詳しく見てみます。
Ruby 1.8.7 でのrequire
の挙動
main.rb
から、required.rb
を、下記のようにrequire
する場合を考えます。
1 2 | # required.rb puts( "loaded" ) |
1 2 3 4 5 6 | # main.rb puts( "check 1" ) require( "required" ) puts( "check 2" ) require( "required" ) # 二回目の require ではロードされない。 puts( "end" ) |
ここでmain.rb
を実行すると、出力は下記のようになり、二回目のrequire
ではrequired.rb
がロードされていないことが分かります。
1 2 3 4 5 | $ ruby main.rb check 1 loaded check 2 end |
これは当然の挙動だと思います。
次に、以下のように、相対パスや絶対パスを指定してrequired.rb
を読み込んでみます。
1 2 | # required.rb puts( "loaded" ) |
1 2 3 4 5 6 7 8 | # main.rb puts( "check 1" ) require( "required" ) puts( "check 2" ) require( "./required" ) # 相対パスで指定 puts( "check 3" ) require( File .expand_path( "required" )) # 絶対パスで指定 puts( "end" ) |
出力は以下のようになります。
1 2 3 4 5 6 7 8 | $ ruby main.rb check 1 loaded check 2 loaded check 3 loaded end |
このように、予想に反して (かどうかは分かりませんが) 三回とも読み込まれています。
Ruby 1.9.2 でのrequire
の挙動
一方で、同じコードを Ruby 1.9.2 で実行してみます。
ただし、Ruby 1.9.2 では、カレントディレクトリがロードパスに含まれなくなっているので、先頭で追加してやった状態で実行してみます。
1 2 | # required.rb puts( "loaded" ) |
1 2 3 4 5 6 7 8 9 | # main.rb $:.unshift( "." ) # カレントディレクトリをロードパスに追加 puts( "check 1" ) require( "required" ) puts( "check 2" ) require( "./required" ) # 相対パスで指定 puts( "check 3" ) require( File .expand_path( "required" )) # 絶対パスで指定 puts( "end" ) |
出力は以下のようになります。
1 2 3 4 5 6 | $ ruby1.9 hoge.rb check 1 loaded check 2 check 3 end |
今回は、一度しか読み込まれませんでした。
このように、Ruby のバージョンによってrequire
の挙動は微妙に異なります。
Ruby 自体のソースを見れば分かりますが、「既に読み込まれたか」を表すリストである$"
, $LOADED_FEATURES
に記憶する方法が、Ruby 1.8.7 と Ruby 1.9.2 では異なるようです。
Ruby 1.8.7 では指定されたファイルをそのままの形で$LOADED_FEATURES
に覚えておきます。
そのため、require("required")
やrequire("./required")
など、指定方法が異なると、複数回ロードされることとなります。
一方で、Ruby 1.9.2 では、常に絶対パスに直して覚えるようになっています。 その結果、実際に同じファイルであれば、指定方法が異なっても複数回ロードされることはありません。
Redmine プラグインでrequire_dependency
を使わないと正しく動作しない理由
以上を踏まえて、なぜ production モードであっても Redmine プラグインでrequire_dependency
を使わないとうまく動作しないのか、について書きます。
Redmine は、現在も Ruby 1.8.7 で動作しています。
そのため、あるファイルをrequire
する際に、絶対パスで指定した箇所とファイル名だけを指定した箇所がある場合、そのファイルは二重にロードされることになります。
一方で、require_dependency
では、コードを追うと分かりますが、最終的に引数のファイル名を絶対パスに展開してからrequire
もしくはload
します。
ここで、以下のように書いてしまった場合を考えてみます。
1 2 3 4 5 6 7 8 9 | # Redmine プラグインの lib 以下のファイル require( "application_helper" ) module ApplicationHelper def format_activity_title(text) # h(truncate_single_line(text, :length => 100)) # original h(truncate_single_line(text, :length => 110 )) end end |
この例では、ApplicationHelper
モジュールのformat_activity_title
メソッドを上書きしています。
しかし、このファイルとは別のファイルでrequire_dependency("application_helper")
と書かれていると、そちらではapplication_helper
が絶対パスに展開されてから、require
に渡されることになります。
そのため、ファイル名だけを指定してrequire("application_helper")
としたときとは別のファイルとして扱われてしまい、別途、application_helper.rb
がロードされることになります。
その結果、せっかく上書きしたメソッドが再度上書きされてしまい、変更が反映されなかったように見えてしまいます。
というわけで、素直にrequire_dependency
を使うようにするのがよいと思います。
0 件のコメント:
コメントを投稿