est ici un hack (un peu fragile):
# 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)
Cela fonctionne avec votre exemple, mais je ne l'ai pas testé avec beaucoup d'autres
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
Bien sûr, ce won Ne travaillez pas si la liaison ne définit pas la variable que vous essayez d'évaluer, mais c'est un problème général avec les liaisons. Si une variable n'est pas définie, elle ne sait pas ce que c'est.
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
Pour contourner ce problème, vous devez faire une erreur de manipulation dans la chaîne évaluée:
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
Je l'ai essayé sur ma boite et comme ma réponse, ça dépend de x étant déclaré dans une portée différente également (ici si x = 456 n'est pas indiqué, cela ne fonctionne pas). –
Si x n'est pas défini dans la liaison de l'appelant, il ne va pas le trouver, pas plus que 'eval 'met x', binding()' fonctionnerait dans le contexte de l'appelant. Voir mon édition pour plus. – rampion
@Chris: L'implémentation 'caller_binding' de Rampion fonctionne sans adaptation spéciale. Bien qu'il ait besoin de '" 1.8.7 "=> -3' ajouté à son hachage offset afin de travailler avec Ruby 1.8.7. – Chuck