首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Ruby方法拦截

Ruby方法拦截
EN

Stack Overflow用户
提问于 2010-09-23 14:30:33
回答 3查看 2.7K关注 0票数 5

我希望拦截对ruby类的方法调用,并能够在实际执行该方法之前和之后执行一些操作。我尝试了以下代码,但得到了错误:

MethodInterception.rb:16:in before_filter': (eval):2:in别名_ method‘:未定义方法say_hello' for classHomeWork’(NameError):2:in `before_filter‘

有人能帮我把事情做好吗?

代码语言:javascript
运行
复制
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
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2010-09-23 16:19:55

从原始代码更改的代码较少。我只修改了两行。

代码语言:javascript
运行
复制
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

这个效果很好。

代码语言:javascript
运行
复制
HomeWork.new.say_hello
#=> going to call former method
#=> say hello
#=> former method called
票数 2
EN

Stack Overflow用户

发布于 2010-09-23 15:25:42

我刚想到这个:

代码语言:javascript
运行
复制
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

用它就像这样:

代码语言:javascript
运行
复制
class HomeWork
  extend MethodInterception

  before_filter(:say_hello)

  def say_hello
    puts "say hello"
  end
end

作品:

代码语言:javascript
运行
复制
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:

  • 使用一个混合体而不是一个类:在
  • 中继承是一个非常有价值的资源,因为您只能从一个类继承。然而,Mixins很便宜:你可以随意混合。另外:你真的能说家庭作业是-A MethodInterception?
  • use Module#define_method而不是evaleval是邪恶的。‘'Nuff说。(在OP的代码中,首先完全没有理由使用eval。)
  • 使用方法包装技术而不是alias_methodalias_method链技术用无用的old_fooold_bar方法污染了名称空间。我喜欢我的名称空间干净。

我只是修正了上面提到的一些限制,并增加了一些特性,但是我太懒了,不想重写我的解释,所以我在这里重新发布了修改后的版本:

代码语言:javascript
运行
复制
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
票数 15
EN

Stack Overflow用户

发布于 2010-09-23 15:42:28

J rg W Mittag的解决方案相当不错。如果您想要更健壮的东西(阅读测试良好),最好的资源应该是rails回调模块。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/3779456

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档