How can I get the binding from method_missing?

回眸只為那壹抹淺笑 提交于 2020-01-04 04:16:27

问题


I am trying to find a way to get the binding from the caller within method_missing in Ruby (1.8), but I can't seem to find a way to do it.

Hopefully the following code explains what I would like to do:

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

So... is there a way to obtain the caller's binding, or is this just impossible in Ruby (1.8)?


回答1:


Here's a (somewhat fragile) 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)

This works with your example, but I haven't tested it with much else

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

Of course, this won't work if the binding doesn't define the variable you're trying to evaluate, but this is a general issue with bindings. If a variable is not defined, it doesn't know what it is.

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

To get around this, you need to do some error handling within the evaluated string:

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



回答2:


If the method is invoked with a block you can get the block's binding (which closes over the caller's binding) by doing block.binding. That doesn't work without a block though.

You can't get the caller's binding directly (well, unless you pass it explicitly of course).

Edit: I should add that there once was a Binding.of_caller method floating around, but that doesn't work with any of the recent ruby versions anymore (where recent includes 1.8.6)




回答3:


This may be a bit messier than you wanted, but here's one way I was able to do it.

#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

Replacing the class and method definitions with the Class.new and define_method calls is only half the job, though. Unfortunately, the ugly part is that it only works if you already define x beforehand, so you're not really grabbing the caller's binding (instead the callee is modifying the variable at a different scope).

This may be equivalent to just defining all your variables as globals, but this may work for you depending on your situation. And maybe with this you'll be able to find the last piece of the puzzle with this change in hand (if this doesn't work for you).

EDIT: You can get the binding of any of the methods as follows, but even with it, I'm not able to eval successfully (be sure to put this at the top). This will fill up @@binding with the bindings for some_method and method_missing (in that order), so maybe that can help out somehow.

@@binding = []

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


来源:https://stackoverflow.com/questions/1314592/how-can-i-get-the-binding-from-method-missing

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!