Ruby - 从方法体发送的捕获方法调用

时间:2015-02-18 18:47:28

标签: ruby metaprogramming

我正在寻找方法来确保我的对象中定义的一组方法都调用特定的方法。为了说明,比方说我有对象A和B,两者都有这样的方法:

class A
  def method_a
     important_method!
  end
end

class B
  def method_b
     important_method!
  end
end

如何轻松确保来自A和method_a的{​​{1}}正在调用method_b

在这种情况下,important_method!将来自将包含在A和B中的模块(实际上在它们的公共超类中)。

到目前为止,我尝试将两个对象都包装在定义important_method的代理中并收集方法调用,但这只会告诉我method_missingmethod_a被调用。有任何想法吗?

2 个答案:

答案 0 :(得分:3)

我的回答最初是使用TracePoint,但后来我读了@ user2074840如何使用alias,这让我想到将aliascaller结合使用,导致一个非常直接的解决方案。我将下面的内容显示为#2 Use Caller

#1使用TracePoint

在Ruby 2.0+中,您可以使用TracePoint来获取所需的信息。 (在早期版本中,您可以使用Kernel#set_trace_point。)

要了解其工作原理,请提供一些示例代码:

def a
  puts "a"
  c
  important
end

def b
  puts "b"
  important
end

def c
  puts "c"
end

def important
  puts "important"
end

我们现在设置跟踪,指定两个感兴趣的事件:call:return,以及我们要保存的信息,事件(:call或{{1 }}和方法(:return:a:b):

:c

然后执行代码:

events = []
trace = TracePoint.trace(:call, :return) { |tp|
  events <<  { event: tp.event, method: tp.method_id } }

禁用跟踪:

4.times { send([:a, :b, :c][rand(0..2)]) }
  # b
  # important
  # b
  # important
  # a
  # c
  # important
  # c

并检查收集的信息:

trace.disable

请注意,这不能在p events # [{:event=>:call, :method=>:b}, # {:event=>:call, :method=>:important}, # {:event=>:return, :method=>:important}, # {:event=>:return, :method=>:b}, # {:event=>:call, :method=>:b}, # {:event=>:call, :method=>:important}, # {:event=>:return, :method=>:important}, # {:event=>:return, :method=>:b}, # {:event=>:call, :method=>:a}, # {:event=>:call, :method=>:c}, # {:event=>:return, :method=>:c}, # {:event=>:call, :method=>:important}, # {:event=>:return, :method=>:important}, # {:event=>:return, :method=>:a}, # {:event=>:call, :method=>:c}, # {:event=>:return, :method=>:c}] IRB中运行。

我们现在可以按如下方式提取对PRY的调用:

:important

#2使用来电者

此方法使用def calls_to_method(events, method) stack = [] events.each_with_object([]) do |h, calling_methods| stack << h while stack.size > 1 && stack[-1][:event] == :return && stack[-2][:event] == :call && stack[-1][:method] == stack[-2][:method] do if (stack.size > 2 && (stack[-1][:method] == method)) calling_methods << stack[-3][:method] end stack.pop stack.pop end end end calls = calls_to_method(events, :important) #=> [:b, :b, :a] calls.uniq #=> [:b, :a] Kernel#caller

alias

以下是@calling_methods = [] alias :old_important :important def important @calling_methods << caller.first[/`(.*)'/,1] old_important end 4.times { send([:a, :b, :c][rand(0..2)]) } # b # important # c # a # c # important # a # c # important # b # a # a p @calling_methods #=> ["b", "a", "a"] 返回的数组示例:

caller

它只是我们使用的第一个元素:

caller
  #=> ["abc.rb:9:in `b'",
  #    "abc.rb:32:in `block in <main>'",
  #    "abc.rb:32:in `times'",
  #    "abc.rb:32:in `<main>'"]

我们应用正则表达式来提取方法名称,前面是caller.first #=> "abc.rb:9:in `b'", &#39;`(Ascii 96),然后是单引号。

答案 1 :(得分:1)

我已经玩了一点。您对此类解决方案有何看法?

module CallImportantMethod

  def important_method!
    puts 'Important Method called!'
  end 

  def self.included(base)


    base.instance_methods(false).each do |method_name|

      base.class_eval do
        alias_method :"old_#{method_name}", method_name 
      end

      base.class_eval <<-eoruby
        def #{method_name}(*args, &block)
          important_method!
          old_#{method_name}(*args, &block)
        end
      eoruby

    end 
  end

end 

class SomeClass

  def testing 
    puts 'My Method was called'
  end 

  def testing_with_args(a,b)
    puts a + b
  end 

  def testing_with_block
    yield
  end 

  include CallImportantMethod

end 

i = SomeClass.new 
i.testing 
# => Important Method called!
# => My Method was called
i.testing_with_args(5,8)
# => Important Method called!
# => 13
i.testing_with_block { puts 'passing block...' } 
# => Important Method called!
# => passing block...