2009-08-21 51 views
3

我想找到一种方法来获取来自调用方在Ruby(1.8)method_missing中的绑定,但我似乎无法找到一种方法来做到这一点。如何从method_missing获取绑定?

希望下面的代码解释了我想做些什么:

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 

所以...有没有办法来获得主叫方的结合,或者这只是不可能的红宝石(1.8)?

回答

6

这里的(有些脆弱)黑客:

# 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 
+0

我试过了在我的盒子和喜欢我的答案,它依靠x也在不同的范围内声明(这里如果x = 456没有说明,它不起作用)。 – 2009-08-22 18:18:41

+0

如果x在调用者的绑定中没有定义,它不会找到它,只不过是'eval“放x”,binding()'可以在调用者的上下文中工作。看到我的编辑更多。 – rampion 2009-08-23 09:17:53

+0

@Chris:Rampion的'caller_binding'实现没有特别的适应性。尽管为了与Ruby 1.8.7一起工作,它需要将“1.8.7”=> -3'添加到它的偏移散列中。 – Chuck 2009-08-23 09:26:08

3

如果用块调用该方法,您可以通过执行block.binding来获得块的绑定(关闭调用者的绑定)。尽管如此,这并不起作用。

你不能直接得到调用者的绑定(当然,除非你明确地通过它)。

编辑:我要补充一点,曾经有一个Binding.of_caller方法左右浮动,但不与任何最近的Ruby版本的工作了(其中包括最近的1.8.6)

+0

宕,遗憾的是我没有在我的情况块...我希望你在某种程度上是错误的,但我不认为你是:-( – 2009-08-21 23:35:49

2

这可能是有点比你想要的更复杂,但这是我能够做到的一个方法。

#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.newdefine_method电话更换类和方法的定义是只有一半的工作,虽然。不幸的是,丑陋的部分是,它只有在你事先已经定义了x时才有效,所以你并没有真正抓住调用者的绑定(而是被调用者正在修改不同范围的变量)。

这可能等同于将所有变量定义为全局变量,但根据您的情况,这可能适用于您。也许有了这个,你将能够在手中找到最后一块难题(如果这不适合你)。

编辑:你可以得到任何方法的绑定如下,但即使有它,我不能eval成功(一定要把它放在顶部)。这将填满@@binding,绑定为some_methodmethod_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 
+0

欣赏这些想法,不幸的是我实际上并没有访问我需要绑定的方法......它是在我想要的库中定义的为了避免有一个个人补丁,所以我不能真正转移的东西,所以我有权访问该变量。Upvoted虽然努力!:-) – 2009-08-24 17:02:57