FIVETEESIXONE

require returns array rather than bool


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 を返す

requireKernel モジュールで定義されているメソッドです。その仕様は、どう読んでも bool を返すようにしか見えません。ではバージョンの違い?いや、そういうことでもないようです。

読んでいた Ruby on Rails チュートリアルは日本語翻訳版ですが、原著では版が更新されているので、最新版の記述を見てみることにしました。

>> require './example_user'     # This is how you load the example_user code.
=> true

お?truetrueになってるじゃん!なあんだ!ただの誤植か何かだったのか!解決!

…いやいや、こんな間違いしねーよね、ふつう。ってことは、呼び出している 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 を自分で定義したものに差し替えています。