Programing

루비에서 클래스의 모든 자손을 찾으십시오.

crosscheck 2020. 6. 22. 07:57
반응형

루비에서 클래스의 모든 자손을 찾으십시오.


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 응용 프로그램의 모든 모델을 찾아서 나열하려고하기 때문에 발생합니다. 그러한 모델에서 작동 할 수있는 컨트롤러가 있고 새 모델을 추가하고 싶습니다. 컨트롤러를 수정할 필요가 없습니다.)


예를 들면 다음과 같습니다.

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을 사용하면 두 가지 옵션이 있습니다. .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]

멋진 체인 반복자가 포함 된 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

루비 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

inherited 라는 클래스 메서드를 재정의하십시오 . 이 메소드는 생성 될 때 추적 할 수있는 서브 클래스로 전달됩니다.


또는 (ruby 1.9+로 업데이트) :

ObjectSpace.each_object(YourRootClass.singleton_class)

루비 1.8 호환 방식 :

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

모듈에서는 작동하지 않습니다. 또한 YourRootClass가 답변에 포함됩니다. Array #-또는 다른 방법으로 제거 할 수 있습니다.


오브젝트 공간의 작품을 사용하지만, 상속 클래스 메소드는 여기에 더 적합한 것 같다 (서브 클래스) 루비 문서 상속

오브젝트 공간은 본질적으로 현재 할당 된 메모리를 사용하는 모든 것에 액세스 할 수있는 방법이므로 Animal 클래스의 하위 요소인지 여부를 확인하기 위해 모든 요소 중 하나를 반복하는 것이 이상적이지 않습니다.

In the code below, the inherited Animal class method implements a callback that will add any newly created subclass to its descendants array.

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

  def self.descendants
    puts @descendants 
  end
end

I know you are asking how to do this in inheritance but you can achieve this with directly in Ruby by name-spacing the class (Class or 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]

It's much faster this way than comparing to every class in ObjectSpace like other solutions propose.

If you seriously need this in a inheritance you can do something like this:

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

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

update

in the end all you have to do is this

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

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

class Foo < DarthVader
end

DarthVader.descendants #=> [Foo]

thank you @saturnflyer for suggestion


(Rails <= 3.0 ) Alternatively you could use ActiveSupport::DescendantsTracker to do the deed. From source:

This module provides an internal implementation to track descendants which is faster than iterating through ObjectSpace.

Since it is modularize nicely, you could just 'cherry-pick' that particular module for your Ruby app.


Ruby Facets has Class#descendants,

require 'facets/class/descendants'

It also supports a generational distance parameter.


A simple version that give an array of all the descendants of a class:

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

You can require 'active_support/core_ext' and use the descendants method. Check out the doc, and give it a shot in IRB or pry. Can be used without Rails.


Rails provides a subclasses method for every object, but it's not well documented, and I don't know where it's defined. It returns an array of class names as strings.


Using descendants_tracker gem may help. The following example is copied from the gem's doc:

class Foo
  extend DescendantsTracker
end

class Bar < Foo
end

Foo.descendants # => [Bar]

This gem is used by the popular virtus gem, so I think it's pretty solid.


This method will return a multidimensional hash of all of an Object's descendants.

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

{ MasterClass => descendants_mapper(MasterClass) }

If you have access to code before any subclass is loaded then you can use inherited method.

If you don't (which is not a case but it might be useful for anyone who found this post) you can just write:

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

Sorry if I missed the syntax but idea should be clear (I don't have access to ruby at this moment).

참고URL : https://stackoverflow.com/questions/2393697/look-up-all-descendants-of-a-class-in-ruby

반응형