Как обернуть вызов метода Ruby, добавив модуль? - PullRequest
5 голосов
/ 18 ноября 2010

Я хочу получать уведомления, когда определенные вещи происходят в некоторых из моих классов. Я хочу настроить это таким образом, чтобы реализация моих методов в этих классах не изменилась.

Я думал, что у меня будет что-то вроде следующего модуля:

module Notifications
  extend ActiveSupport::Concern

  module ClassMethods
    def notify_when(method)
      puts "the #{method} method was called!"
      # additional suitable notification code
      # now, run the method indicated by the `method` argument
    end
  end
end

Тогда я могу смешать это в мои классы так:

class Foo
  include Notifications

  # notify that we're running :bar, then run bar
  notify_when :bar

  def bar(...)  # bar may have any arbitrary signature
    # ...
  end
end

Мое ключевое желание состоит в том, чтобы мне не нужно было изменять :bar, чтобы уведомления работали правильно. Можно ли это сделать? Если так, как бы я написал реализацию notify_when?

Кроме того, я использую Rails 3, поэтому, если есть ActiveSupport или другие методы, которые я могу использовать, пожалуйста, не стесняйтесь поделиться. (Я посмотрел на ActiveSupport :: Уведомления , но это потребовало бы от меня изменения метода bar.)


До меня дошло, что я могу использовать «Модуль + супер трюк». Я не уверен, что это - возможно, кто-то может просветить меня?

Ответы [ 3 ]

15 голосов
/ 28 мая 2014

Прошло довольно много времени с тех пор, как этот вопрос был активен, но есть еще одна возможность обернуть методы включенным (или расширенным) модулем.

Начиная с версии 2.0 вы можете предварять модуль, фактически превращая его в прокси для класса предоплаты.

В приведенном ниже примере вызывается метод модуля расширенного модуля, передавая имена методов, которые вы хотите обернуть. Для каждого из имен методов создается и добавляется новый модуль. Это для простоты кода. Вы также можете добавить несколько методов к одному прокси.

Важным отличием решений, использующих alias_method и instance_method, которое позже будет связано с self, является то, что вы можете определить методы, которые нужно обернуть, до определения самих методов.

module Prepender

  def wrap_me(*method_names)
    method_names.each do |m|
      proxy = Module.new do
        define_method(m) do |*args|
          puts "the method '#{m}' is about to be called"
          super *args
        end
      end
      self.prepend proxy
    end
  end
end

Использование:

class Dogbert
  extend Prepender

  wrap_me :bark, :deny

  def bark
    puts 'Bah!'
  end

  def deny
    puts 'You have no proof!'
  end
end

Dogbert.new.deny

# => the method 'deny' is about to be called
# => You have no proof!
8 голосов
/ 18 ноября 2010

Я полагаю, вы могли бы использовать цепочку псевдонимов методов.

Примерно так:

def notify_when(method)  
  alias_method "#{method}_without_notification", method
  define_method method do |*args|
    puts "#{method} called"
    send "#{method}_without_notification", args
  end
end

При таком подходе вам не нужно самим изменять методы.

3 голосов
/ 19 ноября 2010

Я могу думать о двух подходах:

(1) Украсить методы Foo, чтобы включить уведомление.

(2) Использовать прокси-объект, который перехватывает вызовы метода для Foo и уведомляет вас, когда они происходят

Первое решение - это подход, использованный Jakub, хотя решение alias_method не лучший способ достичь этого, используйте вместо этого:

def notify_when(meth)  
  orig_meth = instance_method(meth)
  define_method(meth) do |*args, &block|
    puts "#{meth} called"
    orig_meth.bind(self).call *args, &block
  end
end

Второе решение требует, чтобы вы использовали method_missing в сочетании с прокси:

class Interceptor
  def initialize(target)
    @target = target
  end

  def method_missing(name, *args, &block)
    if @target.respond_to?(name)
      puts "about to run #{name}"
      @target.send(name, *args, &block)
    else
      super
    end
  end
end

class Hello; def hello; puts "hello!"; end; end

i = Interceptor.new(Hello.new)
i.hello #=> "about to run hello"
        #=> "hello!"

Первый метод требует изменения методов (то, что вы сказали, что вы не хотели), а второй метод требует использованияпрокси, может быть, вы чего-то не хотите.Простого решения нет, прости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...