Ruby / Rails:Prepend,将代码附加到所有方法

我写了一个小的基准测试类来测试我的代码进行开发。 目前我必须将Class添加到每个方法的开头和结尾。 是否可以预先添加,动态附加,以便我不必弄乱我的代码?

class ApplicationController before_filter :init_perf after_filter :write_perf_results_to_log! def init_perf @perf ||= Perf.new end def write_perf_results_to_log! @perf.results end end class Products < ApplicationsController def foo @perf.log(__methond__.to_s) caculation = 5 *4 @perf.write! end def bar @perf.log(__methond__.to_s) caculation = 1 / 5 @perf.write! end end 

这是Perf类。 它位于services文件夹中。

 class Perf def initialize @results = [] end def log(note) @start = Time.now @note = note end def write! if @results.find {|h| h[:note] == @note } # Update :sec method exists in results @results.select { |h| h["note"] == @note; h[":sec"] = (Time.now - @start).round(3) } else # Add new Hash to results @results < @note, :sec => (Time.now - @start).round(3) } end end def results content = " PERFORMANCE STATISTICS! " @results.each do |r| content += r[:note] + " " + r[:sec].to_s + " " end content += " " Rails.logger.info content end end 

在一般计算术语中,您想要做的是称为代码检测 。 有几种方法可以实现这一点,但是这是使用一些元编程的一个(粗略)示例:

首先定义一个新方法,我们将用它来注入我们的检测代码:

 class ApplicationController def self.instrument_methods(*methods) methods.each { |m| # Rename original method self.send(:alias_method, "#{m}_orig", m) # Redefine old method with instrumentation code added define_method m do puts "Perf log #{m}" self.send "#{m}_orig" puts "Perf write" end } end end 

如何使用它:

 class Product < ApplicationController def foo puts "Foo" end def bar puts "Bar" end # This has to be called last, once the original methods are defined instrument_methods :foo, :bar end 

然后:

 p = Product.new p.foo p.bar 

将输出:

 Perf log foo Foo Perf write Perf log bar Bar Perf write 

以下是一些其他方法来检测ruby代码并测量性能:

http://ruby-prof.rubyforge.org/
http://www.igvita.com/2009/06/13/profiling-ruby-with-googles-perftools/

有更好的解决方案。

 class ApplicationController def self.inherited(klass) def klass.method_added(name) return if @_not_new @_not_new = true original = "original #{name}" alias_method original, name define_method(name) do |*args, &block| puts "==> called #{name} with args: #{args.inspect}" result = send original, *args, &block puts "<== result is #{result}" result end @_not_new = false end end end class Product < ApplicationController def meth(a1, a2) a1 + a2 end end product = Product.new puts product.meth(2,3) 

结果如下:

 ==> called meth with args: [2, 3] <== result is 5 5 

来源和解释如下: http : //pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming 。 我建议不花很多钱来学习这门课程。

我是aspector gem的作者。 感谢dimuch提到它。

我想出了一个使用aspector的解决方案。 以下是高级步骤:

  1. 创建一个方面作为Aspector :: Base的子类
  2. 在方面内,定义建议(之前/之后/周围是主要类型的建议)
  3. 在目标类(或模块/对象)上应用方面

完整的代码可以在这个要点中找到。 如果您有任何疑问或解决方案没有达到您的目的,请随时告诉我。

 class PerfAspect  true do | method,proxy,* args,&block |
     @ perf.log(方法)
     result = proxy.call * args,&block
     @ perf.write!
    结果
  结束
结束

 action_methods = [:action]
 other_methods = Products.instance_methods(false) -  action_methods

 PerfAspect.apply(Products,:action_methods => action_methods,:other_methods => other_methods)

猜猜aspector gem可以提供帮助。 它没有很好的文档记录,但有一些有用的例子。