2009-08-21 50 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