Using refinements to patch a core Module such as Kernel
Modules can be refined as of ruby 2.4:
Module#refine
accepts a module as the argument now. [Feature #12534]
The old caveat ("Refinements only modify classes, not modules so the argument must be a class") no longer applies (although it wasn't removed from the documentation until ruby 2.6).
Example:
module ModuleRefinement
refine Enumerable do
def tally(&block)
block ||= ->(value) { value }
counter = Hash.new(0)
each { |value| counter[block[value]] += 1 }
counter
end
end
end
using ModuleRefinement
p 'banana'.chars.tally # => {"b"=>1, "a"=>3, "n"=>2}
Ruby refinements subtleties
Context (or binding) is the reason why module_eval works and yield doesn't in your last set of examples. It actually has nothing to do with refinements, as demonstrated below.
Starting with module_eval
:
class Foo
def run(&block)
self.class.module_eval(&block)
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts foo.hello # => "hello"
puts hello => # '<main>': undefined method 'hello' for main:Object (NameError)
In Foo#run
we call module_eval
on Foo
. This switches the context (self
) to be Foo
. The result is much like we had simple defined hello
inside of class Foo
originally.
Now let's take a look at yield
:
class Foo
def run
yield
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
yield
simply invokes the block in its original context, which in this example would be <main>
. When the block is invoked, the end result is exactly the same as if the method were defined at the top level normally:
class Foo
def run
yield
end
end
foo = Foo.new
def hello
"hello"
end
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
You might notice that foo
seems to have the hello
method in the yield
examples. This is a side effect of defining hello
as a method at the top level. It turns out that <main>
is just an instance of Object
, and defining top level methods is really just defining private methods on Object
which nearly everything else ends up inheriting. You can see this by opening up irb and running the following:
self # => main
self.class # => Object
def some_method
end
"string".method(:some_method) # => #<Method: String(Object)#some_method>
Now back to your examples.
Here's what happens in the yield
example:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
# like my yield example, this block is going to
# end up being invoked in its original context
using ref_module1(Base) {
def foo
"I'm defined on Receiver1"
end
}
def bar
# calling foo here will simply call the original
# Base#foo method
Base.new.foo
end
end
# as expected, if we call Receiver1#bar
# we get the original Base#foo method
Receiver1.new.bar # => "foo"
# since the block is executed in its original context
# the method gets defined in Receiver1 -- its original context
Receiver1.new.foo # => "I'm defined on Receiver1"
As for module_eval
, it works in your examples because it causes the block to be run in the context of the new module, rather than on the Receiver1
class.
How to refine module method in Ruby?
This piece of code will work:
module Math
def self.pi
puts 'original method'
end
end
module RefinementsInside
refine Math.singleton_class do
def pi
puts 'refined method'
end
end
end
module Main
using RefinementsInside
Math.pi #=> refined method
end
Math.pi #=> original method
Explanation:
Defining a module #method is equivalent to defining an instance method on its #singleton_class.
How to use refinements dynamically
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.
Add method to a class which can only be accessed inside specific class
You could use refinements for this:
Due to Ruby's open classes you can redefine or add functionality to existing classes. This is called a “monkey patch”. Unfortunately the scope of such changes is global. All users of the monkey-patched class see the same changes. This can cause unintended side-effects or breakage of programs.
Refinements are designed to reduce the impact of monkey patching on other users of the monkey-patched class. Refinements provide a way to extend a class locally. Refinements can modify both classes and modules.
Something like this:
module HashPatches
refine Hash do
def new_hash_method
# ...
end
end
end
and then:
class YourClass
using HashPatches
def m
{}.new_hash_method
end
end
That would let you call YourClass.new.m
(which would use new_hash_method
) but it wouldn't pollute Hash
globally so outside YourClass
, some_hash.new_hash_method
would be a NoMethodError
.
Reading:
- Official Refinements docs
- Refinements spec
Using Refinements Hierarchically
Wow, this was really interesting to play around with! Thanks for asking this question! I found a way that works!
module M
refine String do
alias :dbl_eql :==
def ==(other)
downcase.dbl_eql(other.downcase)
end
end
refine Array do
def ==(other)
zip(other).all? {|x, y| x == y}
end
end
end
a = [[1, "a"],
[2, "b"],
[3, "c"],
[4, "d"]]
b = [[1, "AA"],
[2, "B"],
[3, "C"],
[5, "D"]]
using M
a.zip(b).count { |ae,be| ae == be } # 2
Without redefining ==
in Array
, the refinement won't apply. Interestingly, it also doesn't work if you do it in two separate modules; this doesn't work, for instance:
module M
refine String do
alias :dbl_eql :==
def ==(other)
downcase.dbl_eql(other.downcase)
end
end
end
using M
module N
refine Array do
def ==(other)
zip(other).all? {|x, y| x == y}
end
end
end
a = [[1, "a"],
[2, "b"],
[3, "c"],
[4, "d"]]
b = [[1, "AA"],
[2, "B"],
[3, "C"],
[5, "D"]]
using N
a.zip(b).count { |ae,be| ae == be } # 0
I'm not familiar enough with the implementation details of refine
to be totally confident about why this behavior occurs. My guess is that the inside of a refine block is treated sort of as entering a different top-level scope, similarly to how refines defined outside of the current file only apply if the file they are defined in is parsed with require
in the current file. This would also explain why nested refines don't work; the interior refine goes out of scope the moment it exits. This would also explain why monkey-patching Array
as follows works:
class Array
using M
def ==(other)
zip(other).all? {|x, y| x == y}
end
end
This doesn't fall prey to the scoping issues that refine
creates, so the refine
on String
stays in scope.
How can super within a refinement call an overridden method?
Based on the documentation, the method lookup for a refinement's built in behaviour is the same as you have observed.
Your assumption was correct that it is not typical inheritance, that can be seen by invoking superclass
class C
def foo
puts "C#foo"
end
end
module M
refine C do
def foo
puts "C#foo in M"
puts "class: #{self.class}"
puts "superclass: #{self.class.superclass}"
super
end
end
end
using M
x = C.new
x.foo
The output:
C#foo in M
class: C
superclass: Object
C#foo
best way to organize a long piece of code in ruby refinement block
Here's a general pattern I ended up using. Basically I found no workaround for using global identifiers at some level. But this can be done fairly cleanly by making those globals classes/modules. This will be more clear as an example:
module StringPatches
def self.non_empty?(string)
!string.empty?
end
def non_empty?
StringPatches.non_empty?(self)
end
def non_non_empty?
!StringPatches.non_empty?(self)
end
refine String do
include StringPatches
end
end
class Foo
using StringPatches
puts "asd".non_empty? # => true
puts "asd".non_non_empty? # => false
end
The class methods on StringPatches
don't get exported to using
. But since classes/modules are constants (globals) they can be accessed from anywhere.
Related Topics
I Need to Generate Uuid for My Rails Application. What Are the Options(Gems) I Have
Binary String Literals in Ruby 2.0
Declaring Instance Variables Iterating Over a Hash!
How to Call Rake Tasks That Are Defined in the Standard Rakefile from an Other Ruby Script
Specifying a Layout and a Template in a Standalone (Not Rails) Ruby App, Using Slim or Haml
How to Validate Associated Model Id
Framework for Non-Web Ruby Project
How to Open a File and Search for a Word
Getting a Dns Txt Record in Ruby
Catching Line Numbers in Ruby Exceptions
Respond_To' VS. 'Respond_To_Missing'
How to Check from Ruby Whether a Process with a Certain Pid Is Running
How to Require a Specific Version of a Ruby Gem
How to Get the Width of Terminal Window in Ruby
How to Sort a Hash by Value in Descending Order and Output a Hash in Ruby