在Ruby中查找类的所有后代

时间:2010-03-06 18:47:27

标签: 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应用程序中的所有模型,这些模型来自基类并列出它们;我有一个控制器可以使用任何这样的模型,我希望能够添加新模型而无需修改控制器。)

17 个答案:

答案 0 :(得分:137)

以下是一个例子:

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

puts Parent.descendants为您提供:

GrandChild
Child

使Child.descendants给你:

GrandChild

答案 1 :(得分:55)

如果您使用Rails&gt; = 3,则有两个选项。如果您想要多个级别的子类,请使用.descendants,或者对第一级子类使用.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]

答案 2 :(得分:26)

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

Ruby pre-1.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

答案 3 :(得分:19)

重写名为inherited的类方法。创建子类时,此方法将被传递给子类,您可以跟踪它。

答案 4 :(得分:12)

或者(为ruby 1.9 +更新):

ObjectSpace.each_object(YourRootClass.singleton_class)

Ruby 1.8兼容方式:

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

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

答案 5 :(得分:7)

虽然使用ObjectSpace可行,但继承的类方法似乎更适合inherited(subclass) Ruby documentation

Objectspace本质上是一种访问当前使用已分配内存的任何东西和所有东西的方法,因此迭代它的每一个元素以检查它是否是Animal类的子类并不理想。

在下面的代码中,继承的Animal类方法实现了一个回调,它将新创建的子类添加到其后代数组中。

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

  def self.descendants
    puts @descendants 
  end
end

答案 6 :(得分:3)

(Rails&lt; = 3.0)或者您也可以使用ActiveSupport :: DescendantsTracker来完成契约。 来自:

  

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

由于它模块化很好,你可以为你的Ruby应用程序'挑选'那个特定的模块。

答案 7 :(得分:3)

我知道你是在询问如何在继承中这样做但你可以直接在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]

与其他解决方案提出的ObjectSpace中的每个班级相比,这种方式要快得多。

如果您在继承中非常需要它,您可以执行以下操作:

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的建议

答案 8 :(得分:2)

Ruby Facets有Class#descendants,

require 'facets/class/descendants'

它还支持世代距离参数。

答案 9 :(得分:2)

一个简单的版本,它提供了一个类的所有后代的数组:

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

答案 10 :(得分:1)

基于其他答案(尤其是推荐subclassesdescendants的答案),您可能会发现在Rails.env.development中,事情变得令人困惑。这是由于开发中的紧急加载已关闭(默认情况下)。

如果您在rails console中四处游荡,则只需命名该类即可,它将被加载。从那时起,它将显示在subclasses中。

在某些情况下,您可能需要强制在代码中加载类。对于单表继承(STI)尤其如此,您的代码很少直接提及子类。我遇到一种或两种情况,必须迭代所有STI子类...在开发中效果不佳。

这是我的黑客,只加载那些类,仅用于开发:

if Rails.env.development?
  ## These are required for STI and subclasses() to eager load in development:
  require_dependency Rails.root.join('app', 'models', 'color', 'green.rb')
  require_dependency Rails.root.join('app', 'models', 'color', 'blue.rb')
  require_dependency Rails.root.join('app', 'models', 'color', 'yellow.rb')
end

之后,子类将按预期工作:

> Color.subclasses
=> [Color::Green, Color::Blue, Color::Yellow]

请注意,在生产中这不是必需的,因为所有类都渴望预先加载。

是的,这里有各种各样的代码味道。接受它还是保留它……它使您可以在开发时就省去繁重的工作,同时仍然可以进行动态的类操作。一旦投入生产,这不会对性能产生影响。

答案 11 :(得分:1)

您可以require 'active_support/core_ext'并使用descendants方法。 Check out the doc,并在IRB或pry中试一试。可以在没有Rails的情况下使用。

答案 12 :(得分:0)

计算任意类别的传递性船体

def descendants(parent: Object)
     outSet = []
     lastLength = 0
     
     outSet = ObjectSpace.each_object(Class).select { |child| child < parent }
     
     return if outSet.empty?
     
     while outSet.length == last_length
       temp = []
       last_length = outSet.length()
       
       outSet.each do |parent|
        temp = ObjectSpace.each_object(Class).select { |child| child < parent }
       end
       
       outSet.concat temp
       outSet.uniq
       temp = nil
     end
     outSet
     end
   end

答案 13 :(得分:0)

此方法将返回所有Object的后代的多维哈希。

CLIPS> 
(defclass a
  (is-a USER)
  (role abstract)
  (slot column1))
CLIPS>    
(defclass b
  (is-a a) 
  (role concrete)
  (multislot column2))
CLIPS> 
(definstances myobjects
  (object1 of b (column1 A) (column2 B C)))
CLIPS> (reset)
CLIPS> (instances)
[initial-object] of INITIAL-OBJECT
[object1] of b
For a total of 2 instances.
CLIPS> 

答案 14 :(得分:0)

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

class Foo
  extend DescendantsTracker
end

class Bar < Foo
end

Foo.descendants # => [Bar]

这个宝石被流行的virtus gem使用,所以我觉得它非常稳固。

答案 15 :(得分:0)

Rails为每个对象提供了一个子类方法,但它没有很好地记录,我不知道它在哪里定义。它返回一个类名数组作为字符串。

答案 16 :(得分:-1)

如果您在加载任何子类之前有权访问代码,那么您可以使用inherited方法。

如果你不这样做(这不是一个案例,但对于发现这篇文章的人可能有用),你可以写一下:

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

很抱歉,如果我错过了语法,但想法应该清楚(此时我无法访问ruby)。