在类定义之上获取所有实例方法

时间:2018-10-22 15:08:34

标签: ruby

我正在尝试包装TestClass的所有实例方法,以在调用 之前和 之后执行代码。到目前为止,此代码有效:

module Wrapper
  def wrap(*methods)
    prependable_module = Module.new do
      methods.each do |m|
        define_method(m) do |*args, &block|
          p 1
          super(*args, &block)
          p 3
        end
      end
    end

    prepend prependable_module
  end
end

class TestClass
  extend Wrapper
  wrap :instance_method1

  def instance_method1
    p 2
  end
end

TestClass.new.instance_method1 # => 1, 2, 3

我可以使用所有方法名称作为参数来调用wrap。如果我尝试包装所有方法而不单独列出它们,则需要使用类定义底部的instance_methods(false)进行调用。

class TestClass
  extend Wrapper

  def instance_method1
    p 2
  end

  wrap(*instance_methods(false))
end

在Rails中,通常在类定义的顶部调用诸如before_actionafter_create之类的所有回调方法。 我的目标是也要在类定义的顶部调用wrap (不单独列出所有方法)。在这种情况下,我无法在类定义的顶部调用instance_methods(false),因为此时尚未定义任何方法。

感谢您的帮助!

更新

借助Kimmo Lehto's方法,我可以使用method_added钩子包装每个实例方法。我不想为每个定义的方法添加一个新模块,所以我将所有重写的方法添加到同一模块中。

module Wrapper
  def method_added(method_name)
    tmp_module = find_or_initialize_module
    return if tmp_module.instance_methods(false).include?(method_name)

    tmp_module.define_method(method_name) do |*args, &block|
      p 1
      super(*args, &block)
      p 3
    end
  end

  def find_or_initialize_module
    module_name  = "#{name}Wrapper"
    module_idx   = ancestors.map(&:to_s).index(module_name)

    unless module_idx
      prepend Object.const_set(module_name, Module.new)
      return find_or_initialize_module
    end

    ancestors[module_idx]
  end
end

class TestClass
  extend Wrapper

  def instance_method1
    p 2
  end
end

tc = TestClass.new
tc.instance_method1 # => 1, 2, 3

1 个答案:

答案 0 :(得分:1)

您可以使用Module#method_added挂钩自动包装添加的所有方法。

您将需要一些魔术来避免无限循环导致堆栈溢出。

另一种选择是一旦定义了类,就使用TracePoint来触发包装。您可以使用Module#extended来设置跟踪点。像这样:

module Finalizer
  def self.extended(obj)
    TracePoint.trace(:end) do |t|
      if obj == t.self
        obj.finalize
        t.disable
      end
    end
  end

  def finalize
    wrap(*instance_methods(false))
  end
end

除非明确地.freeze,否则类通常不是完全“关闭”的,因此这是一个有点棘手的解决方案,并且如果以后添加方法也不会触发。 method_added可能是您最好的选择。