Я только что придумал это:
module MethodInterception
def method_added(meth)
return unless (@intercepted_methods ||= []).include?(meth) && !@recursing
@recursing = true # protect against infinite recursion
old_meth = instance_method(meth)
define_method(meth) do |*args, &block|
puts 'before'
old_meth.bind(self).call(*args, &block)
puts 'after'
end
@recursing = nil
end
def before_filter(meth)
(@intercepted_methods ||= []) << meth
end
end
Используйте это так:
class HomeWork
extend MethodInterception
before_filter(:say_hello)
def say_hello
puts "say hello"
end
end
Работает:
HomeWork.new.say_hello
# before
# say hello
# after
Основная проблема в вашем коде заключалась в том, что вы переименовали метод в своем методе before_filter
, но затем в своем клиентском коде вы вызвали before_filter
до того, как метод был фактически определен, что привело к попытке переименовать метод, который не существует.
Решение простое: не делай этого!
Ну да ладно, может не все так просто. Вы можете просто заставить своих клиентов всегда вызывать before_filter
после определения ими своих методов. Однако это плохой дизайн API.
Таким образом, вы должны каким-то образом устроить так, чтобы ваш код откладывал перенос метода до тех пор, пока он действительно не существует. Что я и сделал: вместо того, чтобы переопределять метод внутри метода before_filter
, я фиксирую только тот факт, что он будет переопределен позже. Затем я выполняю фактическое переопределение в хуке method_added
.
В этом есть крошечная проблема, потому что если вы добавите метод внутри method_added
, то конечно он тут же вызовется снова и снова добавит метод, что приведет к его повторному вызову и так далее. Итак, мне нужно защититься от рекурсии.
Обратите внимание, что это решение на самом деле также обеспечивает порядок на клиенте: в то время как версия OP только работает, если вы вызываете before_filter
после определения метода, мой версия работает, только если вы вызываете ее before. Однако его тривиально легко расширить, чтобы он не страдал от этой проблемы.
Обратите также внимание, что я сделал некоторые дополнительные изменения, которые не связаны с проблемой, но которые я считаю более рубиновыми:
- используйте миксин вместо класса: наследование — очень ценный ресурс в Ruby, потому что вы можете наследовать только от одного класса. Миксины, однако, дешевы: вы можете смешивать столько, сколько хотите. Кроме того: можете ли вы действительно сказать, что Домашнее задание ЯВЛЯЕТСЯ методом перехвата?
- используйте
Module#define_method
вместо eval
: eval
зло. 'Достаточно. (Во-первых, не было абсолютно никакой причины использовать eval
в коде ОП.)
- используйте технику переноса методов вместо
alias_method
: метод цепочки alias_method
загрязняет пространство имен бесполезными методами old_foo
и old_bar
. Мне нравятся чистые пространства имен.
Я просто исправил некоторые из упомянутых выше ограничений и добавил еще несколько функций, но мне лень переписывать свои объяснения, поэтому я публикую измененную версию здесь:
module MethodInterception
def before_filter(*meths)
return @wrap_next_method = true if meths.empty?
meths.delete_if {|meth| wrap(meth) if method_defined?(meth) }
@intercepted_methods += meths
end
private
def wrap(meth)
old_meth = instance_method(meth)
define_method(meth) do |*args, &block|
puts 'before'
old_meth.bind(self).(*args, &block)
puts 'after'
end
end
def method_added(meth)
return super unless @intercepted_methods.include?(meth) || @wrap_next_method
return super if @recursing == meth
@recursing = meth # protect against infinite recursion
wrap(meth)
@recursing = nil
@wrap_next_method = false
super
end
def self.extended(klass)
klass.instance_variable_set(:@intercepted_methods, [])
klass.instance_variable_set(:@recursing, false)
klass.instance_variable_set(:@wrap_next_method, false)
end
end
class HomeWork
extend MethodInterception
def say_hello
puts 'say hello'
end
before_filter(:say_hello, :say_goodbye)
def say_goodbye
puts 'say goodbye'
end
before_filter
def say_ahh
puts 'ahh'
end
end
(h = HomeWork.new).say_hello
h.say_goodbye
h.say_ahh
person
Jörg W Mittag
schedule
23.09.2010