2009-08-21 9 views
3

J'essaye de trouver un moyen d'obtenir la liaison de l'appelant dans method_missing dans Ruby (1.8), mais je n'arrive pas à trouver un moyen de le faire.Comment puis-je obtenir la liaison de method_missing?

Espérons que le code suivant explique ce que je voudrais faire:

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 

Alors ... est-il un moyen d'obtenir la liaison de l'appelant, ou est-ce juste impossible à Ruby (1.8)?

Répondre

6

est ici un hack (un peu fragile):

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

Cela fonctionne avec votre exemple, mais je ne l'ai pas testé avec beaucoup d'autres

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 

Bien sûr, ce won Ne travaillez pas si la liaison ne définit pas la variable que vous essayez d'évaluer, mais c'est un problème général avec les liaisons. Si une variable n'est pas définie, elle ne sait pas ce que c'est.

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 

Pour contourner ce problème, vous devez faire une erreur de manipulation dans la chaîne évaluée:

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

Je l'ai essayé sur ma boite et comme ma réponse, ça dépend de x étant déclaré dans une portée différente également (ici si x = 456 n'est pas indiqué, cela ne fonctionne pas). –

+0

Si x n'est pas défini dans la liaison de l'appelant, il ne va pas le trouver, pas plus que 'eval 'met x', binding()' fonctionnerait dans le contexte de l'appelant. Voir mon édition pour plus. – rampion

+0

@Chris: L'implémentation 'caller_binding' de Rampion fonctionne sans adaptation spéciale. Bien qu'il ait besoin de '" 1.8.7 "=> -3' ajouté à son hachage offset afin de travailler avec Ruby 1.8.7. – Chuck

3

Si la méthode est appelée avec un bloc, vous pouvez obtenir la liaison du bloc (qui se ferme sur la liaison de l'appelant) en faisant block.binding. Cela ne fonctionne pas sans bloc cependant.

Vous ne pouvez pas obtenir la liaison de l'appelant directement (enfin, à moins que vous ne le passiez explicitement bien sûr).

Edit: Je dois ajouter qu'il y était une fois une méthode Binding.of_caller flottant autour, mais cela ne fonctionne pas avec l'une des versions les plus récentes de rubis plus (où récent comprend 1.8.6)

+0

dang, malheureusement, je ne dispose pas d'un bloc dans la situation que je suis en ...J'espère que vous êtes en quelque sorte mal, mais je ne pense pas que vous êtes :-( –

2

Cela peut être un peu plus désordonné que vous ne le vouliez, mais voici une façon de le faire.

#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 

Remplacement des définitions de classe et de méthode avec les Class.new et define_method appels est seulement la moitié du travail, cependant. Malheureusement, la partie la plus laide est que cela ne fonctionne que si vous avez déjà défini x auparavant, donc vous ne saisissez pas vraiment la liaison de l'appelant (à la place, l'appelé modifie la variable sur une portée différente). Cela peut être équivalent à la définition de toutes vos variables en tant que globales, mais cela peut fonctionner pour vous selon votre situation. Et peut-être qu'avec cela vous pourrez trouver la dernière pièce du puzzle avec ce changement de main (si cela ne marche pas pour vous).

EDIT: Vous pouvez obtenir la liaison de l'une des méthodes comme suit, mais même avec elle, je ne suis pas en mesure de eval (assurez-vous de mettre ce en haut) avec succès. Cela va remplir @@binding avec les liaisons pour some_method et method_missing (dans cet ordre), alors peut-être que cela peut aider d'une manière ou d'une autre.

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

Appréciez les idées, malheureusement, je n'ai pas vraiment accès à la méthode dont j'ai besoin de la liaison pour ... il est défini dans une bibliothèque que je voulais pour éviter d'avoir un patch personnel, donc je ne peux pas vraiment changer les choses, donc j'ai accès à la variable. Upvoted pour l'effort si :-) –

Questions connexes