在Ruby中查找一个类的所有后代

我可以很容易地提升Ruby中的类层次结构:

String.ancestors # [String, Enumerable, Comparable, Object, Kernel] Enumerable.ancestors # [Enumerable] Comparable.ancestors # [Comparable] Object.ancestors # [Object, Kernel] Kernel.ancestors # [Kernel] 

有没有办法降低层次结构呢? 我想这样做

 Animal.descendants # [Dog, Cat, Human, ...] Dog.descendants # [Labrador, GreatDane, Airedale, ...] Enumerable.descendants # [String, Array, ...] 

但似乎没有一个descendants方法。

(这个问题的出现是因为我想在一个Rails应用程序中find所有的模型,它们从一个基类inheritance而来,列出它们;我有一个控制器可以处理任何这样的模型,我希望能够添加新的模型而不必修改控制器。)

这里是一个例子:

 class Parent def self.descendants ObjectSpace.each_object(Class).select { |klass| klass < self } end end class Child < Parent end class GrandChild < Child end puts Parent.descendants puts Child.descendants 

把Parent.descendants给你:

 GrandChild Child 

把Child.descendants给你:

 GrandChild 

如果你使用Rails> = 3,你有两个选项。 如果您想要多于一个级别的子级别,则使用.subclasses ,或者.subclasses级别的第一级别使用.subclasses

例:

 class Animal end class Mammal < Animal end class Dog < Mammal end class Fish < Animal end Animal.subclasses #=> [Mammal, Fish] Animal.descendants #=> [Dog, Mammal, Fish] 

Ruby 1.9(或1.8.7)与漂亮的链式迭代器:

 #!/usr/bin/env ruby1.9 class Class def descendants ObjectSpace.each_object(::Class).select {|klass| klass < self } end end 

ruby1.8.7之前:

 #!/usr/bin/env ruby class Class def descendants result = [] ObjectSpace.each_object(::Class) {|klass| result << klass if klass < self } result end end 

像这样使用它:

 #!/usr/bin/env ruby p Animal.descendants 

覆盖名为inherited的类方法。 这个方法在被创build的时候会被传递给子类,你可以跟踪它。

另外(更新ruby1.9 +):

 ObjectSpace.each_object(YourRootClass.singleton_class) 

Ruby 1.8兼容的方式:

 ObjectSpace.each_object(class<<YourRootClass;self;end) 

请注意,这不适用于模块。 另外,YourRootClass将被包含在答案中。 您可以使用Array#或其他方式删除它。

虽然使用ObjectSpace的作品,inheritance的类方法似乎更适合在这里inheritance(子类)的Ruby文档

对象空间本质上是一种访问当前正在使用分配内存的任何东西的方法,因此遍历每一个元素来检查它是否是Animal类的子类是不理想的。

在下面的代码中,inheritance的Animal类方法实现了一个callback函数,它会将任何新创build的子类添加到它的后代数组中。

 class Animal def self.inherited(subclass) @descendants = [] @descendants << subclass end def self.descendants puts @descendants end end 

Ruby Facets有Class#子孙,

 require 'facets/class/descendants' 

它也支持代数距离参数。

我知道你问的是如何在inheritance中做到这一点,但你可以直接在Ruby中通过名称来实现这一点 – 间隔类( ClassModule

 module DarthVader module DarkForce end BlowUpDeathStar = Class.new(StandardError) class Luck end class Lea end end DarthVader.constants # => [:DarkForce, :BlowUpDeathStar, :Luck, :Lea] DarthVader .constants .map { |class_symbol| DarthVader.const_get(class_symbol) } .select { |c| !c.ancestors.include?(StandardError) && c.class != Module } # => [DarthVader::Luck, DarthVader::Lea] 

比起其他解决scheme中的ObjectSpace每个类,这种方法要快得多。

如果你真的需要这个inheritance,你可以这样做:

 class DarthVader def self.descendants DarthVader .constants .map { |class_symbol| DarthVader.const_get(class_symbol) } end class Luck < DarthVader # ... end class Lea < DarthVader # ... end def force 'May the Force be with you' end end 

基准: http : //www.eq8.eu/blogs/13-ruby-ancestors-descendants-and-other-annoying-relatives

更新

最后你所要做的就是这个

 class DarthVader def self.inherited(klass) @descendants ||= [] @descendants << klass end def self.descendants @descendants || [] end end class Foo < DarthVader end DarthVader.descendants #=> [Foo] 

谢谢@saturnflyer的build议

(Rails <= 3.0)或者,您可以使用ActiveSupport :: DescendantsTracker来完成契约。 来源:

这个模块提供了一个内部的实现来跟踪后代,这比迭代ObjectSpace更快。

由于它很好地模块化,您可以为您的Ruby应用程序“挑选”该特定模块。

您可以require 'active_support/core_ext'并使用descendants方法。 查看文档 ,并在IRB或pry中进行一下。 可以使用没有Rails。

给出一个类的所有后代数组的简单版本:

 def descendants(klass) all_classes = klass.subclasses (all_classes + all_classes.map { |c| descendants(c) }.reject(&:empty?)).flatten end 

Rails为每个对象提供了一个子类方法,但是没有很好的logging,我不知道它在哪里定义。 它返回一个类名string数组。

使用descendants_tracker gem可能会有帮助。 以下示例是从gem的文档复制的:

 class Foo extend DescendantsTracker end class Bar < Foo end Foo.descendants # => [Bar] 

这个gem被stream行的德尔宝所使用 ,所以我觉得它非常稳固。

这个方法将返回所有Object的后代的多维散列。

 def descendants_mapper(klass) klass.subclasses.reduce({}){ |memo, subclass| memo[subclass] = descendants_mapper(subclass); memo } end { MasterClass => descendants_mapper(MasterClass) } 

如果您加载任何子类之前有权访问代码则可以使用inheritance的方法。

如果你不这样做(这不是一个例子,但是对任何发现这个post的人都有用),你可以写下:

 x = {} ObjectSpace.each_object(Class) do |klass| x[klass.superclass] ||= [] x[klass.superclass].push klass end x[String] 

对不起,如果我错过了语法,但想法应该是明确的(我目前没有访问ruby)。