我希望拦截对ruby类的方法调用,并能够在实际执行该方法之前和之后执行一些操作。我尝试了以下代码,但得到了错误:
MethodInterception.rb:16:in
before_filter': (eval):2:in别名_ method‘:未定义方法say_hello' for classHomeWork’(NameError):2:in `before_filter‘
有人能帮我把事情做好吗?
class MethodInterception
  def self.before_filter(method)
    puts "before filter called"
    method = method.to_s
    eval_string = "
      alias_method :old_#{method}, :#{method}
      def #{method}(*args)
        puts 'going to call former method'
        old_#{method}(*args)
        puts 'former method called'
      end
    "
    puts "going to call #{eval_string}"
    eval(eval_string)
    puts "return"
  end
end
class HomeWork < MethodInterception
  before_filter(:say_hello)
  def say_hello
    puts "say hello"
  end
end发布于 2010-09-23 16:19:55
从原始代码更改的代码较少。我只修改了两行。
class MethodInterception
  def self.before_filter(method)
    puts "before filter called"
    method = method.to_s
    eval_string = "
      alias_method :old_#{method}, :#{method}
      def #{method}(*args)
        puts 'going to call former method'
        old_#{method}(*args)
        puts 'former method called'
      end
    "
    puts "going to call #{eval_string}"
    class_eval(eval_string) # <= modified
    puts "return"
  end
end
class HomeWork < MethodInterception
  def say_hello
    puts "say hello"
  end
  before_filter(:say_hello) # <= change the called order
end这个效果很好。
HomeWork.new.say_hello
#=> going to call former method
#=> say hello
#=> former method called发布于 2010-09-23 15:25:42
我刚想到这个:
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时才能工作,而我的版本只有在您之前调用它时才能工作。然而,它很容易扩展,这样它就不会受到这个问题的影响。
还请注意,我做了一些与问题无关的额外更改,但我认为这些更改更像Rubyish:
Module#define_method而不是eval:eval是邪恶的。‘'Nuff说。(在OP的代码中,首先完全没有理由使用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发布于 2010-09-23 15:42:28
J rg W Mittag的解决方案相当不错。如果您想要更健壮的东西(阅读测试良好),最好的资源应该是rails回调模块。
https://stackoverflow.com/questions/3779456
复制相似问题