在Ruby类中覆盖module_function并访问原始函数

3

我试图覆盖一个Rails帮助方法,该方法定义如下:

class Foo
  module Bar

    def orig
      # orig code
    end

    alias o orig

    module_function :o
    module_function :orig

  end
end

这样我就可以重写并添加功能到origo,类似于这样:

def orig
  # new code
  # super (run orig code)
end
alias o orig

我已经查看了几种不同的猴子补丁方法,但它们似乎都不起作用。我相信module_function是问题所在。

有人知道我该如何实现吗?


我认为Rails标签没有任何理由存在,我会把"override"和"monkeypatching"去掉,只留下"ruby"。 - Cary Swoveland
2个回答

4

这里有一个解决方法。您可以重新打开模块,创建一个未绑定的对原始实例方法的引用,然后重新定义它以调用原始方法(带有某些更改的行为)。

首先,是原始定义:

module Foo
  def bar(arg)
    "self=#{self}, arg=#{arg}"
  end
  module_function :bar
end

接下来,重新打开并重新定义该方法:
module Foo
  OrigBarMethod = instance_method(:bar)
  def bar(arg)
    Foo::OrigBarMethod.bind(self).call(arg + 1)
  end
  module_function :bar
end

puts Foo.bar(1) # => "self=Foo, arg=2"

我使用bind(self)来保证原始方法仍然能够使用self,例如:

class MyClass
  include Foo
end

MyClass.new.send(:bar, 1) # => "self=#<MyClass:0x00007fb66a86cbf8>, arg=2"

我曾经是这个技巧的坚定支持者,因为与alias_method不同,它不会污染命名空间,但现在已经不再必要了。现在可以用简单的继承(prepend)完成所有可以用alias_method完成的事情。 - Jörg W Mittag
@JörgWMittag 好的,我必须承认我还没有养成使用prepend的习惯。但是,我看到它在这里更简单。谢谢。 - max pleaner
“prepend”是一种通用的强大继承机制。它可以包含猴子补丁、面向切面编程(前置、后置和环绕建议)、CLOS风格的方法组合器(同样是前置、后置和环绕组合器)。如果您滚动到我的答案的末尾,OP链接到了这个答案,我会给出一些增强Ruby继承的想法示例,这些想法在“prepend”被引入之前就已经提出,并展示如何使用“prepend”替换所有这些想法,通常甚至还具有额外的功能和灵活性。 - Jörg W Mittag

4
几乎任何以前需要使用猴子补丁的情况,现在都可以通过继承和Module#prepend来解决:
module Foo
  def bar(arg)
    "self=#{self}, arg=#{arg}"
  end
  module_function :bar
end

module FooExtension
  def bar(arg)
    super(arg + 1)
  end
end

[Foo, Foo.singleton_class].each do |mod|
  mod.prepend FooExtension
end

Foo.bar(1) #=> "self=Foo, arg=2"

class MyClass
  include Foo
end

MyClass.new.bar(1) #=> "self=#<MyClass:0x00007fb66a86cbf8>, arg=2"

1
不错,Jörg。与其修改Foo(可能在其他地方使用),最好在类定义中完成所有包含和前置操作:[self, singleton_class].each { |k| k.include Foo; k.prepend FooExtension }。Thomas:请注意,这并不依赖于语句module_function :bar。如果您不需要bar成为Foo的模块方法,则可以删除该语句。我同意Jörg,这是您应该采取的方法。 - Cary Swoveland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接