如何从method_missing获取绑定?

Mik*_*one 3 ruby binding

我试图在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
Run Code Online (Sandbox Code Playgroud)

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

ram*_*ion 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)
Run Code Online (Sandbox Code Playgroud)

这适用于您的示例,但我还没有用其他方法测试它

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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)