如何通过包含模块来包装调用Ruby方法?
我想在某些课程发生某些事情时收到通知。我想以这样的方式设置它,以便在这些类中实现我的方法不会改变。如何通过包含模块来包装调用Ruby方法?
我想我有类似如下模块:
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
以使通知正常运行。这可以做到吗?如果是这样,我将如何编写notify_when
实现?
此外,我使用Rails 3,所以如果有ActiveSupport或其他技术我可以使用,请随时分享。 (我看着ActiveSupport::Notifications,但这需要我来修改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_method
解决方案是不是实现这一目标的最佳途径的代理对象,用这个来代替:
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
第二种方法则需要您结合使用method_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开始,你可以prepend一个模块,有效地使它成为预先分类的代理。
在下面的示例中,调用扩展模块模块的方法,传递要包装的方法的名称。对于每个方法名称,都会创建并预先创建一个新模块。这是为了简化代码。您还可以将多个方法附加到单个代理。
与使用alias_method
和instance_method
(稍后绑定在self
上)解决方案的一个重要区别是您可以在定义方法本身之前定义要包装的方法。
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
用途:
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!
如果:方法接受一个块? – 2010-11-18 20:47:02
您不必使用'define_method',您可以通过'eval'来定义方法。例如。 ''eval“def#{method}(* args,&block)...' – 2010-11-18 20:49:40
这要求'notify_when'出现在方法定义之后,所以它不会在你的代码示例中工作,并且它修改了'bar '方法,但是除了使用代理对象外,你还可以修改(装饰)你的方法。 – horseyguy 2010-11-18 21:05:39