Refinement関係の小技とできない事をまとめてみた

Refinementを実用的に使うために、色々と調査してみて分かったことがいくつかあるのでまとめておく。

## できる事
### Module#using
まず、Ruby2.1から`Module#using`が使えるようになっている。
これはとても素晴らしい。この調子でもうちょっと自由度が上がっていってくれると最高。

### Refinementでクラスメソッドを定義する方法
これは駄目な例

```ruby
module BadClassMethodRefine
  refine String do
    def self.hoge
      p "hoge"
    end
  end
end

using BadClassMethodRefine

String.hoge # NoMethodError
```

こうすると定義できる

```ruby
module ClassMethodRefine
  refine String.singleton_class do
    def hoge
      p "hoge"
    end
  end
end

using ClassMethodRefine

String.hoge
```

`refine`ブロックの中で`p self`とすると分かるのだが、この中はクラスのコンテキストではなく、Refinement用の特殊なモジュールのスコープとして評価されている。
`p self`すると、`#<refinement:#<Class:String>@ClassMethodRefine>`って感じの出力が出てくる
なので、ここで`def self.hoge`とやっても意味が無い。
直接クラスの特異クラスを引っ張ってきて、特異クラスのインスタンスメソッドとして定義する必要がある。

### refineは後からでもできる

```ruby
module RefineModule
end

RefineModule.module_eval do
  refine Fixnum do
    def |(other)
      [self, other]
    end
  end
end

using RefineModule

1 | 2
```

refineについては制限がそんなに無くて、後から`module_eval`して定義しても大丈夫。
ただし、Module内に定義されるより前に`using`を実行してもメソッドは見つからない。


### refineにProcは渡せないが、module\_evalはできる
refineにProcを渡そうとすると怒られる。

```ruby
module ErrorRefineModule
  definition_proc = -> {
    def dynamic_def
      p "dynamic_def"
    end
  }

  refine(String, &definition_proc)
end
```

```
refine.rb:43:in `refine': can't pass a Proc as a block to Module#refine (ArgumentError)
        from refine.rb:43:in `<module:ErrorRefineModule>'
        from refine.rb:36:in `<main>'
```

しかし、refine内で`module_eval`はできる

```ruby
module DynamicRefineModule
  definition_proc = -> {
    def dynamic_def
      p "dynamic_def"
    end
  }

  refine String do
    module_eval(&definition_proc)
  end
end
```

`instance_eval`じゃないので注意。

これを利用して一つgemを作ってみた。

[joker1007/refining](https://github.com/joker1007/refining "joker1007/refining")

クラス定義の中でインラインモジュールとして自クラスに関するRefinementモジュールを定義する時、インデントを一つ減らすだけのgem。
意味あるかなあ……。まあとりあえず自分でこういう書き方を適度に試してみている。


## できない事

### メソッド内でusingを利用できない
```ruby
class Module
  def using_in_method(mod)
    using mod
  end
end

module Foo
  using_in_method Hoge
end
```

```
refine.rb:13:in `using': Module#using is not permitted in methods (RuntimeError)
        from refine.rb:13:in `using_in_method'
        from refine.rb:41:in `<module:ErrorRefineModule>'
        from refine.rb:40:in `<main>'
```

### module\_evalでusingできるが、そのブロックから外では効果が無いし、eval系もかなり制限がある
```ruby
module RefineModule
  refine String do
    def refined_method
      p "refined_method"
    end
  end
end

module Foo
end

Foo.module_eval do
  using RefineModule

  "foo".refined_method
end

Foo.module_eval do
  "foo".refined_method # こっちでは使えない
end
```

これはクラス内でusingして、別の場所でオープンクラスした時と同じ挙動。
つまりusingしたスコープ内でのみ効果がある。
他からもってきたprocを無理矢理`instance_eval`なり`module_eval`しても効果は無い。(残念だ)

ブロックではなくテキストとして引き渡すevalだったら外部から無理やり何か差し込むのは不可能じゃないが、実用性には乏しい。


## RSpecとの組み合わせ
RSpecの`describe`は実は`ExampleGroup`を親クラスとした無名クラスの定義と同じことをしている。
つまり、`describe`ブロックの中はクラス定義と同じなので、中で`Module#using`が使える。
`describe`のブロックごとに別々のクラスとして定義されているので、そのスコープから出るとちゃんとRefinementが外れてくれる。

これを利用して、組み込みクラスを局所的に書き換えてテストに活用できる。
私がコントリビュートしている[tomykaira/rspec-parameterized](https://github.com/tomykaira/rspec-parameterized "tomykaira/rspec-parameterized")というgemに試験的に実装してみた。

```ruby
describe "plus" do
  using RSpec::Parameterized::TableSyntax

  with_them do
    it "should do additions" do
      (a + b).should == answer
    end
  end

  where(:a, :b, :answer) do
    1 | 2 | 3  >
    5 | 8 | 13 >
    0 | 0 | 0
  end
end
```

Groovyっぽくパラメタライズドテストを書けるようにするシンタックスをRefinementで実現している。
(行末に苦肉の策感が残っているが……)

とりあえず全体としてはusingを自分でちゃんと指定できる用途なら、何かしらに使えそうな気がする。
例えば、Rails4.1の`concerning`を参考にして、インラインモジュールでrefineを定義しておいて特定のコンテキストの時だけusingして利用すれば、
明示的に利用が宣言されない限りは、他で一切利用できないメソッドが定義できる。
まあ、インラインモジュールを定義しておいて、`extend`するのと余り変わらない、という気もするのだが……。
現状のRefinementはRSpecと相性が良いので、テストしやすいというメリットぐらいはあるかもしれない。

後はやっぱりConcernモジュールや`concerning`の中で使うって方向かなあ。