使用respond_to进行Ruby细化问题?

时间:2015-08-12 14:18:15

标签: ruby monkeypatching ruby-2.1 ruby-2.2

我试图将实例方法foo添加到Ruby的Array类中 因此,当它被调用时,数组的字符串元素将更改为字符串" foo"。

这可以通过猴子修补Ruby的StringArray类来轻松完成。

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

现在我尝试使用Ruby 2' s refinements feature重写上述内容。 我使用的是Ruby 2.2.2版。

以下作品(在文件中,例如ruby test.rb,但由于某种原因不在irb中)

module M
  refine String do
    def foo
      replace "foo"
    end
  end
end

using M
s = ''
s.foo
puts s      # you get 'foo'

但是,在foo课程中添加Array时,我无法将其付诸实践。

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

有两个问题:

  1. 使用新方法优化类后,respond_to?即使可以调用也不起作用 对象上的方法。尝试添加puts 'yes' if s.respond_to? :foo 作为第二个代码段中的最后一行,您将看到“是”'是打印。
  2. 在我的Array细化中,String#foo超出了范围。如果您从中移除if x.respond_to? :foo 数组#foo,您将收到错误undefined method 'foo' for "a":String (NoMethodError)。所以问题是:如何在Array#foo细化中看到String#foo细化?
  3. 我如何克服这两个问题,以便我可以使用它?

    (请不要提供不涉及改进的替代解决方案,因为这是一个理论练习,所以我可以学习如何使用细化)。

    谢谢。

2 个答案:

答案 0 :(得分:1)

  1. respond_to?方法不起作用,记录在案 here

  2. 问题是您只能在顶级激活细化 它们在范围上是词汇。

  3. 一种解决方案是:

    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
    

答案 1 :(得分:0)

再次举起你的例子,一个简单的解决方案可能是覆盖respond_to?细化块中的方法:

module M
  refine String do
    def foo
      replace "foo"
    end
    def respond_to?(name,all=false)
      list_methods = self.methods.concat [:foo]
      list_methods.include? name
    end
  end

  refine Array do
    def foo
      self.each {|x| x.foo if x.respond_to? :foo }
    end
  end

end

using M

a = ['a', 1, 'b']
a.foo
puts a.join(", ")    # you get 'foo, 1, foo'