Ruby - 捕获从方法体发送的方法调用
Ruby - Capture method calls sent from method body
我正在寻找确保我的对象中定义的一组方法都调用特定方法的方法。为了说明,假设我有对象 A 和 B,它们都有这样的方法:
class A
def method_a
important_method!
end
end
class B
def method_b
important_method!
end
end
如何轻松确保来自 A 的 method_a
和 method_b
都在呼叫 important_method!
?
在这种情况下,important_method
将来自将包含在 A 和 B 中的模块(实际上是在它们的公共超类中)。
到目前为止,我尝试的是将两个对象包装在定义 method_missing
和收集方法调用的代理中,但这只告诉我调用了 method_a
和 method_b
。有任何想法吗?
我的答案最初是使用 TracePoint
,但后来我读到 @user2074840 如何使用 alias
,这让我想到将 alias
与 caller
结合使用, 这导致了一个非常直接的解决方案。我在下面将其表示为 #2 Use Caller
.
#1 使用 TracePoint
在 Ruby 2.0+ 中你可以使用 TracePoint to obtain the information you need. (In earlier versions, you may be able to use 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
或 :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
的调用,如下所示:
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]
#2 使用来电
此方法使用 alias
和 Kernel#caller:
@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'",
我们应用正则表达式提取方法名称,方法名称前面是 '
'` (Ascii 96),后面是单引号。
我玩过这个。您如何看待这样的解决方案?
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...
我正在寻找确保我的对象中定义的一组方法都调用特定方法的方法。为了说明,假设我有对象 A 和 B,它们都有这样的方法:
class A
def method_a
important_method!
end
end
class B
def method_b
important_method!
end
end
如何轻松确保来自 A 的 method_a
和 method_b
都在呼叫 important_method!
?
在这种情况下,important_method
将来自将包含在 A 和 B 中的模块(实际上是在它们的公共超类中)。
到目前为止,我尝试的是将两个对象包装在定义 method_missing
和收集方法调用的代理中,但这只告诉我调用了 method_a
和 method_b
。有任何想法吗?
我的答案最初是使用 TracePoint
,但后来我读到 @user2074840 如何使用 alias
,这让我想到将 alias
与 caller
结合使用, 这导致了一个非常直接的解决方案。我在下面将其表示为 #2 Use Caller
.
#1 使用 TracePoint
在 Ruby 2.0+ 中你可以使用 TracePoint to obtain the information you need. (In earlier versions, you may be able to use 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
或 :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
的调用,如下所示:
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]
#2 使用来电
此方法使用 alias
和 Kernel#caller:
@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'",
我们应用正则表达式提取方法名称,方法名称前面是 '
'` (Ascii 96),后面是单引号。
我玩过这个。您如何看待这样的解决方案?
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...