特異メソッドを生やすときにスコープを引き継ぎたい

メタプログラミングが強力な Ruby では、特異メソッドというオブジェクト固有のメソッドをその場で定義することが出来ます。

```rb
hoge = "hoge"
def hoge.piyo
  "piyo"
end
hoge.piyo # => "piyo"
```

`def hoge.piyo` の内側はインスタンスのスコープのため、定義時のローカル変数やメソッドは参照できません。

```rb
hoge = "hoge"
message = "piyo"
def hoge.piyo
  message
end
hoge.piyo # => NameError: undefined local variable or method `message' for "fuga":String
```

なんとかしてローカル変数やメソッドを参照したいです。

# ローカル変数を参照する

[Object#define_singleton_method](http://apidock.com/ruby/Object/define_singleton_method) を使うことで、定義時のローカル変数を特異メソッドから参照することができます。

```rb
hoge = "hoge"
message = "piyo"
hoge.define_singleton_method(:piyo) do
  message
end
hoge.piyo # => "piyo"
```

Ruby のブロックはクロージャです。`define_singleton_method` に渡したブロック内では、定義時のスコープが引き継がれており、`message` を参照できます。

# メソッドを参照する

メソッドを参照する場合、同じように `define_singleton_method` を使うだけではうまくいきません。

```rb
class Piyo
  def message
    "piyo"
  end

  def def_piyo(obj)
    obj.define_singleton_method(:piyo) do
      message
    end
  end
end

hoge = "hoge"
Piyo.new.def_piyo(hoge)
hoge.piyo # => NameError: undefined local variable or method `message' for "hoge":String
```

`define_singleton_method` に渡したブロックは、**`self` が `define_singleton_method` のレシーバすなわち `hoge` の状態で実行されます**。
そのため、`message` は `hoge.message` と解釈され、エラーとなります。

解決方法はいろいろあります。

## a. self を退避する

```rb
class Piyo
  def message
    "piyo"
  end

  def def_piyo(obj)
    self_orig = self
    obj.define_singleton_method(:piyo) do
      self_orig.message
    end
  end
end

hoge = "hoge"
Piyo.new.def_piyo(hoge)
hoge.piyo # => "piyo"
```

self を適当な変数に避けておけば、定義時の self を参照できます。

## b. Method オブジェクトに固める

```rb
class Piyo
  def message
    "piyo"
  end

  def def_piyo(obj)
    message_method = method(:message)
    obj.define_singleton_method(:piyo) do
      message_method.call
    end
  end
end

hoge = "hoge"
Piyo.new.def_piyo(hoge)
hoge.piyo # => "piyo"
```

Method オブジェクトはレシーバが `self` に依存しないので、ふつうに呼べます。

## c. 実行結果をローカル変数に入れておく

```rb
class Piyo
  def message
    "piyo"
  end

  def def_piyo(obj)
    message_value = message
    obj.define_singleton_method(:piyo) do
      message_value
    end
  end
end

hoge = "hoge"
Piyo.new.def_piyo(hoge)
hoge.piyo # => "piyo"
```

一番ラクな方法かもしれないが、`message` メソッドが `def_piyo` 実行時に呼ばれてしまうので、都合が悪い場合も多そう。

**ブロックが実行されるときに、`self` が置き換えられる** という考え方は直感的なので、これを理解していると ***_eval 系でもハマることが少なくなるかもしれません。

# (おまけ) 特異クラス

[Object#singleton_class](http://apidock.com/ruby/Object/singleton_class) でオブジェクトの特異クラスを取得できます。特異クラスは、オブジェクトの特異メソッドを保有するクラスです。

```rb
hoge = "hoge"
def hoge.piyo
  "piyo"
end
hoge.singleton_class # => #<Class:#<String:xxx>>
hoge.singleton_class.instance_methods - String.instance_methods # => [:piyo]
```

上例では、`hoge.singleton_class` がインスタンスメソッドとして piyo を持っていることが分かります。

オブジェクトの特異クラスで `def` や `define_method` することで、特異メソッドを生やせます。

```rb
hoge = "hoge"
message = "piyo"
hoge.singleton_class.class_eval do
  define_method(:piyo) do
    message
  end
end
hoge.piyo # => "piyo"
```

ただし、`def` だとスコープが変わるのでローカル変数は使えません。

```rb
hoge = "hoge"
message = "piyo"
hoge.singleton_class.class_eval do
  def piyo
    message
  end
end
hoge.piyo # => NameError: undefined local variable or method `message' for "hoge":String
```

クラス名はただの定数なので、`singleton_class` を定数に代入すれば `class` 定義文でもオープンできます。`class << object` 構文と同じ意味になります。

```rb
hoge = "hoge"
HogeSingletonClass = hoge.singleton_class
class HogeSingletonClass
  def piyo
    "piyo"
  end
end
hoge.piyo # => "piyo"
```

# 参考

- [A Closure Is Not Always A Closure In Ruby](http://www.skorks.com/2013/03/a-closure-is-not-always-a-closure-in-ruby/)
- [Metaprogramming in Ruby: It’s All About the Self](http://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self/)