首页 > 解决方案 > 获取类定义之上的所有实例方法

问题描述

我试图在调用实例方法之前之后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_actionorafter_create通常在类定义之上调用。我的目标是也调用wrap类定义(不单独列出所有方法)。在这种情况下,我不能instance_methods(false)在类定义之上调用,因为此时还没有定义任何方法。

谢谢你的帮助!

更新

感谢Kimmo Lehto 的方法,我可以使用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

标签: ruby

解决方案


您可以使用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可能是你最好的选择。


推荐阅读