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


144

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

답변:


146

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

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
감사합니다! 나는 당신이 밀리 초를 면도하려고한다면 모든 수업을 방문하는 것이 너무 느릴 것이라고 생각하지만, 그것은 나에게 완전히 빠릅니다.
Douglas Squirrel

1
singleton_classClass훨씬 빠르게 만드는 대신 ( apidock.com/rails/Class/descendants의 출처를 보십시오 )
brauliobo

21
클래스가 메모리에 아직로드되지 않은 상황이있을 경우 조심 ObjectSpace하십시오.
Edmund Lee

어떻게이 작품을 만들 수 ObjectBasicObject호기심은 표시 알고?는
아몰 Pujari

@AmolPujari p ObjectSpace.each_object(Class)는 모든 클래스를 인쇄합니다. self메소드의 코드 라인에서 이름을 대체하여 원하는 클래스의 자손을 얻을 수도 있습니다 .
BobRodes

62

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]

6
개발 과정에서 로딩을 끄는 경우 이러한 메소드는 클래스가로드 된 경우 (예 : 실행중인 서버에서 이미 참조한 경우)에만 클래스를 반환합니다.
stephen.hanson

1
@ stephen.hanson 정확한 결과를 보장하는 가장 안전한 방법은 무엇입니까?
크리스 에드워즈

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

루비 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
이것은 모듈에서도 작동합니다. 코드에서 "Class"의 두 인스턴스를 "Module"로 바꾸십시오.
korinthe

2
안전성을 ObjectSpace.each_object(::Class)높이려면 YourModule :: Class가 정의되어있을 때 코드가 계속 작동해야합니다.
르네 자르 수

19

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


나도 이것을 좋아한다. 메서드를 재정의하는 것은 거의 방해가되지 않지만 모든 클래스를 방문 할 필요가 없으므로 하위 메서드를 조금 더 효율적으로 만듭니다.
Douglas Squirrel

@Douglas 덜 방해가되지는 않지만, 요구 사항을 충족하는지 (예 : Rails가 컨트롤러 / 모델 계층을 언제 구축합니까?) 실험해야 할 것입니다.
Josh Lee

또한 MRI 이외의 다양한 루비 구현에보다 이식성이 뛰어나며, 그 중 일부는 ObjectSpace 사용으로 인해 성능 오버 헤드가 심각합니다. Class # inherited는 Ruby에서 "자동 등록"패턴을 구현하는 데 이상적입니다.
John Whitley

예를 공유 하시겠습니까? 클래스 수준이기 때문에 각 클래스를 일종의 전역 변수에 저장해야한다고 생각합니까?
노즈

@Noz No, 클래스 자체의 인스턴스 변수. 그러나 GC는 개체를 수집 할 수 없습니다.
Phrogz

13

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

ObjectSpace.each_object(YourRootClass.singleton_class)

루비 1.8 호환 방식 :

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

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


그것은 굉장했다. 어떻게 작동하는지 설명해 주시겠습니까? 내가 사용ObjectSpace.each_object(class<<MyClass;self;end) {|it| puts it}
David West

1
루비 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 2016 년

10

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

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

아래 코드에서 상속 된 Animal 클래스 메서드는 새로 만든 하위 클래스를 하위 배열에 추가하는 콜백을 구현합니다.

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

  def self.descendants
    puts @descendants 
  end
end

6
`@descendants || = []`그렇지 않으면 당신은 마지막 자손을 얻게 될 것입니다
Axel Tetzlaff

4

상속 에서이 작업을 수행하는 방법을 묻고 있지만 루비에서 직접 이름을 지정하여 클래스의 이름을 지정 하여이 작업을 수행 할 수 있습니다 ( 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


3

(레일 <= 3.0) 또는 ActiveSupport :: DescendantsTracker를 사용하여 작업을 수행 할 수 있습니다. 출처에서 :

이 모듈은 ObjectSpace를 반복하는 것보다 빠른 하위 항목을 추적하기위한 내부 구현을 제공합니다.

모듈화가 잘되어 있기 때문에 루비 앱을위한 특정 모듈을 '체리-픽'할 수 있습니다.


2

Ruby Facets에는 Class # descendants가 있습니다.

require 'facets/class/descendants'

또한 생성 거리 매개 변수도 지원합니다.


2

클래스의 모든 자손 배열을 제공하는 간단한 버전 :

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

1
이것은 탁월한 답변처럼 보입니다. 불행히도 여전히 클래스의 게으른 로딩에 빠져 있습니다. 그러나 나는 그들 모두를 생각합니다.
Dave Morse

@DaveMorse 파일을 나열하고 상수를 수동으로로드하여 하위 항목으로 등록하도록했습니다 (그리고이 모든 것을 제거했습니다 : D)
Dorian

1
이는 #subclassesRails ActiveSupport에서 제공 한 것입니다.
Alex D

1

방법을 require 'active_support/core_ext'사용할 수 있습니다 descendants. 문서를 확인하고 IRB 또는 pry로 샷을 제공하십시오. 레일없이 사용할 수 있습니다.


1
Gemfile에 적극적인 지원을 추가해야한다면 실제로 "레일없이"있는 것은 아닙니다. 원하는 레일을 선택하는 것입니다.
Caleb

1
이것은 여기의 주제와 관련이없는 철학적 접선처럼 보이지만 Rails 구성 요소를 사용하는 것이 반드시 using Rails전체 론적 의미 임을 의미한다고 생각합니다 .
thelostspore

0

Rails는 모든 객체에 대해 서브 클래스 메소드를 제공하지만 잘 문서화되어 있지 않으며 정의 된 위치를 모르겠습니다. 클래스 이름 배열을 문자열로 반환합니다.


0

822ants_tracker gem을 사용하면 도움이 될 수 있습니다. 다음 예제는 gem의 문서에서 복사되었습니다.

class Foo
  extend DescendantsTracker
end

class Bar < Foo
end

Foo.descendants # => [Bar]

이 보석은 인기있는 virtus 보석에 의해 사용 되므로 꽤 견고하다고 생각합니다.


0

이 메소드는 객체의 모든 자손에 대한 다차원 해시를 반환합니다.

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

{ MasterClass => descendants_mapper(MasterClass) }

-1

서브 클래스가로드 되기 전에 코드에 액세스 할 수 있으면 상속 된 메소드를 사용할 수 있습니다 .

그렇지 않은 경우 (이 경우는 아니지만이 게시물을 찾은 사람에게 유용 할 수 있음) 다음과 같이 쓸 수 있습니다.

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

구문을 놓치면 죄송하지만 아이디어가 분명해야합니다 (현재로서는 루비에 액세스 할 수 없습니다).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.