2010-03-06 16 views
114

我可以很容易地提升在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應用程序中的所有模型,它們從基類中下來並列出它們;我有一個可以與任何這樣的模型一起工作的控制器,我希望能夠以添加新型號而無需修改控制器。)

回答

117

下面是一個例子:

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 
+1

這很好,謝謝!我想如果你想減少毫秒,訪問每個類可能會太慢,但對我而言,這是非常快速的。 – 2010-03-06 20:01:34

+0

'singleton_class'而不是'Class'使其更快(請參閱http://apidock.com/rails/Class/descendants處的源代碼) – brauliobo 2015-05-09 00:41:44

+13

如果您可能遇到類未被加載到內存中的情況,請小心,'ObjectSpace'不會有。 – 2016-04-05 02:01:45

15

重寫名爲inherited的類方法。這個方法在被創建時可以通過子類,你可以跟蹤它。

+0

我喜歡這一個了。覆蓋該方法有點侵擾性,但它使得後代方法更高效一些,因爲您不必訪問每個類。 – 2010-03-06 20:00:28

+0

@Douglas雖然它不那麼具有侵入性,但您可能需要試驗以確定它是否符合您的需求(即Rails何時構建控制器/模型層次結構?)。 – 2010-03-06 20:25:53

+0

這對於各種非MRI ruby​​實現來說也更具移植性,其中一些實現會由於使用ObjectSpace而造成嚴重的性能開銷。繼承的Class#非常適合在Ruby中實現「自動註冊」模式。 – 2013-01-23 22:49:53

-1

如果您在之前有權訪問之前的任何子類,那麼您可以使用inherited方法。

如果不這樣做(這不是個例,但它可能是任何人誰發現這個職位很有用),你可以這樣寫:

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

很抱歉,如果我錯過了語法,但思路應該是清楚的(我目前無法使用ruby)。

24

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 

紅寶石預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

這也適用於模塊;只需在代碼中將「Class」的兩個實例替換爲「Module」即可。 – korinthe 2011-05-15 16:52:53

+2

爲了更加安全,應該編寫'ObjectSpace.each_object(:: Class)' - 當你碰巧定義了一個YourModule :: Class時,這將保持代碼正常工作。 – 2011-10-03 10:42:42

9

或者(更新的紅寶石1.9+):

ObjectSpace.each_object(YourRootClass.singleton_class) 

紅寶石1.8兼容方式:

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

請注意,這不適用於模塊。此外,YourRootClass將包含在答案中。您可以使用Array#或其他方式將其刪除。

+0

太棒了。你能向我解釋那是如何工作的嗎?我使用了ObjectSpace.each_object(class << MyClass; self; end){| it |放在它}' – 2014-06-03 13:22:37

+1

在ruby 1.8中,'class << some_obj; self; end'返回一個對象的singleton_class。在1.9+中,你可以使用'some_obj.singleton_class'來代替(更新我的答案以反映這一點)。每個對象都是它的singleton_class的一個實例,它也適用於類。由於each_object(SomeClass)返回SomeClass的所有實例,SomeClass是SomeClass.singleton_class的實例,因此each_object(SomeClass.singleton_class)將返回SomeClass和所有子類。 – apeiros 2014-06-04 07:31:59

1

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

該模塊提供了一個內部實現來追蹤後代,這比通過ObjectSpace迭代更快。

由於它很好地模塊化,您可以爲您的Ruby應用程序「挑選」該特定模塊。

0

Rails爲每個對象提供了一個子類方法,但它沒有很好的記錄,我不知道它在哪裏定義。它返回一個類名字符串數組。

2

紅寶石刻面具有類#後代,

require 'facets/class/descendants' 

它還支持代距離參數。

44

如果您使用Rails> = 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] 
1

您可以require 'active_support/core_ext'並使用descendants方法。 Check out the doc,並在IRB或pry中給它一個鏡頭。可以使用沒有Rails。

+1

如果您必須爲您的Gemfile添加主動支持,那麼它不是真的「沒有導軌」。這只是選擇你喜歡的鋼軌。 – Caleb 2015-02-11 22:35:47

+1

這看起來像是一個與這裏的主題無關的哲學切線,但我認爲使用Rails組件確實意味着「整體使用Rails」。 – thelostspore 2015-02-12 23:52:03

2

我知道你是問如何在繼承做到這一點,但你可以直接在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的建議

0

使用descendants_tracker gem可能會有幫助。下面的例子是從寶石的文檔複製:

class Foo 
    extend DescendantsTracker 
end 

class Bar < Foo 
end 

Foo.descendants # => [Bar] 

這種寶石使用的流行virtus gem,所以我認爲這是非常堅實的。

4

雖然使用對象空間的作品,繼承類方法似乎更適合這裏inherited(subclass) Ruby documentation

對象空間基本上是訪問,因此目前使用分配的內存予取予求的方式,所以迭代它的元素,以每一個檢查它是否是Animal類的一個子類是不理想的。

在下面的代碼中,繼承的Animal類方法實現了一個回調函數,它將任何新創建的子類添加到它的後代數組中。

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

    def self.descendants 
    puts @descendants 
    end 
end 
+0

'@descendants || = []'否則你只會得到最後的後裔 – 2017-05-29 10:28:54

0

該方法將返回所有對象後代的多維散列。

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

{ MasterClass => descendants_mapper(MasterClass) } 
1

一個簡單的版本,讓一個班級的所有後代組成的數組:

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

這看起來像一個優越的答案。不幸的是,它仍然是延遲加載類的犧牲品。但我認爲他們都這樣做。 – 2017-04-13 23:20:19

+0

@DaveMorse我最終列出了文件,並手動加載常量讓它們註冊爲後代(然後最終刪除了這個事情:D) – Dorian 2017-04-13 23:21:43

相關問題