Getting a list of classes that include a module
I have a mixin for which I would like to get a list of all the classes that have included it. In the mixin module, I did the following:
module MyModule
def self.included(base)
@classes ||= []
@classes << base.name
end
def self.classes
@classes
end
end
class MyClass
include MyModule
end
This works pretty well:
> MyModule.classes #=> nil
> MyClass.new #=> #<MyClass ...>
> MyModule.classes #=> ["MyClass"]
Now, I would like to extract this part out into a separate module that can be included in my other mixins. So, I came up with the following:
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
开发者_StackOverflow社区 end
end
module MyModule
include ListIncludedClasses
end
This doesn't work though, because the #included(module_base) method being added to MyModule from ListIncludedClasses is never getting run. Interestingly enough, it does successfully add #classes to 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
What am I missing?
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]
See ObjectSpace:: each_object.
One caveat to Cary's answer (which is great!) is that it will only pick up on classes that have already been evaluated by the VM.
So, if you're running that code in a setting like a development Rails console you'll need to explicitly require
the files you're interested in before checking if the module has been included.
You can do that like this:
Dir[Rails.root.join("app/models/**/*.rb")].each { |f| require f }
Actually, your module extension module works. The problem is in your test: when you created a random unnamed class with Class.new
, you forgot to include MyModule
. As a side note, you can take your read-only accessor for classes that include the module and use the helpful Module#attr_reader
method.
You probably should use extend instead of include since former adds class level methods, while latter - instance level methods (why you have access to @classes
).
Try this:
module MyModule
extend ListIncludedClasses::ClassMethods
end
精彩评论