为 Ruby 模块中的每个方法调用执行代码

发布于 2024-10-29 13:51:18 字数 456 浏览 2 评论 0原文

我正在 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

但我想避免在每个方法中显式放置重用语句代码。有办法这样做吗?

(如果重要的话,重用语句将使每个方法在调用时打印自己的名称。它将通过puts __method__的某种变体来实现。)

I'm writing a module in Ruby 1.9.2 that defines several methods. When any of these methods is called, I want each of them to execute a certain statement first.

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

But I want to avoid putting that a re-used statement code explicitly in every single method. Is there a way to do so?

(If it matters, a re-used statement will have each method, when called, print its own name. It will do so via some variant of puts __method__.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

奢望 2024-11-05 13:51:18

像这样:

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"

Like this:

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"
青衫儰鉨ミ守葔 2024-11-05 13:51:18

这正是创建 spector 的目的。

使用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

This is exactly what aspector is created for.

With aspector you don't need to write the boilerplate metaprogramming code. You can even go one step further to extract the common logic into a separate aspect class and test it independently.

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
丶情人眼里出诗心の 2024-11-05 13:51:18

您可以通过代理模块使用 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...

You can implement it with method_missing through proxy Module, like this:

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...
萌酱 2024-11-05 13:51:18

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

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 及更高版本

更新:并且也不能使用块,即实例方法中没有收益

You can do this by metaprogramming technique, here's an example:

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

works only in ruby 1.9 and higher

UPDATE: and also can't use block, i.e. no yield in instance methods

遗失的美好 2024-11-05 13:51:18

我不知道为什么我被否决了——但是一个合适的 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()

I dunno, why I was downvoted - but a proper AOP framework is better than meta-programming hackery. And thats what OP was trying to achieve.

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

Another Solution could be:

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()
多孤肩上扛 2024-11-05 13:51:18

通过元编程是可能的。

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

It is possible with meta-programming.

Another alternative is Aquarium. Aquarium is a framework that implements Aspect-Oriented Programming (AOP) for Ruby. AOP allow you to implement functionality across normal object and method boundaries. Your use case, applying a pre-action on every method, is a basic task of AOP.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文