Как я могу получить привязку от method_missing? - PullRequest
3 голосов
/ 22 августа 2009

Я пытаюсь найти способ получить привязку от вызывающей стороны в 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)?

Ответы [ 3 ]

6 голосов
/ 22 августа 2009

Вот (несколько хрупкий) взлом:

# 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
3 голосов
/ 22 августа 2009

Если метод вызывается с блоком, вы можете получить привязку блока (которая закрывается по привязке вызывающего), выполнив block.binding. Это не работает без блока, хотя.

Вы не можете получить привязку вызывающего напрямую (ну, конечно, если вы не передаете ее явно).

Редактировать: я должен добавить, что когда-то был метод Binding.of_caller, но он больше не работает ни с одной из последних версий ruby ​​(где последние включают 1.8.6)

2 голосов
/ 22 августа 2009

Это может быть немного грязнее, чем вы хотели, но вот один из способов, которым я смог это сделать.

#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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...