Ruby'de bir sınıfın tüm torunlarına bakın


144

Ruby'deki sınıf hiyerarşisine kolayca yükselebilirim:

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

Hiyerarşiyi de azaltmanın bir yolu var mı? Bunu yapmak isterim

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

ama bir descendantsyöntem yok gibi görünüyor .

(Bu soru, bir Rails uygulamasında bir temel sınıftan inen ve bunları listeleyen tüm modelleri bulmak ve bu tür herhangi bir modelle çalışabilecek bir denetleyicim olması ve yeni modeller eklemek istiyorum diye ortaya çıkıyor. denetleyiciyi değiştirmek zorunda kalmadan.)

Yanıtlar:


146

İşte bir örnek:

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 size şunları verir:

GrandChild
Child

Child.descendants size verir:

GrandChild

1
Harika çalışıyor, teşekkürler! Milisaniyelik tıraş olmaya çalışıyorsanız, her sınıfı ziyaret etmek çok yavaş olabilir, ancak benim için çok hızlı.
Douglas Sincap

1
singleton_classClassçok daha hızlı yapmak yerine ( apidock.com/rails/Class/descendants adresindeki kaynağa bakın )
brauliobo

21
Sınıfın henüz belleğe yüklenmediği bir durum varsa ObjectSpace, buna sahip olmayacağınıza dikkat edin.
Edmund Lee

Nasıl için bu işi yapabilir Objectve BasicObjectmeraklı Onlar gelene kadar bilmek?
Amol Pujari

@ AmolPujari p ObjectSpace.each_object(Class)tüm sınıfları basacaktır. Ayrıca self, yöntemdeki kod satırında adını değiştirerek istediğiniz herhangi bir sınıfın torunlarını da alabilirsiniz .
BobRodes

62

Rails> = 3 kullanıyorsanız, iki seçeneğiniz vardır. Kullanım .descendantsEğer çocuk sınıfların birden fazla seviye derinliğini istiyorsanız veya kullanım.subclasses çocuk sınıflarının birinci seviye için.

Misal:

class Animal
end

class Mammal < Animal
end

class Dog < Mammal
end

class Fish < Animal
end

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

6
Geliştirme sırasında, istekli yüklemeyi kapattıysanız, bu yöntemlerin yalnızca yüklendiyse (yani çalışan sunucu tarafından zaten başvurulmuşsa) sınıfları döndüreceğini unutmayın.
stephen.hanson

1
@ stephen.hanson Burada doğru sonuçları garanti etmenin en güvenli yolu nedir?
Chris Edwards

26

Şık zincirleme yineleyicileri olan Ruby 1.9 (veya 1.8.7):

#!/usr/bin/env ruby1.9

class Class
  def descendants
    ObjectSpace.each_object(::Class).select {|klass| klass < self }
  end
end

Yakut 1.8.7 öncesi:

#!/usr/bin/env ruby

class Class
  def descendants
    result = []
    ObjectSpace.each_object(::Class) {|klass| result << klass if klass < self }
    result
  end
end

Şöyle kullanın:

#!/usr/bin/env ruby

p Animal.descendants

3
Bu Modüller için de geçerlidir; "Sınıf" ın her iki örneğini de koddaki "Modül" ile değiştirin.
korinthe

2
Ekstra güvenlik için yazmanız gerekir; ObjectSpace.each_object(::Class)bu, bir YourModule :: Sınıfınız tanımlandığında kodun çalışmaya devam etmesini sağlar.
Rene Saarsoo

19

Miras adlı sınıf yöntemini geçersiz kıl . Bu yöntem, izleyebileceğiniz oluşturulduğunda alt sınıfa geçirilir.


Bunu da beğendim. Yöntemin geçersiz kılınması marjinal olarak müdahalecidir, ancak her sınıfı ziyaret etmek zorunda olmadığınız için soyundan gelen yöntemi biraz daha verimli hale getirir.
Douglas Sincap

@Douglas Daha az müdahaleci olsa da, muhtemelen ihtiyaçlarınızı karşılayıp karşılamadığını denemeniz gerekecektir (örneğin, Rails denetleyici / model hiyerarşisini ne zaman oluşturur?).
Josh Lee

Ayrıca, bazıları ObjectSpace kullanımından kaynaklanan ciddi performans yükü olan çeşitli MRI olmayan yakut uygulamaları için daha taşınabilir. Class # miras, Ruby'de "otomatik kayıt" kalıplarını uygulamak için idealdir.
John Whitley

Bir örnek paylaşmak ister misiniz? Sınıf seviyesi olduğundan, her sınıfı bir çeşit küresel değişkente saklamanız gerekeceğini tahmin ediyorum.
Noz

@Noz Hayır, sınıfın kendisinde bir örnek değişken. Ancak nesneler GC tarafından toplanamaz.
Phrogz

13

Alternatif olarak (ruby 1.9+ için güncellendi):

ObjectSpace.each_object(YourRootClass.singleton_class)

Ruby 1.8 uyumlu yol:

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

Bunun modüller için çalışmadığını unutmayın. Ayrıca cevabınıza YourRootClass da dahil edilecektir. Dizi # - veya onu kaldırmak için başka bir yol kullanabilirsiniz.


bu harikaydı. Bunun nasıl çalıştığını bana açıklayabilir misin? KullandımObjectSpace.each_object(class<<MyClass;self;end) {|it| puts it}
David West

