试图找到包含模块的所有 类

Trying to find all classes that include a module

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

1) 可能有一个 class X,其定义为:

module ParentModule
   class X
   end
 end

2) 在不同的模块下也可能有一个嵌套的 class :

module ParentModule
   module ChildModule
     class Y
     end
   end
 end

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

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

我正在尝试在我的 ParentModule 中获取包含 OtherModule 的 classes 的列表。这是我目前所拥有的,运行良好:

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 的 classes 的列表,太棒了!但不幸的是,它无法找到嵌套在子模块下的 class,如 #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
  } 

不幸的是,这个 returns 是同一个列表。

[注意:@engineersmnky 说明了一种使用 flat_map 消除对 matching_classes 参数的需要的方法。我发现它更难理解,但它是 flat_map 的完美用法和有价值的解决方案。代码发布在 https://repl.it/@engineersmnky/IllinformedMountainousAnkole ]

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

#!/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]