【问题标题】:Executing code for every method call in a Ruby module为 Ruby 模块中的每个方法调用执行代码
【发布时间】:2011-07-27 16:19:14
【问题描述】:

我正在用 Ruby 1.9.2 编写一个模块,它定义了几种方法。当调用这些方法中的任何一个时,我希望它们中的每一个都首先执行某个语句。

module MyModule
  def go_forth
    a re-used statement
    # code particular to this method follows ...
  end

  def and_multiply
    a re-used statement
    # then something completely different ...
  end
end

但我想避免将 a re-used statement 代码明确地放在每个方法中。有办法吗?

(如果重要,a re-used statement 将拥有每个方法,在调用时打印自己的名称。它将通过puts __method__ 的某些变体来实现。)

【问题讨论】:

  • 这个问题与 Ruby 1.9.2 有关。但是这些天来,如果您刚刚发现这个问题,那么您可能正在使用 Ruby 2+。在 Ruby 2+ 中,prepend 是一个不错的选择。参见,例如,stackoverflow.com/questions/4219277/…

标签: ruby methods module metaprogramming


【解决方案1】:

像这样:

module M
  def self.before(*names)
    names.each do |name|
      m = instance_method(name)
      define_method(name) do |*args, &block|  
        yield
        m.bind(self).(*args, &block)
      end
    end
  end
end

module M
  def hello
    puts "yo"
  end

  def bye
    puts "bum"
  end

  before(*instance_methods) { puts "start" }
end

class C
  include M
end

C.new.bye #=> "start" "bum"
C.new.hello #=> "start" "yo"

【讨论】:

  • +1 喜欢。但是 Ruby 1.8.7 不支持? NoMethodError: undefined method before' 代表 M:Module`
  • @fl00r,要让它在 1.8.7 中工作,您只需要更改 proc 调用语法,我使用的是 .()(仅限 1.9)而不是 .call()
  • 嗨,你能解释一下 m.bind(self).(*args, &block) 到底是做什么的吗?我已经从谷歌搜索了 ruby​​ 文档和许多页面,但我仍然不知道它是如何工作的。非常感谢您的帮助。
  • @reizals 见ruby-doc.org/core-2.1.2/UnboundMethod.html#method-i-bind。 (回复仅供大家参考。)
  • 等等,如果你在 C 类定义中使用了 before 方法,为什么这不起作用?如果您将 before(*instance_methods) { puts "start " } 移至 C 类,我将得到 <class:C>': undefined method before' for C:Class (NoMethodError)`
【解决方案2】:

这正是创建 aspector 的目的。

使用 aspector,您无需编写样板元编程代码。您甚至可以更进一步,将通用逻辑提取到单独的方面类中并独立测试。

require 'aspector'

module MyModule
  aspector do
    before :go_forth, :add_multiply do
      ...
    end
  end

  def go_forth
    # code particular to this method follows ...
  end

  def and_multiply
    # then something completely different ...
  end
end

【讨论】:

    【解决方案3】:

    您可以通过代理模块使用method_missing 来实现它,如下所示:

    module MyModule
    
      module MyRealModule
        def self.go_forth
          puts "it works!"
          # code particular to this method follows ...
        end
    
        def self.and_multiply
          puts "it works!"
          # then something completely different ...
        end
      end
    
      def self.method_missing(m, *args, &block)
        reused_statement
        if MyModule::MyRealModule.methods.include?( m.to_s )
          MyModule::MyRealModule.send(m)
        else
          super
        end
      end
    
      def self.reused_statement
        puts "reused statement"
      end
    end
    
    MyModule.go_forth
    #=> it works!
    MyModule.stop_forth
    #=> NoMethodError...
    

    【讨论】:

      【解决方案4】:

      你可以通过元编程技术做到这一点,这里有一个例子:

      module YourModule
        def included(mod)
          def mod.method_added(name)
            return if @added 
            @added = true
            original_method = "original #{name}"
            alias_method original_method, name
            define_method(name) do |*args|
              reused_statement
              result = send original_method, *args
              puts "The method #{name} called!"
              result
            end
            @added = false
          end
        end
      
        def reused_statement
        end
      end
      
      module MyModule
        include YourModule
      
        def go_forth
        end
      
        def and_multiply
        end
      end
      

      仅适用于 ruby​​ 1.9 及更高版本

      更新:也不能使用块,即实例方法中没有产量

      【讨论】:

        【解决方案5】:

        我不知道,为什么我被否决了 - 但一个合适的 AOP 框架比元编程黑客要好。这就是 OP 想要实现的目标。

        http://debasishg.blogspot.com/2006/06/does-ruby-need-aop.html

        另一种解决方案可能是:

        module Aop
          def self.included(base)
            base.extend(ClassMethods)
          end
        
          module ClassMethods
            def before_filter(method_name, options = {})
              aop_methods = Array(options[:only]).compact
              return if aop_methods.empty?
              aop_methods.each do |m|
                alias_method "#{m}_old", m
                class_eval <<-RUBY,__FILE__,__LINE__ + 1
                  def #{m}
                    #{method_name}
                    #{m}_old
                  end
                RUBY
              end
            end
          end
        end
        
        module Bar
          def hello
            puts "Running hello world"
          end
        end
        
        class Foo
          include Bar
          def find_hello
            puts "Running find hello"
          end
          include Aop
          before_filter :find_hello, :only => :hello
        end
        
        a = Foo.new()
        a.hello()
        

        【讨论】:

          【解决方案6】:

          元编程是可能的。

          另一种选择是Aquarium。 Aquarium 是一个为 Ruby 实现面向切面编程 (AOP) 的框架。 AOP 允许您跨普通对象和方法边界实现功能。您的用例,对每个方法应用预操作,是 AOP 的一项基本任务。

          【讨论】:

          • 我也不知道为什么这被否决了。也许是因为没有示例,只有一个链接。
          • 对随机库的链接投反对票,但没有解释为什么我应该点击链接
          猜你喜欢
          • 2011-02-02
          • 2023-03-21
          • 1970-01-01
          • 1970-01-01
          • 2013-09-29
          • 2011-10-22
          • 2017-11-10
          • 1970-01-01
          • 2016-06-09
          相关资源
          最近更新 更多