I'm trying to add an instance method foo
to Ruby's Array
class
so when it's invoked, the array's string elements are changed to string "foo".
This can be done easily by monkey patching Ruby's String
and Array
classes.
class String
def foo
replace "foo"
end
end
class Array
def foo
self.each {|x| x.foo if x.respond_to? :foo }
end
end
a = ['a', 1, 'b']
a.foo
puts a.join(", ") # you get 'foo, 1, foo' as expected
Now I'm trying to rewrite the above using Ruby 2's refinements feature. I'm using Ruby version 2.2.2.
The following works (in a file, eg. ruby test.rb, but not in irb for some reason)
module M
refine String do
def foo
replace "foo"
end
end
end
using M
s = ''
s.foo
puts s # you get 'foo'
However, I can't get it to work when adding foo
onto the Array
class.
module M
refine String do
def foo
replace "foo"
end
end
end
using M
module N
refine Array do
def foo
self.each {|x| x.foo if x.respond_to? :foo }
end
end
end
using N
a = ['a', 1, 'b']
a.foo
puts a.join(", ") # you get 'a, 1, b', not 'foo, 1, foo' as expected
There're two issues:
respond_to?
does not work even when you can invoke
the method on an object. Try adding puts 'yes' if s.respond_to? :foo
as the last line in the second code snippet, you'll see 'yes' is not printed.if x.respond_to? :foo
from
the Array#foo, you'll get the error undefined method 'foo' for "a":String (NoMethodError)
. So the question is: how do you make the String#foo refinement visible inside the Array#foo refinement?How do I overcome these two issues so I can get this to work?
(Please don't offer alternative solutions that don't involve refinement, because this is a theoretical exercise so I can learn how to use refinement).
Thank you.
The respond_to?
method does not work and this is documented
here.
The problem is that you can only activate a refinement at top-level and they are lexical in scope.
One solution would be:
module N
refine String do
def foo
replace 'foobar'
end
end
refine Array do
def foo
self.each do |x|
x.foo rescue x
end
end
end
end
using N
a = ['a', 1, 'b']
p a.foo
puts a.join(", ") # foo, 1, foo