💎️

refinements の使い方

に公開

恐くて自分からは使ってなかったけど、他のライブラリを使う上で挙動を把握していないと、そのライブラリが使えないという状況になったので学ぶ。

基本形

class Foo; end

module M
  refine Foo do
    def xxx
      "OK"
    end
  end
end

using M                         # using Foo ではない
Foo.new.xxx  # => "OK"

うろ覚えで書いていたら refine Foo に対応させて using Foo と書いてしまった。そうではなく refine を内包する外側のモジュールを指定する。

トップレベルで有効にしていればどこでも有効になる

using M

Foo.new.xxx      # => "OK"

module X
  Foo.new.xxx    # => "OK"
end

module X
  module Y
    Foo.new.xxx  # => "OK"
  end
end

それはそうだろう。

親で有効にしていれば子でも有効になる

module X
  using M

  Foo.new.xxx          # => "OK"

  module Y
    Foo.new.xxx        # => "OK"
  end
end

Foo.new.xxx rescue $!  # => #<NoMethodError: undefined method 'xxx' for an instance of Foo>

ということは、とにかく親スコープで有効であれば子スコープでも有効ってことか?

モジュールを再度開くと無効になっている

module X
  using M
end

module X
  # なんでだ?
  Foo.new.xxx rescue $!    # => #<NoMethodError: undefined method 'xxx' for an instance of Foo>
end

# 当然 X で使えないなら Y でも使えないだろう
module X
  module Y
    Foo.new.xxx rescue $!  # => #<NoMethodError: undefined method 'xxx' for an instance of Foo>
  end
end

これは意外だったがなるべく適用範囲を局所的したいようだ。

トップレベルで using を使ったときと挙動が違うので、「とにかく親スコープで有効になっていれば子スコープでも有効」だと考えてしまうと躓いてしまう。

ファイルを跨ぐと無効になっている

a.rb
using M
Foo.new.xxx  # => "OK"
b.rb
require "./a"
Foo.new.xxx rescue $!  # => #<NoMethodError: undefined method 'xxx' for an instance of Foo>

この仕様もモジュールの考えと似ている。したがって物理的に見える範囲の直近で using してないと効かないぐらいの認識でいよう。

Discussion