问题
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