Getting a list of classes that include a module

Viewed 10143

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

4 Answers

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 }
Related