programing

Ruby에서 클래스의 모든 하위 항목 검색

oldcodes 2023. 6. 3. 08:43
반응형

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 응용 프로그램에서 찾아 나열하기 때문에 이 문제가 발생합니다. 이러한 모델과 함께 작동할 수 있는 컨트롤러가 있으므로 컨트롤러를 수정하지 않고도 새 모델을 추가할 수 있습니다.)

다음은 예입니다.

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

putsParent.descendent는 다음을 제공합니다.

GrandChild
Child

putsChild.descendents는 다음을 제공합니다.

GrandChild

Rails >= 3을 사용하는 경우 두 가지 옵션이 있습니다.둘 이상의 하위 클래스 수준 깊이를 원하는 경우 사용하거나 첫 번째 수준의 하위 클래스에 사용합니다.

예:

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)(Nifthy Chained 반복기 포함):

#!/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

상속된 이름의 클래스 메서드를 재정의합니다.이 메서드는 추적할 수 있는 하위 클래스가 생성될 때 전달됩니다.

또는 (Ruby 1.9+용으로 업데이트됨):

ObjectSpace.each_object(YourRootClass.singleton_class)

Ruby 1.8 호환 방법:

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

모듈에서는 작동하지 않습니다.또한 귀하의 루트 클래스가 답변에 포함됩니다.Array#-를 사용하거나 다른 방법으로 제거할 수 있습니다.

ObjectSpace를 사용하면 작동하지만 상속된 클래스 메서드가 상속된(하위 클래스) 루비 문서에 더 적합한 것 같습니다.

객체 공간은 기본적으로 할당된 메모리를 사용하는 모든 것과 모든 것에 액세스할 수 있는 방법이므로 모든 요소에 대해 반복적으로 객체가 동물 클래스의 하위 클래스인지 확인하는 것은 이상적이지 않습니다.

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

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

  def self.descendants
    puts @descendants 
  end
end

에서 이것을 할 수 . 하지만 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]

제안해 주셔서 @preflyer 감사합니다.

(Rails <= 3.0) 대신 ActiveSupport를 사용할 수 있습니다.: 행위를 하는 후손 추적기.원본:

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

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

클래스의 모든 하위 항목 배열을 제공하는 단순 버전:

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

Ruby Facets에는 Class # 하위 항목이 있습니다.

require 'facets/class/descendants'

또한 세대 거리 매개 변수도 지원합니다.

클래스 # 하위 클래스(루비 3.1+)

Ruby 3.1부터는 Class# 서브클래스라는 내장 메서드가 있습니다.

싱글톤 클래스를 제외하고 수신기가 클래스의 직접 수퍼 클래스인 클래스 배열을 반환합니다.

따라서 ActiveSupport를 사용하거나 원숭이 패치를 작성할 필요가 없습니다.

class A; end
class B < A; end
class C < B; end
class D < A; end

A.subclasses        #=> [D, B]
B.subclasses        #=> [C]
C.subclasses        #=> []

출처:

레일즈는 모든 객체에 대해 하위 클래스 메소드를 제공하지만, 문서화되어 있지 않고 정의된 위치를 알 수 없습니다.클래스 이름 배열을 문자열로 반환합니다.

넌 할 수 있다.require 'active_support/core_ext'다음을 사용합니다.descendants방법.의사를 확인하고 IRB나 캐묻기를 시도해보세요.레일 없이 사용할 수 있습니다.

다른 답변(특히 권장되는 답변)을 기반으로 합니다.subclasses그리고.descendants), 할 수 있습니다.), Rails.env.development에서 사용할 수 있습니다.이는 개발 중에 (기본적으로) 비활성화된 빠른 로드 때문입니다.

만약당장있난다면고치이신▁,▁you▁if▁foolingre고난.rails console클래스 이름을 지정하면 로드됩니다.그때부터, 그것은 다음에 나타날 것입니다.subclasses.

경우에 따라 코드에서 클래스를 강제로 로드해야 할 수도 있습니다.특히 STI(Single Table Inheritance)의 경우 코드에서 하위 클래스를 직접 언급하는 경우가 거의 없습니다.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]

모든 클래스가 전면에 로드되기 때문에 프로덕션에서는 이 작업이 필요하지 않습니다.

네, 여기에는 온갖 종류의 코드 냄새가 있습니다.그대로 두셔도 됩니다. 동적인 클래스 조작을 수행하면서도 개발 과정에서 부하를 빠르게 줄일 수 있습니다.일단 운영을 시작하면 성능에 영향을 미치지 않습니다.

dependents_tracker gem을 사용하면 도움이 될 수 있습니다.다음 예제는 Gem의 문서에서 복사한 것입니다.

class Foo
  extend DescendantsTracker
end

class Bar < Foo
end

Foo.descendants # => [Bar]

이 보석은 유명한 버추얼 원석이 사용하는 것이기 때문에 꽤 견고한 것 같습니다.

이 메서드는 개체의 모든 하위 항목에 대한 다차원 해시를 반환합니다.

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

{ MasterClass => descendants_mapper(MasterClass) }

임의 클래스의 전이 선체를 계산하는 방법

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

Ruby 3.1+ Class # 하위 클래스를 사용할 수 있습니다.클래스 # 하위 항목이 구현되지 않았습니다.

class A; end
class B < A; end
class C < B; end
class D < A; end

A.subclasses => [B, D]

A.descendants => NoMethodError: undefined method 'descendants' for A:Class

A.methods.grep('descendants') => []

Ruby < 3.1의 경우 레일즈 구현보다 약간 빠릅니다.

def descendants
  ObjectSpace.each_object(singleton_class).reduce([]) do |des, k|
    des.unshift k unless k.singleton_class? || k == self
    des
  end
end

Ruby 3.1+ # 하위 클래스는 위에 제시된 하위 메서드보다 훨씬 빠르게 나타납니다.

하위 클래스가 로드되기 전에 코드에 액세스할 수 있는 경우 상속된 메서드를 사용할 수 있습니다.

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

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

구문을 놓쳤다면 미안하지만 아이디어는 명확해야 합니다(지금은 루비에 접근할 수 없습니다).

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

반응형