Ruby on Rails チュートリアルの「4.4.5 ユーザークラス」を読んでいるときに気になるところがありました。
>> require './example_user' # example_user のコードを読み込む方法
=> ["User"]
これは、example_user.rb
ファイルに定義された User
クラスのインスタンスを作ってみよう!的なお勉強で、そのクラスを読み込むために Rails コンソール上で require
を実行したときの出力です。気になったのは、require
の戻り値が ["User"]
になっているところ。
あれ? require
の戻り値は true / false じゃないの?
実際、手元の Rails コンソールで実行したときの結果は、
>> require './example_user'
=> true
となっています。いったいなぜ?
require が真偽値ではなく array を返す
require
は Kernel
モジュールで定義されているメソッドです。その仕様は、どう読んでも bool
を返すようにしか見えません。ではバージョンの違い?いや、そういうことでもないようです。
読んでいた Ruby on Rails チュートリアルは日本語翻訳版ですが、原著では版が更新されているので、最新版の記述を見てみることにしました。
>> require './example_user' # This is how you load the example_user code.
=> true
お?true
?true
になってるじゃん!なあんだ!ただの誤植か何かだったのか!解決!
…いやいや、こんな間違いしねーよね、ふつう。ってことは、呼び出している require
が別な何者かにすり替わっているとしか考えられない。
require を追ってみる
いったい、ここで呼び出している require
は何者なんでしょうか。幸い、我々には pry という文明の利器があるので、それを追うのは簡単です。
素の pry
[1] pry(main)> $ require
From: load.c (C Method):
Owner: Kernel
Visibility: private
Number of lines: 5
VALUE
rb_f_require(VALUE obj, VALUE fname)
{
return rb_require_safe(fname, rb_safe_level());
}
まあ、何ていうか普通ですね。Kernel
モジュールの private
メソッドとして定義されているようで、期待した通りの内容です。
Rails コンソール (pry-rails)
[1] pry(main)> $ require
From: /Users/5t111111/rails_projects/sample_app/vendor/bundle/ruby/2.0.0/gems/activesupport-4.0.5/lib/active_support/dependencies.rb @ line 227:
Owner: ActiveSupport::Dependencies::Loadable
Visibility: public
Number of lines: 5
def require(file)
result = false
load_dependency(file) { result = super }
result
end
おっと!やっぱり別の何者かが登場しましたね。
Owner は ActiveSupport::Dependencies::Loadable
となっていて、確実にさっきの結果とは異なっています。犯人はおそらくこの人で間違いない気がします。ただ、この require
が返す値は array
ではなく、どうやら真偽値であるようです。もちろん、これは Rails コンソールで実行したときの結果と合致しているので当然といえば当然です。ということは、ActiveSupport のバージョンが関係しているのでしょうか。
旧バージョンの ActiveSupport
古いバージョンの ActiveSupport を確認してみます。これは、実際に実行して確認してみるのが一番早そうです。それなら、Ruby のバージョンも古いやつにしといたほうがいいでしょう。ただ、今更古いバージョンの Ruby を自分のところにインストールしたくはないので、EC2 でさくっと CentOS 6 のインスタンスを起動することにします。CentOS 6 の Ruby パッケージは 1.8.7 なのでちょうどよいです。
早速試してみます。ActiveSupport (Rails) のバージョンは 2.3.8 です。
irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'active_support'
=> true
irb(main):003:0> require 'fileutils'
=> ["FileUtils"]
うん、やっぱりそうですね。ActiveSupport を読み込んだあとは、require
メソッドが array
を返しています。
まとめると、ActiveSupport は require
メソッドを上書きしており、意図はわからない (調べてない) が、それはかつて array
を返していた。それが、いつからかはわからない (これも調べてない) が、Kernel
モジュールの require
と同じように真偽値を返すように変わった。ということのようです。
もう少し詳しく
このとき、ActiveSupport が何をやっているのかもう少し詳しく見てみます。
ActiveSupport は、Object
クラスに ActiveSupport::Dependencies::Loadable
をインクルードしています。これは継承チェーンを見てみると、確かに組み込まれていることがわかります。
[1] pry(main)> Object.ancestors
[
[0] Object < BasicObject,
[1] ActiveSupport::Dependencies::Loadable,
[2] PP::ObjectMixin,
[3] JSON::Ext::Generator::GeneratorMethods::Object,
[4] Kernel,
[5] BasicObject
]
Ruby では、トップレベルでレシーバなしで実行したメソッドは、Object
クラスのオブジェクトがレシーバとなります。Kernel
モジュールの require
がレシーバなしで実行できるのも、Object
クラスが Kernel
モジュールをインクルードしているからです。ActiveSupport は、Object
クラスに ActiveSupport::Dependencies::Loadable
をミックスインすることによって、レシーバなしで実行される require
を自分で定義したものに差し替えています。