2012-02-21 85 views
5

我想写一点“弃用它”库,并使用“method_added”回调很多。 但现在我注意到,当包含一个模块时,这个回调没有被触发。红宝石method_added回调不触发,包括模块

有没有任何回调或变通方法,当somewhing被包含到自身中时,通知类“Foobar”?

小的演示证明:

# Including Moduls won't trigger method_added callback 

module InvisibleMethod 
    def invisible 
    "You won't get a callback from me" 
    end 
end 

class Foobar 
    def self.method_added(m) 
    puts "InstanceMethod: '#{m}' added to '#{self}'" 
    end 

    def visible 
    "You will get a callback from me" 
    end 

    include InvisibleMethod 
end 

[:invisible, :visible, :wont_exist].each do |meth| 
    puts "#{meth}: #{Foobar.public_method_defined? meth}" 
end 

这就是结果:

InstanceMethod: 'visible' added to 'Foobar' 
invisible: true 
visible: true 
wont_exist: false 

附加信息:

我真的需要使用一个钩状method_added。

ActiveModel在运行时通过匿名模块将public_instance_methods添加到类。

+1

不幸的是我不认为有一个很好的答案,但这可能会指向你的一些黑客工作的方向.. http:// stackoverflow。com/questions/4191214/callback-for-classes-defined-within-a-module – David 2012-02-21 23:45:53

回答

8

问题是,包含模块不会向类中添加方法 - 它只会更改方法调用链。这个链定义了哪些类/模块将被搜索到一个方法,这个方法并没有为相关类定义。包含模块时发生的情况是在该链中添加一个条目。

这与在超类中添加方法时完全相同 - 因为它未在超类中定义,所以不会调用method_added。如果一个子类可以改变超类的行为,那将是非常奇怪的。

你可以修复,通过手动调用添加包含的模块的方法,通过重新定义include为你的类:

class Foobar 
    def self.include(included_module) 
    included_module.instance_methods.each{|m| self.method_added(m)} 
    super 
    end 
end 

它比重新定义Moduleincluded方法更安全 - 改变只收窄到你定义你自己的类。

+0

默认情况下,method_added方法是私有的,所以如果你想做这个例子的逆过程并且在被包含的模块中做这些工作,你必须这样做:def self.included(base); base.send:method_added,:method_name;结束 – odigity 2015-11-08 22:08:11

3

正如其中一条评论所建议的那样,您可以使用其他挂钩来获得所需的行为。例如,试着在你的代码的开头补充一点:

class Module 
    def included(klass) 
    if klass.respond_to?(:method_added) 
     self.instance_methods.each do |method| 
     klass.method_added(method) 
     end 
    end 
    end 
end 

每当一个模块包含在一个类中,该模块的所有实例方法将被通知到类,只要它定义的方法method_added。通过上面的变化运行你的代码,我得到这个结果:

InstanceMethod: 'visible' added to 'Foobar' 
InstanceMethod: 'invisible' added to 'Foobar' 
invisible: true 
visible: true 
wont_exist: false 

我认为这是你想要的行为。

+0

哦,如果有人在覆盖'包含(klass)时没有调用'super',那么玩得开心' – Reactormonk 2012-02-29 22:18:59

1

我认为deprecation是不是大的需要一个库。它在datamapper这样执行。关于method_added钩子;它按预期工作,因为方法已添加到module而不是class。只有你可以得到你预期的结果猴子补丁included挂钩。

# got from https://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/deprecate.rb 
module Deprecate 
    def deprecate(old_method, new_method) 
    class_eval <<-RUBY, __FILE__, __LINE__ + 1 
     def #{old_method}(*args, &block) 
     warn "\#{self.class}##{old_method} is deprecated, use \#{self.class}##{new_method} instead (\#{caller.first})" 
     send(#{new_method.inspect}, *args, &block) 
     end 
    RUBY 
    end 
end # module Deprecate 

class MyClass 
    extend Deprecate 

    def old_method 
    p "I am old" 
    end 
    deprecate :old_method, :new_method 

    def new_method 
    p "I am new" 
    end 
end 

m = MyClass.new 
m.old_method 

# MyClass#old_method is deprecated, use MyClass#new_method instead (pinger.rb:27:in `<main>') 
# "I am new"