获取包含模块的类列表

27

我有一个mixin,我想获取所有已包含它的类的列表。在mixin模块中,我做了以下操作:

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

  def self.classes
    @classes
  end
end

class MyClass
  include MyModule
end

这个很好用:

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

现在,我想将这一部分提取出来成为一个单独的模块,可以包含在我的其他混合模块中。因此,我想出了以下代码:

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

然而这并不起作用,因为从ListIncludedClasses添加到MyModule的#included(module_base)方法从未运行。有趣的是,它确实成功地将#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

我错过了什么?


不要忘记指出我回答了你的问题! - Aaa
你尝试过这个吗:http://stackoverflow.com/questions/3527445/when-are-modules-included-in-a-ruby-class-running-in-rails - rickypai
4个回答

39
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]

请参见ObjectSpace::each_object


3

Cary的答案(非常好!)有一个需要注意的地方,它只会捕捉到VM已经评估过的类。

所以,如果你在像开发Rails控制台这样的环境中运行该代码,你需要在检查模块是否被包含之前明确地require感兴趣的文件。

你可以这样做:

Dir[Rails.root.join("app/models/**/*.rb")].each { |f| require f }

2

实际上,您的模块扩展模块是有效的。问题在于您的测试:当您使用Class.new创建一个随机的未命名类时,您忘记了包含MyModule。顺便说一下,您可以使用有用的Module#attr_reader方法获取包含该模块的类的只读访问器。


谢谢回复。是的,现在我尝试时它可以工作,我不确定当时我在做什么。我想我把它放在了一个Rails项目中而不是隔离它,所以谁知道呢。我没有忘记使用include定义MyClass,我只是在上面的示例中没有重新输入它。否则,MyClass.new将会抛出uninitialized constant MyClass。另外,Module#attr_reader创建实例方法,所以是的,我可以在ClassMethods内部使用它,然后也可以在ListIncludedClassesclass << self块内使用它。这更有效率,因此在生产中这将是正确的方式。 - jangosteve

1

你应该使用extend而不是include,因为前者添加类级别的方法,而后者添加实例级别的方法(这就是为什么你可以访问@classes)。

试试这个:

module MyModule
  extend ListIncludedClasses::ClassMethods
end

2
我不知道你是否注意到了,但是extend(ClassMethods)在self.included(base)方法中,因此它正在扩展类方法。还有其他需要添加的实例方法;这是广泛接受的技术,既包括实例方法,也扩展类方法。此外,直接扩展类方法,就像你建议的那样,没有任何区别。 - jangosteve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接