Trying to make sense of this 'refinements' business.
I'm making a module which refines a core class:
module StringPatch
refine String do
def foo
true
end
end
end
Then a class to use the refinement
class PatchedClass
end
PatchedClass.send :using, StringPatch
I get this error:
RuntimeError: Module#using is not permitted in methods
How can I make this work? I am trying to dynamically patch core classes in a certain scope only. I want to make the patches available in the class and instance scope.
As far as I know, the refinement is active until the end of the script when using
is in main, and until the end of the current Class/Module definition when using
is in a Class or Module.
module StringPatch
refine String do
def foo
true
end
end
end
class PatchedClass
using StringPatch
puts "test".foo
end
class PatchedClass
puts "test".foo #=> undefined method `foo' for "test":String (NoMethodError)
end
This would mean that if you manage to dynamically call using
on a Class or Module, its effect will be directly removed.
You cannot use refine
in methods, but you can define methods in a Class that has been refined :
class PatchedClass
using StringPatch
def foo
"test".foo #=> true
end
end
class PatchedClass
def bar
"test".foo
end
end
patched = PatchedClass.new
puts patched.foo #=> true
puts patched.bar #=> undefined method `foo' for "test":String (NoMethodError)
For your questions, this discussion could be interesting. It looks like refinements are restricted on purpose, but I don't know why :
Because refinement activation should be as static as possible.
Refinements are strictly scoped and can't be used dynamically as it stands. I get around this by structuring modules such that I can use them a refinement or as a monkey patch when needed. There are still limitations to this approach, and with refinements in general, but still handy.
module StringPatch
def foo
true
end
refine String do
include StringPatch
end
end
Used as a refinement
class PatchedClass
using StringPatch
end
Used as a single instance patch
obj = PatchedClass.new
obj.extend(StringPatch)
You can of course also extend the whole class as a monkey patch but then you pollute the class forever and for all eternity.
PatchedClass.prepend(StringPatch)
I appreciate the other answers but I think I have to add my own.
It seems there's very little leeway with using
- the error that it can't be used in methods is serious. The best wiggle room I found is to iteratively call it, passing a variable as argument.
So if I have two patch classes, I can make a constant Patches
, a list that includes both. Then in the class/module I want to load the patches, I can run the iterative using
.
module StringPatch
refine String do
def patch; :string_patch; end
end
end
module HashPatch
refine Hash do
def patch; :hash_patch; end
end
end
Patches = [StringPatch, HashPatch]
class C
Patches.each { |x| using x }
def self.test
[''.patch, {}.patch]
new.test
end
def test
[''.patch, {}.patch]
end
end
C.test
The using
does make the patches available in both instance and class scope.
The limitation is that there is no way to abstract away the using
call.
I can't say Patches.each &method(:using)
or move Patches.each { |x| using x }
to a method,
or use eval "Patches.each { |x| using x}"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With