blog.tkeo.info

rubyのrefinementsを使ってみたかった

今やってるプロジェクトでrefinementsを使うのが適していそうな場面が出てきたので初めて使ってみることにしたがなんだか微妙な感じになったという日記。

試したバージョンは2.3.3と2.4.0で、このあたりを参考にした。

同じmoduleをincludeしているクラスに対して共通のrefinementsを適用したかったが、2.3ではmoduleに対してrefineは使えないのでrefineブロック内でincludeしてみたが…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
module C
end
class A
  include C
end
class B
  include C
end

module Foo
  def foo
    "foo"
  end

  def foobar
    foo + "bar"
  end
end

module R
  refine A do
    include Foo
  end

  refine B do
    include Foo
  end
end

using R
puts A.new.foo    # => foo
puts B.new.foo    # => foo
puts A.new.foobar # => undefined local variable or method `foo' for #<A:0x007fc71782dcb0> (NameError)

どうもそう単純にはいかないらしい。ぐぬぬ。

いろいろこねくり回した結果、思った通りの動きをするコードは一応何通りか書けた。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
module C
end
class A
  include C
end
class B
  include C
end

# module_evalで無理やり突っ込むパターン
FooMethods = -> {
  def foo
    "foo"
  end

  def foobar
    foo + "bar"
  end
}

module R
  refine A do
    module_eval do
      FooMethods.call
    end
  end

  refine B do
    module_eval do
      FooMethods.call
    end
  end
end

# module_eval内でrefineしちゃうパターン
module R2
  %w(A B).each do |klass|
    module_eval <<-CODE
      refine #{klass} do
        def foo
          "foo"
        end

        def foobar
          foo + "bar"
        end
      end
    CODE
  end
end

# あきらめたパターン
module R3
  refine Object do
    def foo
      if kind_of?(C)
        "foo"
      else
        raise NoMethodError
      end
    end

    def foobar
      if kind_of?(C)
        foo + "bar"
      else
        raise NoMethodError
      end
    end
  end
end

using R
puts A.new.foo    # => foo
puts B.new.foo    # => foo
puts A.new.foobar # => foobar

うーん…どれもなんかやだな。 もうちょっとうまいやり方はないものか。