Как я могу получить привязку метода method_missing?
Я пытаюсь найти способ получить привязку от вызывающего в method_missing в Ruby (1.8), но я не могу найти способ сделать это.
Надеюсь, следующий код объясняет, что я хотел бы сделать:
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)?
Ответы
Ответ 1
Здесь (несколько хрупкий) взлом:
# 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
Ответ 2
Если метод вызывается с помощью блока, вы можете получить привязку блока (которая закрывается по привязке звонящего), выполнив block.binding
. Это не работает без блока.
Вы не можете получить привязку звонящего напрямую (ну, если вы не передадите ее явно, конечно).
Изменить: я должен добавить, что когда-то использовался метод Binding.of_caller, но это больше не работает ни с одной из последних рубиновых версий (где последние включают 1.8.6)
Ответ 3
Это может быть немного грязнее, чем вы хотели, но здесь одним способом я смог это сделать.
#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.new
и define_method
составляет только половину задания. К сожалению, уродливая часть заключается в том, что она работает только в том случае, если вы уже заранее определили x
, так что вы на самом деле не захватываете привязку звонящего (вместо этого вызывающая изменяет переменную в другой области).
Это может быть эквивалентно простому определению всех ваших переменных как глобальных, но это может сработать для вас в зависимости от вашей ситуации. И, возможно, с этим вы сможете найти последний кусочек головоломки с этим изменением в руке (если это не сработает для вас).
РЕДАКТИРОВАТЬ: Вы можете получить привязку любого из методов следующим образом, но даже с этим я не могу eval
успешно (обязательно поставьте это вверху), Это заполнит @@binding
привязками для some_method
и method_missing
(в этом порядке), поэтому, возможно, это может помочь как-то.
@@binding = []
class Class
alias real_def define_method
def define_method(method_name, &block)
real_def method_name, &block
@@binding << block.binding
end
end