我可以轻松地提升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应用程序中的所有模型,这些模型来自基类并列出它们;我有一个控制器可以使用任何这样的模型,我希望能够添加新模型而无需修改控制器。)
答案 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中通过名称来隔离类(Class
或Module
)来实现这一点
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)
基于其他答案(尤其是推荐subclasses
和descendants
的答案),您可能会发现在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)。