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的解决方案。 以下是高级步骤:
- 创建一个方面作为Aspector :: Base的子类
- 在方面内,定义建议(之前/之后/周围是主要类型的建议)
- 在目标类(或模块/对象)上应用方面
完整的代码可以在这个要点中找到。 如果您有任何疑问或解决方案没有达到您的目的,请随时告诉我。
class PerfAspecttrue 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可以提供帮助。 它没有很好的文档记录,但有一些有用的例子。