我想写一点“Deprecate-It”lib并且经常使用“method_added”回调。 但现在我注意到,当包含模块时,不会触发此回调。
是否存在任何回调或变通方法,以便在将某些事物包括在内时通知“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添加到Class。
答案 0 :(得分:8)
问题是包含模块不会向类添加方法 - 它只会更改方法调用链。此链定义将搜索哪个类/模块的方法,该方法未针对相关类定义。包含模块时会发生什么是在该链中添加一个条目。
这与在超类中添加方法时完全相同 - 这不会调用method_added
,因为它未在超类中定义。如果子类可以改变超类的行为,那将是非常奇怪的。
您可以通过为您的班级重新定义include
手动调用为附带模块添加的方法来解决此问题:
class Foobar
def self.include(included_module)
included_module.instance_methods.each{|m| self.method_added(m)}
super
end
end
它比重新定义included
中的Module
方法更安全 - 更改仅限于您自己定义的类。
答案 1 :(得分: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
我认为这就是你想要的行为。
答案 2 :(得分: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"