1
Ruby 1.8'de, class<<some_obj;self;endbir nesnenin singleton_class değerini döndürür. 1.9 + 'da kullanabilirsiniz some_obj.singleton_class(bunu yansıtmak için cevabımı güncelledi). Her nesne, sınıflar için de geçerli olan singleton_class öğesinin bir örneğidir. Each_object (SomeClass), SomeClass öğesinin tüm örneklerini döndürdüğünden ve SomeClass SomeClass.singleton_class öğesinin bir örneği olduğundan, each_object (SomeClass.singleton_class) SomeClass ve tüm alt sınıfları döndürür.
apeiros

10

ObjectSpace'i kullanmaya rağmen, devralınan sınıf yöntemi burada devralınan (alt sınıf) Ruby belgelerine daha uygun görünüyor

Nesne alanı esasen tahsis edilmiş belleği kullanan her şeye ve her şeye erişmenin bir yoludur, bu nedenle Animal sınıfının bir alt sınıfı olup olmadığını kontrol etmek için öğelerinin her birini yinelemek ideal değildir.

Aşağıdaki kodda, devralınan Animal sınıfı yöntemi, yeni oluşturulan alt sınıfları onun soy dizisine ekleyecek bir geri arama uygular.

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

  def self.descendants
    puts @descendants 
  end
end

6
`@descendants || = []` aksi takdirde yalnızca son torun sahibi olacaksınız
Axel Tetzlaff

4

Bunu miras olarak nasıl yapacağınızı sorduğunuzu biliyorum, ancak bunu sınıfta ad aralıklarıyla doğrudan Ruby'de elde edebilirsiniz ( Classveya 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]

Bu yoldaki her sınıfa kıyasla çok daha hızlı ObjectSpaceDiğer çözümlerin önerdiği gibi .

Buna ciddi bir mirasa ihtiyacınız varsa, böyle bir şey yapabilirsiniz:

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

karşılaştırmalar burada: http://www.eq8.eu/blogs/13-ruby-ancestors-descendants-and-other-annoying-relatives

Güncelleme

sonunda tek yapman gereken bu

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

  def self.descendants
    @descendants || []
  end
end

class Foo < DarthVader
end

DarthVader.descendants #=> [Foo]

öneri için teşekkür ederim @saturnflyer


3

(Raylar <= 3.0) Alternatif olarak senet yapmak için ActiveSupport :: DescendantsTracker kullanabilirsiniz. Kaynak:

Bu modül, ObjectSpace üzerinden yinelemekten daha hızlı olan torunları izlemek için dahili bir uygulama sağlar.

Güzel bir şekilde modüler hale getirildiğinden, Ruby uygulamanız için belirli bir modülü 'kirazla' seçebilirsiniz.


2

Ruby Facets Sınıf # torunlarına sahiptir,

require 'facets/class/descendants'

Ayrıca bir kuşak mesafe parametresini de destekler.


2

Bir sınıfın tüm torunlarının bir dizisini veren basit bir sürüm:

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

1
Bu üstün bir cevap gibi görünüyor. Ne yazık ki hala derslerin tembel yüklenmesine avlanıyor. Ama sanırım hepsi yapıyor.
Dave Morse

@DaveMorse Dosyaları listelemeye ve sabitleri, torun olarak kaydettirmek için manuel olarak yükledim (ve sonra tüm bunları kaldırarak sonlandırdım: D)
Dorian

1
Bunun #subclassesRails ActiveSupport'tan olduğunu unutmayın.
Alex D

1

Sen edebilirsiniz require 'active_support/core_ext'kullanmak ve descendantsyöntem. Dokümanı kontrol edin ve IRB veya gözetleme ile deneyin. Raysız kullanılabilir.


1
Gemfile'ınıza aktif destek eklemeniz gerekiyorsa, bu gerçekten "raysız" değildir. Sadece beğendiğin ray parçalarını seçiyor.
Caleb

1
Bu, buradaki konuyla ilgili olmayan felsefi bir teğet gibi görünüyor, ancak bir Rails bileşeni kullanmanın zorunlu olarak using Railsbütünsel bir anlamda olduğu anlamına geldiğini düşünüyorum .
thelostspore

0

Rails, her nesne için bir alt sınıf yöntemi sağlar, ancak iyi belgelenmemiştir ve nerede tanımlandığını bilmiyorum. Dizeler olarak bir sınıf adları dizisi döndürür.


0

Kullanılması descendants_tracker mücevher yardımcı olabilir. Aşağıdaki örnek mücevher belgesinden kopyalanmıştır:

class Foo
  extend DescendantsTracker
end

class Bar < Foo
end

Foo.descendants # => [Bar]

Bu taş popüler virtus mücevher tarafından kullanılır , bu yüzden oldukça sağlam olduğunu düşünüyorum.


0

Bu yöntem, bir Nesnenin torunlarının çok boyutlu bir karmasını döndürür.

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

{ MasterClass => descendants_mapper(MasterClass) }

-1

Herhangi bir alt sınıf yüklenmeden önce koda erişiminiz varsa, devralınan yöntemi kullanabilirsiniz .

Yapmazsanız (bu bir durum değildir, ancak bu yayını bulan herkes için yararlı olabilir) yazabilirsiniz:

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

Sözdizimini kaçırırsam özür dilerim ama fikir net olmalı (şu anda ruby'ye erişimim yok).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.