2016-12-05 1 views
3

Скажем, у меня есть этот класс:Как я могу «повторно подключить» отсоединенный метод в Ruby?

class Foo 
    def destroy_target(target) 
    Missile.launch(target) 
    end 
end 

Я хочу временно среднего рода разрушительной силы Foo, например, для целей тестирования, так что я делаю это:

backup = Foo.instance_method(:destroy_target) 

class Foo 
    def destroy_target(target) 
    Pillow.launch(target) 
    end 
end 

Вот мой вопрос: как мне «повторно подключить» исходный метод до Foo, как если бы он никогда не был переопределен в первую очередь?

Я понимаю, что я могу это сделать:

class Foo 
    def destroy_target(target) 
    backup.bind(self).call(target) 
    end 
end 

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

спросил по-другому; как я могу присоединить DetachedMethod к классу «правильно», то есть без определения нового метода, который вызывает отсоединенный.


Примечание: Меня не интересуют альтернативные способы временного изменения функциональности класса. Я специально хочу знать, как заменить метод другим методом, а затем восстановить исходный метод.

+0

'backup = Foo.instance_method (: destroy_target)' без 's' –

+0

Вы хотите заменить метод для конкретного экземпляра' Foo' или класса, т. Е. Для всех 'Foo'? – Stefan

ответ

1

Это, кажется, работает:

Foo.instance_exec { 
    define_method(:destroy_target, backup) 
} 

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

Это также, кажется, работает, если Foo это модуль определяется следующим образом:

module Foo 
    extend self 

    def destroy_target(target) 
    Missile.launch(target) 
    end 
end 
+0

'define_method' кажется правильным, но я бы использовал' Foo.send (: define_method,: destroy_target, backup) '- он выглядит немного менее инвазивным. – Stefan

2

Я проверил свой первый пример, и это, кажется, работает хорошо. Я не мог найти побочного эффекта, но это не значит, что нет.

Вы считаете refinements?

Для класса

class Missile 
    def self.launch(t) 
    puts "MISSILE -> #{t}" 
    end 
end 

class Pillow 
    def self.launch(t) 
    puts "PILLOW -> #{t}" 
    end 
end 

class Foo 
    def destroy_target(target) 
    Missile.launch(target) 
    end 
end 

module PillowLauncher 
    refine Foo do 
    def destroy_target(target) 
     Pillow.launch(target) 
    end 
    end 
end 

module Test 
    using PillowLauncher 
    Foo.new.destroy_target("Tatooine") 
    #=> PILLOW -> Tatooine 
end 

Foo.new.destroy_target("Tatooine") 
#=> MISSILE -> Tatooine 

Это может принести преимущество того, чтобы быть немного больше стандартного и понятно, чем ваш пример.

Для модуля

Если Foo является Module, вы не можете назвать refine Foo напрямую, вы получите TypeError: wrong argument type Module (expected Class).

Вы можете, однако, уточнить его singleton_class:

module Foo 
    def self.destroy_target(target) 
    Missile.launch(target) 
    end 
end 

module PillowLauncher 
    refine Foo.singleton_class do 
    def destroy_target(target) 
     Pillow.launch(target) 
    end 
    end 
end 

module Test 
    using PillowLauncher 
    Foo.destroy_target('Tatooine') 
    #=> PILLOW -> Tatooine 
end 

Foo.destroy_target('Tatooine') 
#=> MISSILE -> Tatooine 

Я не уверен, о вашей заметке

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

Мой предлагаемый код, похоже, делает оба.

+0

Уточнения выглядят действительно круто, но, видимо, они работают только с классами (а не с модулями). В моем текущем случае использования мне нужно, чтобы он работал с модулями. До Ruby версии 2.3 они также должны были быть активированы на верхнем уровне, то есть они не могли быть активированы внутри класса или модуля, как вы демонстрируете. – Hubro

+0

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

+1

@Hubro: обновлен с примером для модуля Foo –

Смежные вопросы