如何通过包含模块来包装Ruby方法的调用?
我希望在我的某些课程中发生某些事情时得到通知。我想以这样一种方式设置它,即我的方法在这些类中的实现不会改变 我想我应该有如下模块:如何通过包含模块来包装Ruby方法的调用?,ruby,methods,module,Ruby,Methods,Module,我希望在我的某些课程中发生某些事情时得到通知。我想以这样一种方式设置它,即我的方法在这些类中的实现不会改变 我想我应该有如下模块: module Notifications extend ActiveSupport::Concern module ClassMethods def notify_when(method) puts "the #{method} method was called!" # additional suitable notifi
module Notifications
extend ActiveSupport::Concern
module ClassMethods
def notify_when(method)
puts "the #{method} method was called!"
# additional suitable notification code
# now, run the method indicated by the `method` argument
end
end
end
然后我可以像这样把它混合到我的课堂中:
class Foo
include Notifications
# notify that we're running :bar, then run bar
notify_when :bar
def bar(...) # bar may have any arbitrary signature
# ...
end
end
我的主要愿望是,我不想修改:bar
,以使通知正常工作。这能做到吗?如果是这样,我将如何编写实现时的通知_
另外,我使用的是Rails3,所以如果有ActiveSupport或其他技术我可以使用,请随意分享。(我查看了,但这需要我修改bar
方法。)
我注意到我可能想使用“模块+超级技巧”。我不确定这是什么——也许有人能启发我?我想你可以使用别名方法链
大概是这样的:
def notify_when(method)
alias_method "#{method}_without_notification", method
define_method method do |*args|
puts "#{method} called"
send "#{method}_without_notification", args
end
end
您不必自己用这种方法修改方法。我可以想到两种方法:
(1) 修饰Foo
方法以包含通知
(2) 使用代理对象拦截对Foo
的方法调用,并在调用发生时通知您
第一种解决方案是Jakub采取的方法,尽管alias_方法
不是实现这一点的最佳方法,但请改用此方法:
def notify_when(meth)
orig_meth = instance_method(meth)
define_method(meth) do |*args, &block|
puts "#{meth} called"
orig_meth.bind(self).call *args, &block
end
end
第二种解决方案要求您将方法\u missing
与代理结合使用:
class Interceptor
def initialize(target)
@target = target
end
def method_missing(name, *args, &block)
if @target.respond_to?(name)
puts "about to run #{name}"
@target.send(name, *args, &block)
else
super
end
end
end
class Hello; def hello; puts "hello!"; end; end
i = Interceptor.new(Hello.new)
i.hello #=> "about to run hello"
#=> "hello!"
第一种方法需要修改方法(你说你不想要的),第二种方法需要使用代理,可能是你不想要的。很抱歉,没有简单的解决方案。这个问题已经有很长一段时间没有解决了,但是还有一种可能是通过包含(或扩展)模块包装方法
由于2.0,您可以创建一个模块,有效地使其成为前置类的代理
在下面的示例中,调用扩展模块的方法,传递要包装的方法的名称。对于每个方法名称,将创建一个新模块并在其前面加上前缀。这是为了代码的简单性。您还可以将多个方法附加到单个代理
使用alias_方法
和instance_方法
的解决方案的一个重要区别是,您可以在定义方法本身之前定义要包装的方法
module Prepender
def wrap_me(*method_names)
method_names.each do |m|
proxy = Module.new do
define_method(m) do |*args|
puts "the method '#{m}' is about to be called"
super *args
end
end
self.prepend proxy
end
end
end
使用:
如果:方法需要一个块怎么办?您不必使用define\u method
,您可以执行eval
来定义方法。例如,eval“def#{method}(*args,&block).
这要求在方法定义之后出现notify_,因此它对代码示例不起作用。它也会修改bar
方法。但除了使用代理对象,还必须修改(装饰)您的方法。请您添加一个解释,为什么重新绑定原始方法比使用alias_方法更好?区别对我来说并不明显。谢谢。不过,我发现了。您的Prepender示例不必要地为每个包装的方法名称创建/预编一个模块;在sin上定义所有方法可能更有意义这是我在回答中所说的-第三段-代码简单性的次优示例。很抱歉,我没有仔细阅读解释。也就是说,我不确定为了“简单性”而保留代码原样是否有意义“。这不是很简单,只是很奇怪。你能提供没有多个模块的工作示例吗?这对我来说似乎不管用me@FilipBartuzi如果将proxy=Module.new
移动到method\u name之前,则应该可以工作。每个,将self.prepend proxy
移动到后面,并说proxy.define\u method
而不是仅仅define\u method
。那么您只需要制作一个代理模块。
class Dogbert
extend Prepender
wrap_me :bark, :deny
def bark
puts 'Bah!'
end
def deny
puts 'You have no proof!'
end
end
Dogbert.new.deny
# => the method 'deny' is about to be called
# => You have no proof!