【问题标题】:How can I get the binding from method_missing?如何从 method_missing 获取绑定?
【发布时间】:2009-08-21 23:17:28
【问题描述】:

我正在尝试找到一种方法来从 Ruby (1.8) 中的 method_missing 中的调用方获取绑定,但我似乎找不到方法。

希望下面的代码能解释我想要做什么:

class A
  def some_method
    x = 123
    nonexistent_method
  end

  def method_missing(method, *args, &block)
    b = caller_binding # <---- Is this possible?
    eval "puts x", b
  end
end

A.new.some_method
# expected output:
#   123

那么...有没有办法获得调用者的绑定,或者这在 Ruby (1.8) 中是不可能的?

【问题讨论】:

    标签: ruby binding


    【解决方案1】:

    这是一个(有点脆弱的)hack:

    # caller_binding.rb
    TRACE_STACK = []
    VERSION_OFFSET = { "1.8.6" => -3, "1.9.1" => -2 }[RUBY_VERSION]
    def caller_binding(skip=1)
      TRACE_STACK[ VERSION_OFFSET - skip ][:binding]
    end
    set_trace_func(lambda do |event, file, line, id, binding, classname|
      item = {:event=>event,:file=>file,:line=>line,:id=>id,:binding=>binding,:classname=>classname}
      #p item
      case(event)
      when 'line'
        TRACE_STACK.push(item) if TRACE_STACK.empty?
      when /\b(?:(?:c-)?call|class)\b/
        TRACE_STACK.push(item)
      when /\b(?:(?:c-)?return|end|raise)\b/
        TRACE_STACK.pop
      end
    end)
    

    这适用于您的示例,但我尚未对其进行过多测试

    require 'caller_binding'
    class A
      def some_method
        x = 123
        nonexistent_method
      end
      def method_missing( method, *args, &block )
        b = caller_binding
        eval "puts x", b
      end
    end
    
    x = 456
    A.new.some_method #=> prints 123
    A.new.nonexistent_method #=> prints 456
    

    当然,如果绑定没有定义您要评估的变量,这将不起作用,但这是绑定的一般问题。如果一个变量没有定义,它就不知道它是什么。

    require 'caller_binding'
    def show_x(b)
      begin
        eval <<-SCRIPT, b
          puts "x = \#{x}"
        SCRIPT
      rescue => e
        puts e
      end
    end
    
    def y
      show_x(caller_binding)
    end
    
    def ex1
      y #=> prints "undefined local variable or method `x' for main:Object"
      show_x(binding) #=> prints "undefined local variable or method `x' for main:Object"
    end
    
    def ex2
      x = 123
      y #+> prints "x = 123"
      show_x(binding) #+> prints "x = 123"
    end
    
    ex1
    ex2
    

    要解决这个问题,您需要在评估的字符串中进行一些错误处理:

    require 'caller_binding'
    def show_x(b)
      begin
        eval <<-SCRIPT, b
          if defined? x
            puts "x = \#{x}"
          else
            puts "x not defined"
          end
        SCRIPT
      rescue => e
        puts e
      end
    end
    
    def y
      show_x(caller_binding)
    end
    
    def ex1
      y #=> prints "x not defined"
      show_x(binding) #=> prints "x not defined"
    end
    
    def ex2
      x = 123
      y #+> prints "x = 123"
      show_x(binding) #+> prints "x = 123"
    end
    
    ex1
    ex2
    

    【讨论】:

    • 我在我的盒子上试过了,就像我的回答一样,它也依赖于在不同的范围内声明 x(如果没有说明 x = 456,它就不起作用)。
    • 如果 x 没有在调用者的绑定中定义,它不会找到它,不会超过 eval "puts x", binding() 在调用者的上下文中工作。有关更多信息,请参阅我的编辑。
    • @Chris:Rampion 的caller_binding 实现无需特殊调整即可工作。尽管它需要将"1.8.7" =&gt; -3 添加到其偏移哈希中才能与 Ruby 1.8.7 一起使用。
    • 不是我想要的解决方案,但我认为这是你能做的最好的,所以我接受了。作为记录,我想访问在 Rails 模板引擎中定义的变量,以设置一个缺少的方法来访问该变量并调用变量上的方法(将“var.method”变成“方法”,所以它更像DSL)。我不想要这么复杂的解决方案,所以我最终只是修补了插件......我一开始就试图避免这种情况。那好吧!感谢您的想法! :-)
    【解决方案2】:

    如果使用块调用该方法,您可以通过执行block.binding 获取块的绑定(关闭调用者的绑定)。但是,如果没有块,那是行不通的。

    你不能直接获取调用者的绑定(当然,除非你明确地传递它)。

    编辑:我应该补充一点,曾经有一个 Binding.of_caller 方法浮动,但它不再适用于任何最近的 ruby​​ 版本(最近包括 1.8.6)

    【讨论】:

    • dang,不幸的是,在我所处的情况下,我没有障碍......我希望你错了,但我不认为你是 :-(
    【解决方案3】:

    这可能比你想要的有点混乱,但这是我能够做到的一种方法。

    #x = 1 # can uncomment out this and comment the other if you like
    
    A = Class.new do
      x = 1
      define_method :some_method do
        x = 123
        nonexistent_method
      end
    
      define_method :method_missing do |method, *args|
        puts x
      end
    end
    
    A.new.some_method
    

    不过,用Class.newdefine_method 调用替换类和方法定义只是完成了一半工作。不幸的是,丑陋的部分是它仅在您已经预先定义 x 时才有效,因此您并没有真正抓住调用者的绑定(而是被调用者在不同的范围内修改变量)。

    这可能等同于将所有变量定义为全局变量,但这可能对您有用,具体取决于您的情况。也许有了这个,你就可以找到这个变化的最后一块拼图(如果这对你不起作用)。

    编辑:您可以按如下方式获取任何方法的绑定,但即使使用它,我也无法成功eval(请务必将其放在顶部) .这将用some_methodmethod_missing 的绑定(按此顺序)填充@@binding,所以也许这会有所帮助。

    @@binding = []
    
    class Class
      alias real_def define_method
      def define_method(method_name, &block)
        real_def method_name, &block
        @@binding << block.binding
      end
    end
    

    【讨论】:

    • 欣赏这些想法,不幸的是我实际上无法访问我需要绑定的方法......它是在一个库中定义的,我想避免使用个人补丁,所以我可以'并没有真正改变事情,所以我可以访问变量。尽管为努力而投票! :-)
    猜你喜欢
    • 2018-04-05
    • 2016-04-05
    • 1970-01-01
    • 2014-06-14
    • 1970-01-01
    • 2010-09-26
    • 2016-04-22
    • 2013-02-10
    • 1970-01-01
    相关资源
    最近更新 更多