Выполнение кода для каждого вызова метода в модуле Ruby - PullRequest
36 голосов
/ 01 апреля 2011

Я пишу модуль в 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__.)

Ответы [ 6 ]

64 голосов
/ 01 апреля 2011

Как это:

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"
11 голосов
/ 07 июля 2012

Это именно то, для чего создан 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
4 голосов
/ 01 апреля 2011

Вы можете реализовать это с помощью 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...
3 голосов
/ 01 апреля 2011

Я не знаю, почему я был отвергнут - но правильная структура AOP лучше, чем хакерство метапрограммирования.И это то, чего пытался достичь ОП.

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()
3 голосов
/ 01 апреля 2011

Вы можете сделать это с помощью техники метапрограммирования, вот пример:

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 и выше

UPDATE: и также не может использовать блок, то есть без выходав экземплярах методов

0 голосов
/ 01 апреля 2011

Это возможно с метапрограммированием.

Другой альтернативой является Аквариум .Aquarium - это фреймворк, реализующий Аспектно-ориентированное программирование (AOP) для Ruby.АОП позволяет реализовать функциональность через обычные границы объекта и метода.Ваш вариант использования, применяя предварительное действие для каждого метода, является основной задачей AOP.

...