Метод переопределения звонит в Ruby?

Я пытаюсь получить обратный вызов, когда любой метод на конкретном классе называют. Переопределение "отправляет", не работает. Это кажется, отправляют, не становится названным в нормальном вызове метода Ruby. Возьмите следующий пример.

class Test
  def self.items
   @items ||= []
  end
end

Если мы переопределяем, отправляют на Тесте, и затем называют Test.items, отправляют, не становится названным.

То, что я пытаюсь сделать возможный?

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

7
задан Alex MacCaw 3 February 2010 в 17:49
поделиться

8 ответов

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

(есть также rcapture http://code.google.com/p/rcapture/ ):

module Interceptor
  def intercept_callback(&block)
    @callback = block
    @old_methods = {}
  end
  def method_added(my_method)
    redefine self, self, my_method, instance_method(my_method)
  end
  def singleton_method_added(my_method)
    meta = class << self; self; end
    redefine self, meta, my_method, method(my_method)
  end
  def redefine(klass, me, method_name, my_method)
    return unless @old_methods and not @old_methods.include? method_name
    @old_methods[method_name] = my_method
    me.send :define_method, method_name do |*args|
      callback = klass.instance_variable_get :@callback
      orig_method = klass.instance_variable_get(:@old_methods)[method_name]
      callback.call *args if callback
      orig_method = orig_method.bind self if orig_method.is_a? UnboundMethod
      orig_method.call *args
    end
  end
end

class Test
  extend Interceptor
  intercept_callback do |*args|
    puts 'was called'
  end
  def self.items
    puts "items"
  end
  def apple
    puts "apples"
  end
end

class Test
  def rock
    puts "rock"
  end
end

Test.items
Test.new.apple
Test.new.rock
4
ответ дан 6 December 2019 в 09:19
поделиться

Используйте псевдоним или alias_method :

# the current implementation of Test, defined by someone else
# and for that reason we might not be able to change it directly
class Test
  def self.items
    @items ||= []
  end
end

# we open the class again, probably in a completely different
# file from the definition above
class Test
  # open up the metaclass, methods defined within this block become
  # class methods, just as if we had defined them with "def self.my_method"
  class << self
    # alias the old method as "old_items"
    alias_method :old_items, :items
    # redeclare the method -- this replaces the old items method,
    # but that's ok since it is still available under it's alias "old_items"
    def items
      # do whatever you want
      puts "items was called!"
      # then call the old implementation (make sure to call it last if you rely
      # on its return value)
      old_items
    end
  end
end

Я переписал ваш код, используя синтаксис class << self чтобы открыть метакласс, потому что я не уверен, как в противном случае использовать alias_method в методах класса.

12
ответ дан 6 December 2019 в 09:19
поделиться

Вы можете сделать что-то подобное, вы даже можете поставить условия на вызываемый метод или нет (я не думаю, что это полезно, но все же у вас это есть на всякий случай).

module MethodInterceptor

  def self.included(base)
    base.extend(ClassMethods)
    base.send(:include, InstanceMethods)
    base.class_eval do 
      # we declare the method_list on the class env
      @_instance_method_list = base.instance_methods.inject(Hash.new) do |methods, method_name|
        # we undef all methods
        if !%w(__send__ __id__ method_missing class).include?(method_name)
          methods[method_name.to_sym] = base.instance_method(method_name)
          base.send(:undef_method, method_name)
        end
        methods
      end
    end
  end

  module ClassMethods

    def _instance_method_list
      @_instance_method_list
    end

    def method_added(name)
      return if [:before_method, :method_missing].include?(name)
      _instance_method_list[name] = self.instance_method(name)
      self.send(:undef_method,  name)
      nil
    end

  end

  module InstanceMethods

    def before_method(method_name, *args)
      # by defaults it always will be called
      true
    end

    def method_missing(name, *args)
      if self.class._instance_method_list.key?(name)
        if before_method(name, *args) 
          self.class._instance_method_list[name].bind(self).call(*args)
        else
          super
        end
      else
        super
      end
    end
  end

end

class Say
  include MethodInterceptor

  def before_method(method_name, *args)
    # you cannot say hello world!
    return !(method_name == :say && args[0] == 'hello world')
  end

  def say(msg)
    puts msg
  end

end

Надеюсь, это сработает.

1
ответ дан 6 December 2019 в 09:19
поделиться

вы пытаетесь подключить метод экземпляра класса? Тогда следующий фрагмент может помочь. Он использует RCapture, который можно установить через

gem install rcapture

. Вводную статью можно найти по адресу здесь

require 'rcapture'

class Test 
  include RCapture::Interceptable
end

Test.capture_post :class_methods => :items do
  puts "items!"
end

Test.items 
#=> items!
1
ответ дан 6 December 2019 в 09:19
поделиться

Да, оба этих образца кода ведут себя одинаково путь в параллельной среде. Изменчивые поля никогда не кэшируются локально , поэтому после того, как один поток вызывает update (), который заменяет список новым списком, затем get () во всех других потоках возвращает новый список.

Но если у вас есть код, который использует его так:

list = get()
list = list.add(something) // returns a new immutable list with the new content
update(list)

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

-121--3338956-

Рекомендуется использовать делегата от контроллера модального вида обратно к контроллеру вида, открывшему вид. Ознакомьтесь с официальными документами для получения примеров.

Причина того, что это рекомендуемый способ, заключается в том, что ViewController, который первоначально запустил модал, также будет управлять его отменой.

Это действительно просто сделать и думать более элегантно, чем использовать viewWillanDispear - как есть и другие причины, почему вид может исчезнуть!

Создайте протокол на модальном ViewController - xViewControleyDelegate

@protocol xViewControllerDelegate

    - (void) modalDialogFinished;

@end

Затем при определении родительского контроллера представления сделайте родительскую реализацию делегата с помощью < xViewControlterDelegate > .

В родительском контроллере представления будет установлен метод modalDialogFinished, который может обрабатывать отклоненную команду и обновление и т. д.

Не забудьте передать id < xViewControleyDelegate > контроллеру модального представления в коде init и сохранить его в качестве поля объекта.

Когда вы хотите к disssmiss свою модальную точку зрения тогда, вы просто должны сослаться на delegate.modalDialogFinished.

Если это не имеет смысла, то я могу указать вам на какой-то лучший пример кода - но я надеюсь, что использование делегатов не ново для вас.

UPDATE::

Вот официальная документация Apple о том, как это сделать для контроллера модального вида:

http://developer.apple.com/iphone/library/featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html

-121--1713811-

Вы можете увидеть, как это делается с помощью функции крючка ExtLib. ExtLib:: Hook в основном позволяет вызывать произвольные обратные вызовы до или после завершения метода. См. код на GitHub здесь, чтобы узнать, как он выполнен (он переопределяет : метод _ добавлен для автоматической перезаписи методов по мере их добавления в класс).

2
ответ дан 6 December 2019 в 09:19
поделиться

У меня нет полного ответа, но я думаю, что method_added может быть здесь полезным.

0
ответ дан 6 December 2019 в 09:19
поделиться

RCapture делает то, что вы хотите: http://cheind.wordpress.com/2010/01/07/introduction-rcapture/

0
ответ дан 6 December 2019 в 09:19
поделиться

Я заставил его работать с помощью класса Proxy - а затем установить константу, используя имя реального класса. Хотя я не знаю, как заставить его работать с экземплярами. Есть ли способ изменить, на какие объектные переменные тоже указывают?

В принципе, я хочу это сделать:

t = Test.new
Persist.new(t)

t.foo # invokes callback

Вот код, который я использовал, чтобы заставить его работать с классами:

class Persist
  class Proxy
    instance_methods.each { |m| 
      undef_method m unless m =~ /(^__|^send$|^object_id$)/ 
    }

    def initialize(object)
      @_persist = object
    end

    protected
      def method_missing(sym, *args)
        puts "Called #{sym}"
        @_persist.send(sym, *args)
      end
  end


  attr_reader :object, :proxy

  def initialize(object)
    @object = object
    @proxy  = Proxy.new(@object)
    if object.respond_to?(:name)
      silence_warnings do
        Object.const_set(@object.name, @proxy)
      end
    end
  end
end
0
ответ дан 6 December 2019 в 09:19
поделиться