There is a pretty good documentation of the current implementation of refinements in ruby here: http://ruby-doc.org//core-2.2.0/doc/syntax/refinements_rdoc.html, but there are some strange corner cases.
First, include module
is orthogonal to using module
(one include the instance method of module while the other activates the refinement). But there is a trick to include a refinement module itself, see
Better way to turn a ruby class into a module than using refinements?.
def to_module(klass)
Module.new do
#note that we return the refinement module itself here
return refine(klass) {
yield if block_given?
}
end
end
class Base
def foo
"foo"
end
end
class Receiver
include to_module(Base) {
def foo
"refined " + super
end
}
end
Receiver.new.foo #=> "refined foo"
Strangely this refinement module can't be used with using
!
m=to_module(Base) {}
m.class #=> Module
using m
#=>TypeError: wrong argument type Class (expected Module)
So using only work on the enclosing module of the refinement modules.
Secondly I wanted to use the above yield trick to be able to pass a Proc to refine (even through it only accepts a block), without resorting to converting the Proc back to source as in
https://www.new-bamboo.co.uk/blog/2014/02/05/refinements-under-the-knife/.
But using yield
as in the include example does not work:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
using ref_module1(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver1.new.bar #=> NoMethodError: super: no superclass method `foo'
We see that Receiver1 still use Bar#foo and not the refined method.
Howewer we can use module_eval
instead:
def ref_module2(klass,&b)
Module.new do
refine(klass) {
module_eval(&b)
}
end
end
class Receiver2
using ref_module2(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver2.new.bar #=> "refined foo"
I don't quite understand why module_eval
works here and not the yield
method. Inside the refinement block, the 'default_definee' is the refinement module, so module_eval
which puts the 'default_definee' to self
='the refinement module' should not affect it. And indeed in the 'include' example at the beginning, I get the same result when I use module_eval
or a direct yield
.
Can anyone explain this behavior?