Ruby method_added回调不包括模块

时间:2012-02-21 22:19:39

标签: ruby-on-rails ruby callback

我想写一点“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。

3 个答案:

答案 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"