💎️
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