如何从method_missing方法中获取绑定?

3

我正在尝试在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)中这是不可能的?

3个回答

6
这里有一个(比较脆弱的)技巧:

# 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,在这里它不起作用)。 - Chris Bunch
如果在调用者的绑定中未定义x,它将无法找到它,就像在调用者的上下文中使用eval "puts x", binding()一样。请参见我的编辑以获取更多信息。 - rampion
@Chris:Rampion的caller_binding实现不需要任何特殊的适应措施。但是,为了让它能够与Ruby 1.8.7一起使用,需要将"1.8.7" => -3添加到其偏移哈希中。 - Chuck
不完全是我想要的解决方案,但我认为这是你能做到的最好的,所以我接受了。记录一下,我想要访问在Rails模板引擎中定义的变量,以设置一个方法缺失来访问该变量并调用变量上的方法(将“var.method”转换为只是“method”,使其更像DSL)。我不想为此使用如此复杂的解决方案,所以最终我只是修补了插件...这正是我一开始想避免的。哦,好吧!谢谢你的建议! :-) - Mike Stone

3
如果使用块调用该方法,您可以通过执行block.binding来获取块的绑定(它闭合了调用者的绑定)。但没有块是不起作用的。
您无法直接获取调用者的绑定(除非您当然明确传递它)。
编辑:我应该补充一下,曾经有一个Binding.of_caller方法漂浮在周围,但它不再与任何最新版本的Ruby版本一起使用(其中最近包括1.8.6)。

当然,不幸的是我在这种情况下没有可用的代码块... 我希望你错了,但我认为你是正确的 :-( - Mike Stone

2
这可能比你想象的要复杂一些,但以下是我能做到的一种方法。
#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

感谢您的想法,不幸的是我实际上无法访问我需要绑定的方法...它在一个库中定义,我想避免为此制作个人补丁,所以我不能真正地调整事情以便我可以访问该变量。尽管如此,我还是给了您点赞! :-) - Mike Stone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接