简体   繁体   English

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

[英]Ruby/Rails: Prepend, append code to all methods

I wrote a small benchmarking Class for testing my code doing development. 我写了一个小的基准测试类来测试我的代码进行开发。 At the moment I have to add the Class to the beginning and end of every method. 目前我必须将Class添加到每个方法的开头和结尾。 Is it posible to prepend, append on the fly, so that I don't have to clutter my code? 是否可以预先添加,动态附加,以便我不必弄乱我的代码?

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

This is the Perf class. 这是Perf类。 It is located in the services folder. 它位于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 => @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

In general computing terms what you want to do is called code instrumentation . 在一般的计算术语中,您想要做的是称为代码检测 There are several ways to accomplish this, however here's one (crude) example using some metaprogramming: 有几种方法可以实现这一点,但是这是使用一些元编程的一个(粗略)示例:

First define a new method that we will use for injecting our instrumentation code: 首先定义一个新方法,我们将用它来注入我们的检测代码:

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

How to use it: 如何使用它:

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

Then: 然后:

p = Product.new
p.foo
p.bar

Will output: 将输出:

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

Here are some other ways to instrument ruby code and measure performance: 以下是一些其他方法来检测ruby代码并测量性能:

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

There is better solution. 有更好的解决方案。

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)

And the result: 结果如下:

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

The source & explanation are here: http://pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming . 来源和解释如下: http//pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming I recommend to spend not a big money to get this course. 我建议不花很多钱来学习这门课程。

I'm the author of aspector gem. 我是aspector gem的作者。 Thanks to dimuch for mentioning it. 感谢dimuch提到它。

I've come up with a solution using aspector. 我想出了一个使用aspector的解决方案。 Here are the high level steps: 以下是高级步骤:

  1. Create an aspect as a subclass of Aspector::Base 创建一个方面作为Aspector :: Base的子类
  2. Inside the aspect, define advices (before/after/around are the primary types of advices) 在方面内部,定义建议(前/后/周围是主要类型的建议)
  3. Apply the aspect on target class (or module/object) 在目标类(或模块/对象)上应用方面

The full code can be found in this gist . 完整的代码可以在这个要点中找到。 Please feel free to let me know if you have questions or the solution doesn't do what you intend to. 如果您有任何疑问或解决方案没有达到您的目的,请随时告诉我。

class PerfAspect < Aspector::Base
  around options[:action_methods] do |proxy|
    @perf ||= Perf.new
    proxy.call
    @perf.results
  end

  around options[:other_methods], :method_arg => true do |method, proxy, *args, &block|
    @perf.log(method)
    result = proxy.call *args, &block
    @perf.write!
    result
  end
end

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

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

Guess aspector gem can help. 猜猜aspector gem可以提供帮助。 It's not well documented but has useful examples. 它没有很好的文档记录,但有一些有用的例子。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM