【问题标题】:Trying to find all classes that include a module试图找到包含模块的所有类
【发布时间】:2026-01-26 21:55:01
【问题描述】:

在深入探讨之前,我将尝试解释我的代码库的结构。 这个问题与 cmets 中的链接问题不同,因为它涉及模块类中包含的模块列表。不直接在类或 1 级模块中

1) 可能有一个 X 类,其定义如下:

module ParentModule
   class X
   end
 end

2) 不同模块下也可能有嵌套类:

module ParentModule
   module ChildModule
     class Y
     end
   end
 end

3) 也可能只有一个模块,里面有一些类:

module ParentModule
   module OtherModule
     def some_awesome_method
       ..does something
     end
   end  
 end

我试图在我的ParentModule 中获取包含OtherModule 的类列表。这是我到目前为止所拥有的,运行良好:

include_resources = ->(klass) {
        begin
          klass_string = klass.to_s
          klass_resource = klass_string.constantize
          klass_string if klass_resource.included_modules.include?(OtherModule)
        rescue NameError # skip all bad naming and other irrelevant constant loading mishaps
          next
        end
      }

所以如果我执行ParentModule.constants.find_all(&include_resources),我会得到包含OtherModule 的类列表,太好了!但不幸的是,它无法找到嵌套在子模块下的类,如 #2 示例所示。所以我尝试这样做:

include_resources = ->(klass) {
        begin
          klass_string = klass.to_s
          klass_resource = klass_string.constantize
          if klass_resource.class == Module
            return "ParentModule::#{klass_string}".constants.find_all do |module_klass|
              module_klass.constantize.included_modules.include?(OtherModule)
            end
          end
          klass_string if klass_resource.included_modules.include?(OtherModule)
        rescue NameError # skip all bad naming and other irrelevant constant loading mishaps
          next
        end
  } 

不幸的是,这会返回相同的列表。

【问题讨论】:

  • 嗨@maxpleaner 感谢您的输入,但您似乎没有阅读该问题。这个问题不同,因为我有子模块,而链接的问题没有。我从字面上添加了我的代码中的案例,我尝试了什么,什么有效,什么无效。如果这不是您在 SO 上提出问题的方式,我不知道是什么。
  • 没有难过的感觉,我只是想帮忙。如果不是相同的问题,您可以拒绝“可能重复”标志。你可以在你的问题中说“我试过 但它没有用,因为
  • @Mongoid 你能控制OtherModule吗?您多久会查询一次它的包含情况?您是否考虑过Module#included,它可以让模块本身跟踪它的位置?显然更多的内存但检索的开销更少。 (除非您打算对许多模块任意执行此操作,在这种情况下答案绝对合适)
  • 嗨@engineersmnky 是的,我确实可以控制OtherModule。我会经常查询它。这实际上不是一个坏主意。谢谢,我会试一试

标签: ruby-on-rails ruby ruby-on-rails-3 ruby-on-rails-4


【解决方案1】:

[注意:@engineersmnky 说明了一种使用flat_map 实现此目的的方法,无需使用matching_classes 参数。我发现它更难理解,但这是对flat_map 的完美使用,也是一个有价值的解决方案。代码贴在https://repl.it/@engineersmnky/IllinformedMountainousAnkole]

以下代码使用递归来降低模块的倒置树。结果(在最后打印)是正确的,并且包括两个模块中的类。 (我编写了一个最小的模块和类层次结构以用作示例。)

#!/usr/bin/env ruby

module ParentModule
  module OtherModule; end
  class ParentClassYes; include OtherModule; end
  class ParentClassNo;  end

  module ChildModule
    class ChildClassYes; include OtherModule; end
    class ChildClassNo;  end
  end
end


def classes_for_module_tree(the_module, matching_classes = [])
  the_module.constants.each_with_object(matching_classes) \
        do |const, matching_classes|
    value = the_module.const_get(const)
    if value.is_a?(Class)
      if value.included_modules.include?(ParentModule::OtherModule)
        matching_classes << value
      end
    elsif value.is_a?(Module)
      # Here is where we call this method recursively. We suspend collecting
      # matches for this module, call the method to process the newly found
      # (sub)module, then use the array returned by that invocation to resume
      # processing this module.
      matching_classes = classes_for_module_tree(value, matching_classes)
    end
  end
  matching_classes
end


p classes_for_module_tree(ParentModule)
# prints: [ParentModule::ParentClassYes, ParentModule::ChildModule::ChildClassYes]

【讨论】:

  • 有任何理由不只使用ParentModule::OtherModuleeigenclass 来通过Module#included 跟踪它自己的包含物吗?它将避免递归潜水。否则,如果这是一个通用的(我会这样对待它)为什么不允许目标模块的传递(在这种情况下ParentModule::OtherModule
  • 另外flat_mapcompact 将避免传递matching_classes 在我看来不需要成为签名的一部分的需要。 Example
  • 您的意思是每次包含模块时都使用included 更新包含模块的数组,对吗?是的,这是另一种方法。它避免了递归的复杂性,但需要修改您想要此支持的每个模块。是的,对于一般用途,目标模块将被参数化。关于flat_map,你能用代码告诉我们在这种情况下你会如何使用它吗?
  • 我在之前的评论中有一个使用flat_mapcompact 的链接,以避免将matching_classes 作为参数传递
  • 好吧,我只是花了 很多 时间试图弄清楚你是如何使用 flat_map 的,我不得不说,这非常棒。正如您所说,我的方法更容易遵循 IMO,但您的方法消除了对额外收集器参数的需要。赞一个!