2010-10-12 15 views
14

J'ai un mixin pour lequel j'aimerais avoir une liste de toutes les classes qui l'ont inclus. Dans le module mixin, je l'ai fait ce qui suit:Obtenir une liste des classes qui incluent un module

module MyModule 
    def self.included(base) 
    @classes ||= [] 
    @classes << base.name 
    end 

    def self.classes 
    @classes 
    end 
end 

class MyClass 
    include MyModule 
end 

Cela fonctionne assez bien:

> MyModule.classes #=> nil 
> MyClass.new #=> #<MyClass ...> 
> MyModule.classes #=> ["MyClass"] 

Maintenant, je voudrais extraire cette partie dehors dans un module séparé qui peut être inclus dans mon autre les mixins. Alors, je suis venu avec ce qui suit:

module ListIncludedClasses 
    def self.included(base) 
    p "...adding #{base.name} to #{self.name}.classes" 

    @classes ||= [] 
    @classes << base.name 

    base.extend(ClassMethods) 
    end 

    def self.classes 
    @classes 
    end 

    module ClassMethods 
    def included(module_base) 
     p "...adding #{module_base.name} to #{self.name}.classes" 

     @module_classes ||= [] 
     @module_classes << module_base.name 
     super(module_base) 
    end 
    def classes 
     @module_classes 
    end 
    end 

end 

module MyModule 
    include ListIncludedClasses 
end 

Cela ne fonctionne pas bien, parce que la méthode #include (de module_base) ajouté à MyModule de ListIncludedClasses est jamais se faire écraser. Fait intéressant, il ajoute avec succès #classes à MyModule.

> MyModule.classes #=> 
    "...adding Rateable to ListIncludedClasses.classes" 
    => nil 
> ListIncludedClasses #=> ["MyModule"] 
> MyClass.new #=> #<MyClass ...> 
# ^^ THIS SHOULD BE ADDING MyClass TO MyModule.classes ^^ 
> MyModule.classes #=> nil 

Que manque-t-il?

+0

N'oubliez pas d'indiquer que j'ai répondu à votre question! – Tom

+0

avez-vous essayé ceci: http://stackoverflow.com/questions/3527445/when-are-modules-included-in-a-ruby-class-running-in-rails – rickypai

Répondre

14
module MyMod; end 

class A; include MyMod; end 
class B < A; end 
class C; end 

ObjectSpace.each_object(Class).select { |c| c.included_modules.include? MyMod } 
    #=> [B, A] 
1

Vous devriez probablement utiliser extend au lieu d'include car les anciens ajoutent des méthodes de niveau classe, tandis que les méthodes de niveau de dernière instance (pourquoi vous avez accès à @classes).

Essayez ceci:

module MyModule 
    extend ListIncludedClasses::ClassMethods 
end 
+2

Je ne sais pas si vous l'avez manqué, mais extend (ClassMethods) est dans la méthode self.included (base), donc il étend les méthodes de classe. Il existe d'autres méthodes d'instance qui doivent être ajoutées; C'est la technique généralement acceptée pour inclure les méthodes d'instance et étendre les méthodes de classe. En outre, étendre directement les méthodes de classe, comme vous l'avez suggéré, ne fait aucune différence. – jangosteve

2

En fait, le fonctionnement de votre module d'extension du module. Le problème est dans votre test: lorsque vous avez créé une classe aléatoire sans nom avec Class.new, vous avez oublié d'inclure MyModule. En remarque, vous pouvez utiliser votre accesseur en lecture seule pour les classes qui incluent le module et utiliser la méthode utile Module#attr_reader.

+0

Merci pour la réponse. Oui, ça marche quand je l'essaye maintenant, je ne suis pas sûr de ce que je faisais à ce moment-là. Je pense que je l'ai eu dans un projet Rails au lieu de l'isoler, alors qui sait. Ce n'est pas que j'ai oublié la définition 'MyClass' avec' include', je ne l'ai pas retapée dans l'exemple ci-dessus. Sinon, MyClass.new aurait lancé 'myClass constant non initialisé'. De plus 'Module # attr_reader' crée des méthodes d'instance, alors oui j'aurais pu l'utiliser dans' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' ''. C'est plus efficace, donc dans la production ce serait le chemin à parcourir. – jangosteve

Questions connexes