Railsガイドを読む(定数の自動読み込みと再読み込み)


[定数の自動読み込みと再読み込み](http://railsguides.jp/constant_autoloading_and_reloading.html)を読んでいて、よくわからなかったことを調べてみました。この章はRubyとRailsの定数に関する挙動を説明していますが、今回はRubyの挙動について調べます。

```shell
$ ruby -v
ruby 2.2.0p0 (2014-12-25 revision 49005)
```

です。

## はじめに

最近こそこそと[Railsガイド](http://railsguides.jp/)の翻訳をお手伝いしています。自分自身で翻訳することはほとんどなくて、主に出来上がった翻訳のチェックをしています。この章は内容が難しいわりに例となるコードが少ないため、(翻訳が適切でも)意味がとりにくい箇所があります。そこで、コードを書きながら補足説明をしようと思い、まとめました。



## 2 定数更新
[定数更新](http://railsguides.jp/constant_autoloading_and_reloading.html#%E5%AE%9A%E6%95%B0%E6%9B%B4%E6%96%B0)を読む上でのポイントは

1. ネスト(Module.nestingの結果)は名前空間と一致しないことがある
1. 相対定数(`A`)と修飾済み定数(`A::B`)の違いは大きい
1. ブロックは特別
1. トップレベルも変わり者

です。

### 2.1 ネスト

> Ruby内のある場所のネストを調べるにはModule.nestingを使用します

とのことなので、[`Module.nesting`](http://docs.ruby-lang.org/ja/2.2.0/class/Module.html#S_NESTING)を使って、ネストを確認していきます。
ちなみに`[A::B, X::Y]`のところはRailsのmasterでは修正されていて、

```ruby
module X
  module Y
  end
end

module A
  module B
  end
end

module X::Y
  module A::B
    # (3)
    # 例えばここで p Module.nestingすると[A::B, X::Y]が表示される
  end
end
```

が正しいそうです。

ここでは`module A::B`が修飾済み定数か相対定数かでネストは変わります。

```ruby
module X
  module Y
  end
end

module A
  module B
  end
end

module X::Y
  module C
    p Module.nesting
    #=> [X::Y::C, X::Y]
  end
end

module X::Y
  module A::D
    p Module.nesting
    #=> [A::D, X::Y]
  end
end
```

`C`のように相対定数のときはcrefに属しているのに対して、`A::D`のように修飾済み定数のときは独立しているものとして扱われます。`X::Y::A::D`ではないんですよね。

> 特異クラスはclass << objectでオープンされるときにスタックにプッシュされ、後でスタックからポップされる。

```ruby
class A
  class << self
    p Module.nesting
    #=> [#<Class:A>, A]
  end
  p Module.nesting
  #=> [A]
end
```

確かに特異クラスがプッシュされていますね。

> *_evalで終わるメソッドが文字列を1つ引数に取って呼び出されると、そのレシーバの特異クラスはevalされたコードのネストにプッシュされる。

```ruby
class A
end

A.class_eval("p Module.nesting")
#=> [A]
A.module_eval("p Module.nesting")
#=> [A]
A.instance_eval("p Module.nesting")
#=> [#<Class:A>]
A.new.instance_eval("p Module.nesting")
#=> [#<Class:#<A:0x007f94dc0c7150>>]
```

あれ、・・・

```ruby
class A
end

A.class_eval("p Module.nesting.first.singleton_class?")
#=> false
A.module_eval("p Module.nesting.first.singleton_class?")
#=> false
A.instance_eval("p Module.nesting.first.singleton_class?")
#=> true
A.new.instance_eval("p Module.nesting.first.singleton_class?")
#=> true
```

`class_eval`や`module_eval`のときはレシーバをプッシュしているようですね(Railsのmasterでは修正されています)。
ちなみにブロックを渡したときはネストにプッシュされません。

```ruby
class A
end

A.class_eval { p Module.nesting }
#=> []
A.module_eval { p Module.nesting }
#=> []
A.instance_eval { p Module.nesting }
#=> []
A.new.instance_eval { p Module.nesting }
#=> []
```

そのためブロック内で定数を定義しようとすると、トップレベルに定義されたりします。

```ruby
class A
end

A.class_eval("FOO = 10")

class B
end

B.class_eval { BAR = 20 }

p A::FOO
#=> 10
p B::BAR
#=> const.rb:12: warning: toplevel constant BAR referenced by B::BAR
#=> 20
```

> Kernel#loadによって解釈されるコードのトップレベルにあるネストは空になる。ただしload呼び出しが第2引数としてtrueという値を受け取る場合を除く。この値が指定されると、無名モジュールが新たに作成されてRubyによってスタックにプッシュされる。

```ruby
# const2.rb
module A
  p Module.nesting
end
p Module.nesting
```

```ruby
load "const2.rb"
#=> [A]
#=> []
load "const2.rb", true
#=> [#<Module:0x007f8054137ac8>::A, #<Module:0x007f8054137ac8>]
#=> [#<Module:0x007f8054137ac8>]
```

たしかに無名モジュールが追加されていますね。

> ブロックがスタックに何の影響も与えないという事実です。特に、Class.newやModule.newに渡される可能性のあるブロックは、newメソッドによって定義されるクラスやモジュールをネストにプッシュしません。

```ruby
A = Class.new do
  p Module.nesting 
end
#=> []
```

これもたしかにプッシュしていません。

### 2.2 クラスやモジュールの定義とは定数への代入のこと

> RubyはObjectにCという定数を作成し、その定数にクラスオブジェクトを保存します。

トップレベルで定義した定数は`Object`の子として扱われます。詳しくは[こちら](http://d.hatena.ne.jp/keyesberry/20080905/p1)もしくは[こちら](https://banisterfiend.wordpress.com/2010/11/23/what-is-the-ruby-top-level/)をみてください。

> 無名クラスや無名モジュールにひとたび名前が与えられた後は、定数とインスタンスで何が起きるかは問題ではありません。たとえば、定数を削除することもできますし、クラスオブジェクトを別の定数に代入したりどの定数にも保存しないでおくこともできます。名前はいったん設定された後は変化しなくなります。

まずは定数を削除してみます。削除しても`Klass.name`は変わりません。

```ruby
klass = Class.new
p klass.name
#=> nil

Klass = klass
p klass.name
#=> "Klass"
p Klass
#=> Klass

Object.send(:remove_const, :Klass)
p klass.name
#=> "Klass"
p Klass
#=> const.rb:10:in `<main>': uninitialized constant Klass (NameError)
```

次に他の定数にも代入してみます。`p Klass2`が`Klass`と表示されます。へぇー。

```ruby
klass = Class.new
Klass = klass
Klass2 = klass

p Klass.name
#=> "Klass"
p Klass2.name
#=> "Klass"
p Klass
#=> Klass
p Klass2
#=> Klass
```

### 2.4 解決アルゴリズム

> 3\. 見つからない場合、crefに対してconst_missingが呼び出される。const_missingのデフォルトの実装はNameErrorを発生するが、これはオーバーライド可能。

```ruby
module A
  def self.const_missing(name)
    p "const_missing of A"
    super
  end
  module B
    p Module.nesting
    #=> [A::B, A]
    def self.const_missing(name)
      p "const_missing of B"
      super
    end

    C
    #=> "const_missing of B"
    #=> const.rb:10:in `const_missing': uninitialized constant A::B::C (NameError)
  end
end
```

`C`を呼び出した場所でのcrefは`A::B`なので、たしかに`B`の`const_missing`が呼ばれています。

> 2\. 探索の結果何も見つからない場合、親のconst_missingが呼び出される。const_missingのデフォルトの実装はNameErrorを発生するが、これはオーバーライド可能。

> 特に、ネストが何の影響も与えていない点にご注意ください。また、モジュールは特別扱いされておらず、モジュール自身またはモジュールの先祖のどちらにも定数がない場合にはObjectはチェックされない点にもご注意ください。

```ruby
E = "e"

module X
  E = "xe"
end

module C
  # E = "ce"
  def self.const_missing(name)
    p "const_missing of C"
    super
  end
end

module X
  p Module.nesting
  #=> [X]
  p E
  #=> "xe"
  p C::E
  #=> "const_missing of C"
  #=> const.rb:27:in `const_missing': uninitialized constant C::E (NameError)
end
```

`E`は相対定数なのでネストを順に探索し、`X::E`を見つけています。一方で`C::E`は修飾済み定数なので`C`もしくは`C`の継承チェーンしか探索しません。そのため定数を発見できず、`C`(親)の`const_missing`が呼ばれています。

## おわりに

`class_eval`にブロックを渡したときと文字列を渡したときで、どうしてネストが変わるんでしょう。どちらのケースも`def`をするとインスタンスメソッドが定義できる(つまりどちらもクラスのコンテキストにいる)のに。

```ruby
class A
end

A.class_eval("def test; p 'test'; end")
A.class_eval do
  def test2
    p "test2"
  end
end

A.new.test
#=> "test"
A.new.test2
#=> "test2"
```

rubyのコードを読んでみると`NODE_FL_CREF_PUSHED_BY_EVAL`というフラグが影響しているようです。

`Module.nesting`では`NODE_FL_CREF_PUSHED_BY_EVAL`フラグのたっているcrefを無視しています。

```c
# eval.c
static VALUE
rb_mod_nesting(void)
{
    VALUE ary = rb_ary_new();
    const NODE *cref = rb_vm_cref();

    while (cref && cref->nd_next) {
	VALUE klass = cref->nd_clss;
	if (!(cref->flags & NODE_FL_CREF_PUSHED_BY_EVAL) &&
	    !NIL_P(klass)) {
	    rb_ary_push(ary, klass);
	}
	cref = cref->nd_next;
    }
    return ary;
}
```

ブロックを渡した`class_eval`ではpushしたcrefの`NODE_FL_CREF_PUSHED_BY_EVAL`フラグを必ずたてるのに対して、文字列のときはSPECIAL_CONSTの時以外は`NODE_FL_CREF_PUSHED_BY_EVAL`フラグをたてません。

```c
#vm_eval.c
/* block eval under the class/module context */

static VALUE
yield_under(VALUE under, VALUE self, VALUE values)
{
    rb_thread_t *th = GET_THREAD();
    rb_block_t block, *blockptr;
    NODE *cref;

    if ((blockptr = VM_CF_BLOCK_PTR(th->cfp)) != 0) {
	block = *blockptr;
	block.self = self;
	VM_CF_LEP(th->cfp)[0] = VM_ENVVAL_BLOCK_PTR(&block);
    }
    cref = vm_cref_push(th, under, NOEX_PUBLIC, blockptr);
    cref->flags |= NODE_FL_CREF_PUSHED_BY_EVAL;

    if (values == Qundef) {
	return vm_yield_with_cref(th, 1, &self, cref);
    }
    else {
	return vm_yield_with_cref(th, RARRAY_LENINT(values), RARRAY_CONST_PTR(values), cref);
    }
}

/* string eval under the class/module context */
static VALUE
eval_under(VALUE under, VALUE self, VALUE src, VALUE file, int line)
{
    NODE *cref = vm_cref_push(GET_THREAD(), under, NOEX_PUBLIC, NULL);

    if (SPECIAL_CONST_P(self) && !NIL_P(under)) {
	cref->flags |= NODE_FL_CREF_PUSHED_BY_EVAL;
    }
    SafeStringValue(src);

    return eval_string_with_cref(self, src, Qnil, cref, file, line);
}
```

メソッド定義用のコンテキストと定数定義用のコンテキストを同じcrefで管理しながら、`NODE_FL_CREF_PUSHED_BY_EVAL`フラグで調整しているのでしょうか。

あ、`_eval`ファミリの[修正PR](https://github.com/rails/rails/pull/18671)がマージされました:D