[英]How to “magically” add code to all public class methods in ruby?
I would like to be able to insert some code at the beginning and at the end of methods in my class. 我希望能够在我的课程的方法的开头和结尾插入一些代码。 I would like to avoid repetition as well.
我也想避免重复。
I found this answer helpful, however it doesn't help with the repetition. 我发现这个答案很有帮助,但它对重复没有帮助。
class MyClass
def initialize
[:a, :b].each{ |method| add_code(method) }
end
def a
sleep 1
"returning from a"
end
def b
sleep 1
"returning from b"
end
private
def elapsed
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
block_value = yield
finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
puts "elapsed: #{finish - start} seconds, block_value: #{block_value}."
block_value
end
def add_code(meth)
meth = meth.to_sym
self.singleton_class.send(:alias_method, "old_#{meth}".to_sym, meth)
self.singleton_class.send(:define_method, meth) do
elapsed do
send("old_#{meth}".to_sym)
end
end
end
end
The above does work, but what would be a more elegant solution? 以上确实有效,但更优雅的解决方案是什么? I would love to be able to, for example, put
attr_add_code
at the beginning of the class definition and list the methods I want the code added to, or perhaps even specify that I want it added to all public methods. 我希望能够,例如,将
attr_add_code
放在类定义的开头,并列出我想要添加代码的方法,或者甚至指定我希望它添加到所有公共方法中。
Note: The self.singleton_class
is just a workaround since I am adding code during the initialisation. 注意:
self.singleton_class
只是一种解决方法,因为我在初始化期间添加代码。
If by repetition you mean the listing of methods you want to instrument, then you can do something like: 如果通过重复表示您想要检测的方法列表,那么您可以执行以下操作:
module Measure
def self.prepended(base)
method_names = base.instance_methods(false)
base.instance_eval do
method_names.each do |method_name|
alias_method "__#{method_name}_without_timing", method_name
define_method(method_name) do
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
public_send("__#{method_name}_without_timing")
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
puts "Method #{method_name} took #{t2 - t1}"
end
end
end
end
end
class Foo
def a
puts "a"
sleep(1)
end
def b
puts "b"
sleep(2)
end
end
Foo.prepend(Measure)
foo = Foo.new
foo.a
foo.b
# => a
# => Method a took 1.0052679998334497
# => b
# => Method b took 2.0026899999938905
Main change is that i use prepend
and inside the prepended
callback you can find the list of methods defined on the class with instance_methods(false)
, the false
parameter indicating that ancestors should not be considered. 主要的变化是我使用
prepend
并在prepended
回调中你可以找到在类上定义的方法列表,其中包含instance_methods(false)
, false
参数表示不应该考虑祖先。
Instead of using method aliasing, which in my opinion is something of the past since the introduction of Module#prepend
, we can prepend an anonymous module that has a method for each instance method of the class to be measured. 而不是使用方法别名,我认为这是自
Module#prepend
引入以来的过去,我们可以预先添加一个匿名模块,该模块有一个方法用于测量类的每个实例方法。 This will cause calling MyClass#a
to invoke the method in this anonymous module, which measures the time and simply resorts to super
to invoke the actual MyClass#a
implementation. 这将导致调用
MyClass#a
来调用此匿名模块中的方法,该模块测量时间并简单地调用super
来调用实际的MyClass#a
实现。
def measure(klass)
mod = Module.new do
klass.instance_methods(false).each do |method|
define_method(method) do |*args, &blk|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
value = super(*args, &blk)
finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
puts "elapsed: #{finish - start} seconds, value: #{value}."
value
end
end
end
klass.prepend(mod)
end
Alternatively, you can use class_eval
, which is also faster and allows you to just call super
without specifying any arguments to forward all arguments from the method call, which isn't possible with define_method
. 或者,您可以使用
class_eval
,它也更快,并且允许您只调用super
而不指定任何参数来转发方法调用中的所有参数,这对于define_method
是不可能的。
def measure(klass)
mod = Module.new do
klass.instance_methods(false).each do |method|
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{method}(*)
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
value = super
finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
puts "elapsed: \#{finish - start} seconds, value: \#{value}."
value
end
CODE
end
end
klass.prepend(mod)
end
To use this, simply do: 要使用它,只需:
measure(MyClass)
It looks like you're trying to do some benchmarking. 看起来你正在尝试做一些基准测试。 Have you checked out the benchmark library ?
你检查了基准库吗? It's in the standard library.
它在标准库中。
require 'benchmark'
puts Benchmark.measure { MyClass.new.a }
puts Benchmark.measure { MyClass.new.b }
Another possibility would be to create a wrapper class like so: 另一种可能性是创建一个这样的包装类:
class Measure < BasicObject
def initialize(target)
@target = target
end
def method_missing(name, *args)
t1 = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
target.public_send(name, *args)
t2 = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
::Kernel.puts "Method #{name} took #{t2 - t1}"
end
def respond_to_missing?(*args)
target.respond_to?(*args)
end
private
attr_reader :target
end
foo = Measure.new(Foo.new)
foo.a
foo.b
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.