2009-08-21 12 views
3

Ich versuche einen Weg zu finden, die Bindung vom Aufrufer innerhalb von method_missing in Ruby (1.8) zu bekommen, aber ich finde keinen Weg, dies zu tun.Wie bekomme ich die Bindung von method_missing?

Hoffentlich wird der folgende Code erklärt, was würde ich tun mag:

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 

So ... ist es eine Möglichkeit, der Anrufer Bindung zu erhalten, oder ist dies einfach unmöglich, in Ruby (1.8)?

Antwort

6

Hier ist eine (etwas zerbrechlich) hacken:

# 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) 

Das mit Ihrem Beispiel funktioniert, aber ich habe es nicht mit viel mehr

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 

Natürlich getestet, das gewonnen Es funktioniert nicht, wenn die Bindung die Variable, die Sie bewerten möchten, nicht definiert. Dies ist jedoch ein generelles Problem bei Bindungen. Wenn eine Variable nicht definiert ist, weiß sie nicht, was sie ist.

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 

Um dies zu umgehen, müssen Sie innerhalb der ausgewerteten String Handling einige Fehler tun:

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

Ich habe es auf meiner Box ausprobiert und mag meine Antwort, darauf baut man auf x wird auch in einem anderen Bereich deklariert (hier, wenn x = 456 nicht angegeben ist, funktioniert es nicht). –

+0

Wenn x in der Bindung des Aufrufers nicht definiert ist, wird es nicht gefunden, nicht mehr als "eval" puts x ", binding()" würde im Kontext des Aufrufers funktionieren. Siehe meine Bearbeitung für mehr. – rampion

+0

@Chris: Rampions 'caller_binding' Implementierung funktioniert ohne spezielle Anpassungen. Allerdings muss "1.8.7" => -3' zu seinem Offset-Hash hinzugefügt werden, um mit Ruby 1.8.7 zu arbeiten. – Chuck

3

Wenn die Methode mit einem Block aufgerufen wird, können Sie die Bindung des Blocks (die über die Bindung des Aufrufers schließt) abrufen, indem Sie block.binding tun. Das funktioniert aber nicht ohne Block.

Sie können die Bindung des Aufrufers nicht direkt erhalten (gut, es sei denn, Sie übergeben es explizit natürlich).

Edit: Ich sollte hinzufügen, dass es einmal eine Binding.of_caller Verfahren war im Umlauf, aber das mehr mit einem der letzten rubin Versionen nicht funktioniert (wo kürzlich 1.8.6 enthält)

+0

dang, ich einen Block in der Situation, in Ich bin nicht über leider ...Ich hoffe du irrst dich irgendwie, aber ich denke du bist nicht :-( –

2

Dies kann ein bisschen unordentlicher als du wolltest, aber hier ist eine Möglichkeit, wie ich es schaffen konnte.

#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 

Ersetzen der Klassen- und Methodendefinitionen mit den Class.new und define_method Anrufen ist nur die halbe Arbeit, though. Leider ist der hässliche Teil, dass es nur funktioniert, wenn Sie vorher bereits x definieren, so dass Sie nicht wirklich die Bindung des Aufrufers packen (stattdessen ändert der Angerufene die Variable in einem anderen Bereich).

Dies kann gleichbedeutend damit sein, dass Sie alle Ihre Variablen als globale Variablen definieren. Dies kann jedoch abhängig von Ihrer Situation für Sie funktionieren. Und vielleicht wirst du mit diesem Wechsel in der Hand das letzte Stück des Puzzles finden können (wenn das nicht für dich funktioniert).

EDIT: Sie können die Bindung von jedem des Verfahrens erhalten, wie folgt, aber auch mit ihm, ich bin nicht in der Lage zu eval erfolgreich (sicher sein, das an der Spitze zu setzen). Dies wird @@binding mit den Bindungen für some_method und method_missing füllen (in dieser Reihenfolge), also kann das vielleicht irgendwie helfen.

@@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

Schätze die Ideen, leider habe ich eigentlich keinen Zugang zu der Methode, für die ich die Bindung brauche ... sie ist in einer Bibliothek definiert, die ich wollte Um einen persönlichen Patch zu vermeiden, kann ich die Dinge nicht wirklich verschieben, also habe ich Zugriff auf die Variable. Upvoted für den Aufwand aber! :-) –

Verwandte Themen