2017-08-30 1 views
3

que je fais un peu (trop) de fantaisie programmation méta, et j'ai du mal à comprendre pourquoi le champ d'application est différente dans les deux cas suivants:Pourquoi mon contenu define_method est-il évalué dans la portée de la classe?

Cas 1:

class TesterA 

    def the_method 
    puts "I'm an instance_method!" 
    end 

    def self.the_method 
    puts "I'm a class_method!" 
    end 

    def self.define_my_methods *method_names 
    method_names.each do |name| 
     define_method("method_#{name}") do 
     the_method 
     end 
    end 
    end 

    define_my_methods :a, :b, :c 
end 

t = TesterA.new 
t.method_a #=> I'm an instance_method! 
t.method_b #=> I'm an instance_method! 
t.method_C#=> I'm an instance_method! 

Case 2

class TesterB 

    def the_method 
    puts "I'm an instance_method!" 
    end 

    def self.the_method 
    puts "I'm a class_method!" 
    end 

    def self.define_the_method attr 
    define_method("method_#{attr}") do 
     begin 
     yield 
     rescue 
     raise $!, "method_#{attr} was called: #$!", [email protected] 
     end 
    end 
    end 

    def self.define_my_methods *method_names 
    method_names.each do |name| 
     define_the_method(name) do 
     the_method 
     end 
    end 
    end 

    define_my_methods :a, :b, :c 
end 

t = TesterB.new 
t.method_a #=> I'm a class_method! 
t.method_b #=> I'm a class_method! 
t.method_C#=> I'm a class_method! 

I le deuxième exemple que je vous présente une sorte de "aide-mothod" define_the_method que j'utilise pour définir la m ethods plutôt que define_method lui-même. Raison de cela est, Que je veux ajouter le nom de la méthode dynamique à tous les messages d'exception qui pourraient se produire à l'intérieur de ces méthodes. Le problème est cependant, que le contenu (en utilisant ce dernier cas) semble être évalué dans la portée de la classe.

Pourquoi est-ce, et comment puis-je le faire évaluer dans la portée de l'instance?

Répondre

2

C'est parce que proc défini avec define_method sera appelée à l'aide instance_eval.

Dans le premier échantillon, il est:

do 
    the_method 
end 

En seconde:

do 
    begin 
    yield 
    rescue 
    raise $!, "method_#{attr} was called: #$!", [email protected] 
    end 
end 

Mais yield aura la portée des parents de l'endroit qu'il a été défini.

Voici ma suggestion:

def self.define_the_method attr, &block 
    define_method("method_#{attr}") do 
    begin 
     instance_eval(&block) 
    rescue 
     raise $!, "method_#{attr} was called: #$!", [email protected] 
    end 
    end 
end 
+0

J'aime celui-ci! Cependant, dans mon cas d'utilisation dans le monde réel, j'ai besoin de pouvoir aussi passer un paramètre aux méthodes, donc j'ai juste fini par utiliser 'instance_exec' à la place. Merci pour le conseil! –

4

Ce bloc:

do 
    the_method 
end 

il défini et scope en classe portée. Il n'y a aucune raison de s'attendre à ce que ce soit injecté dans l'instance-scope. Pour résoudre le problème simplement passer le récepteur explicitement:

yield(self) 

et:

def self.define_my_methods *method_names 
    method_names.each do |name| 
    define_the_method(name) do |receiver| 
     receiver.the_method 
    end 
    end 
end 
+0

Buuuuut, c'est la même dans le premier extrait. non? (the scope) –

+0

@SergioTulentsev Non, 'define_method' gère la portée. – mudasobwa

+0

qui est présent (et appelé à partir d'une méthode de classe) dans les deux snippets –

4

Cela arrive parce que vous fournissez le bloc dans le cadre de la méthode de classe self.define_my_methods, où self est une classe, non une instance. Donc ce que vous pouvez faire est yield la portée de l'define_method lui-même:

def self.define_the_method attr 
    define_method("method_#{attr}") do 
    begin 
     yield self 
    rescue 
     raise $!, "method_#{attr} was called: #$!", [email protected] 
    end 
    end 
end 

def self.define_my_methods *method_names 
    method_names.each do |name| 
    define_the_method(name) do |scope| 
     scope.send(:the_method) 
    end 
    end 
end 
+0

"vous êtes' yield'ing dans le cadre de la méthode de classe "-nope, c'est' yield'ing dans la portée de l'instance, puisque 'define_method' utilise' instance_eval' sous le capot. Le bloc a une portée de classe, c'est pourquoi. – mudasobwa

+0

@mudasobwa: Je suis désolé, c'est ce que je voulais dire en premier lieu, je n'ai pas répondu depuis un moment et l'articulation est un peu rouillée. – potashin

+0

Merci pour la réponse, je suis allé avec le 'instance_eval' à la place, car je devais changer beaucoup de code, si j'avais besoin d'appeler explicitement toutes les méthodes sur la portée. –