Tra cứu tất cả hậu duệ của một lớp trong Ruby


144

Tôi có thể dễ dàng tăng thứ bậc lớp trong Ruby:

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

Có cách nào để đi xuống thứ bậc không? Tôi muốn làm điều này

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

nhưng dường như không có một descendantsphương pháp nào.

(Câu hỏi này xuất hiện vì tôi muốn tìm tất cả các mô hình trong ứng dụng Rails xuất phát từ một lớp cơ sở và liệt kê chúng; Tôi có một bộ điều khiển có thể hoạt động với bất kỳ mô hình nào như vậy và tôi muốn có thể thêm các mô hình mới mà không phải sửa đổi bộ điều khiển.)

Câu trả lời:


146

Đây là một ví dụ:

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

đặt Parent.descendants cho bạn:

GrandChild
Child

đặt Child.descendants cho bạn:

GrandChild

1
Điều đó làm việc tuyệt vời, cảm ơn! Tôi cho rằng việc truy cập vào mỗi lớp học có thể quá chậm nếu bạn đang cố gắng cạo một phần nghìn giây, nhưng nó hoàn toàn nhanh đối với tôi.
Douglas Squirrel

1
singleton_classthay vì Classlàm cho nó nhanh hơn nhiều (xem nguồn tại apidock.com/rails/Class/descendants )
brauliobo

21
Hãy cẩn thận nếu bạn có thể có một tình huống mà lớp chưa được tải vào bộ nhớ, ObjectSpacesẽ không có nó.
Edmund Lee

Làm thế nào tôi có thể làm cho công việc này hoạt động ObjectBasicObject?, Tò mò muốn biết những gì họ thể hiện
Amol Pujari

@AmolPujari p ObjectSpace.each_object(Class)sẽ in ra tất cả các lớp. Bạn cũng có thể có được hậu duệ của bất kỳ lớp nào bạn muốn bằng cách thay thế tên của nó selftrong dòng mã trong phương thức.
BobRodes

62

Nếu bạn sử dụng Rails> = 3, bạn có hai tùy chọn tại chỗ. Sử dụng .descendantsnếu bạn muốn có nhiều hơn một cấp độ sâu của các lớp trẻ em, hoặc sử dụng .subclassescho cấp độ đầu tiên của các lớp trẻ em.

Thí dụ:

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
Lưu ý rằng trong quá trình phát triển, nếu bạn đã tắt tải háo hức, các phương thức này sẽ chỉ trả về các lớp nếu chúng đã được tải (tức là nếu chúng đã được tham chiếu bởi máy chủ đang chạy).
stephen.hanson

1
@ stephen.hanson cách an toàn nhất để đảm bảo kết quả chính xác ở đây là gì?
Chris Edwards

26

Ruby 1.9 (hoặc 1.8.7) với các trình vòng lặp tiện lợi:

#!/usr/bin/env ruby1.9

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

Ruby trước 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

Sử dụng nó như vậy:

#!/usr/bin/env ruby

p Animal.descendants

3
Điều này cũng làm việc cho các Mô-đun; chỉ cần thay thế cả hai phiên bản của "Class" bằng "Module" trong mã.
korinthe

2
Để an toàn hơn, người ta nên viết ObjectSpace.each_object(::Class)- điều này sẽ giữ cho mã hoạt động khi bạn có YourModule :: Class được xác định.
Rene Saarsoo

19

Ghi đè phương thức lớp có tên kế thừa . Phương thức này sẽ được thông qua lớp con khi nó được tạo mà bạn có thể theo dõi.


Tôi thích cái này quá. Ghi đè phương thức bị xâm phạm một chút, nhưng nó làm cho phương thức con cháu hiệu quả hơn một chút vì bạn không phải truy cập vào mỗi lớp.
Douglas Squirrel

@Doumund Trong khi nó ít xâm phạm hơn, có lẽ bạn sẽ phải thử nghiệm để xem liệu nó có đáp ứng nhu cầu của bạn không (tức là khi nào Rails xây dựng hệ thống phân cấp bộ điều khiển / mô hình?).
Josh Lee

Nó cũng dễ mang theo hơn đối với các triển khai ruby ​​không MRI khác nhau, một số trong đó có hiệu suất hoạt động nghiêm trọng từ việc sử dụng ObjectSpace. Lớp # được kế thừa là lý tưởng để triển khai các mẫu "đăng ký tự động" trong Ruby.
John Whitley

Muốn chia sẻ một ví dụ? Vì nó là cấp độ lớp học, tôi đoán bạn sẽ phải lưu trữ mỗi lớp trong một số loại biến toàn cầu?
Noz

@Noz Không, một biến đối tượng trên chính lớp đó. Nhưng sau đó các đối tượng không thể được thu thập bởi GC.
Phrogz

13

Ngoài ra (cập nhật cho ruby ​​1.9+):

ObjectSpace.each_object(YourRootClass.singleton_class)

Cách tương thích với Ruby 1.8:

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

Lưu ý rằng điều này sẽ không làm việc cho các mô-đun. Ngoài ra, YourRootClass sẽ được bao gồm trong câu trả lời. Bạn có thể sử dụng Array # - hoặc một cách khác để loại bỏ nó.


điều đó thật tuyệt. Bạn có thể giải thích cho tôi làm thế nào mà làm việc? Tôi đã sử dụngObjectSpace.each_object(class<<MyClass;self;end) {|it| puts it}
David West

1
Trong ruby ​​1.8, class<<some_obj;self;endtrả về singleton_group của một đối tượng. Trong 1.9+ bạn có thể sử dụng some_obj.singleton_classthay thế (cập nhật câu trả lời của tôi để phản ánh điều đó). Mỗi đối tượng là một thể hiện của singleton_group, nó cũng áp dụng cho các lớp. Vì Each_object (someClass) trả về tất cả các phiên bản của someClass và someClass là một thể hiện của someClass.singleton_ class, Each_object (someClass.singleton_ class) sẽ trả về một số lớp và tất cả các lớp con.
apeiros

10

Mặc dù sử dụng ObjectSpace hoạt động, phương thức lớp được kế thừa có vẻ phù hợp hơn ở đây tài liệu Ruby được kế thừa (lớp con)

Objectspace về cơ bản là một cách để truy cập mọi thứ và mọi thứ hiện đang sử dụng bộ nhớ được phân bổ, do đó, lặp đi lặp lại qua từng yếu tố của nó để kiểm tra xem nó có phải là một lớp con của lớp Thú không lý tưởng không.

Trong đoạn mã dưới đây, phương thức lớp Animal được kế thừa thực hiện một cuộc gọi lại sẽ thêm bất kỳ lớp con mới được tạo nào vào mảng con cháu của nó.

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

  def self.descendants
    puts @descendants 
  end
end

6
`@descendants || = []` nếu không, bạn sẽ chỉ nhận được hậu duệ cuối cùng
Axel Tetzlaff

4

Tôi biết bạn đang hỏi làm thế nào để thực hiện điều này trong kế thừa nhưng bạn có thể đạt được điều này với trực tiếp trong Ruby bằng cách đặt tên cho lớp ( Classhoặc 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]

Cách này nhanh hơn nhiều so với việc so sánh với mọi lớp trong ObjectSpacenhư các giải pháp khác đề xuất.

Nếu bạn thực sự cần điều này trong một tài sản thừa kế, bạn có thể làm một cái gì đó như thế này:

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

điểm chuẩn ở đây: http://www.eq8.eu/bloss/13-ruby-ancestors-descendants-and-other-annoying-relative

cập nhật

cuối cùng tất cả những gì bạn phải làm là đây

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

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

class Foo < DarthVader
end

DarthVader.descendants #=> [Foo]

cảm ơn bạn @saturnflyer đã gợi ý


3

(Rails <= 3.0) Ngoài ra, bạn có thể sử dụng ActiveSupport :: DescendantsTracker để thực hiện hành động. Từ nguồn:

Mô-đun này cung cấp một triển khai nội bộ để theo dõi con cháu nhanh hơn so với việc lặp qua ObjectSpace.

Vì nó được mô đun hóa độc đáo, bạn chỉ có thể 'cherry-pick' mô-đun cụ thể đó cho ứng dụng Ruby của bạn.


2

Ruby Facets có con cháu lớp #,

require 'facets/class/descendants'

Nó cũng hỗ trợ một tham số khoảng cách thế hệ.


2

Một phiên bản đơn giản cung cấp một mảng gồm tất cả các hậu duệ của một lớp:

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

1
Đây trông giống như một câu trả lời vượt trội. Thật không may, nó vẫn rơi vào tình trạng lười biếng tải các lớp học. Nhưng tôi nghĩ tất cả họ đều làm.
Dave Morse

@DaveMorse Tôi đã kết thúc việc liệt kê các tệp và tải thủ công các hằng số để chúng được đăng ký là con cháu (và cuối cùng đã xóa toàn bộ điều này: D)
Dorian

1
Lưu ý rằng đó #subclasseslà từ Rails ActiveSupport.
Alex D

1

Bạn có thể require 'active_support/core_ext'và sử dụng descendantsphương pháp. Kiểm tra tài liệu và bắn nó trong IRB hoặc pry. Có thể được sử dụng mà không cần Rails.


1
Nếu bạn phải thêm hỗ trợ tích cực cho Gemfile của mình, thì đó không thực sự là "không có đường ray". Chỉ là chọn những mảnh đường ray mà bạn thích.
Caleb

1
Điều này có vẻ như là một tiếp tuyến triết học không liên quan đến chủ đề ở đây, nhưng tôi nghĩ rằng việc sử dụng một thành phần Rails không nhất thiết có nghĩa là một trong những using Railsý nghĩa tổng thể.
thelostspore

0

Rails cung cấp một phương thức lớp con cho mọi đối tượng, nhưng nó không được ghi chép tốt và tôi không biết nó được định nghĩa ở đâu. Nó trả về một mảng các tên lớp dưới dạng chuỗi.


0

Sử dụng đá quý descendants_tracker có thể giúp đỡ. Ví dụ sau được sao chép từ doc của gem:

class Foo
  extend DescendantsTracker
end

class Bar < Foo
end

Foo.descendants # => [Bar]

Loại đá quý này được sử dụng bởi loại đá quý phổ biến , vì vậy tôi nghĩ nó khá chắc chắn.


0

Phương thức này sẽ trả về hàm băm đa chiều của tất cả các hậu duệ của Object.

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

{ MasterClass => descendants_mapper(MasterClass) }

-1

Nếu bạn có quyền truy cập vào mã trước khi bất kỳ lớp con nào được tải thì bạn có thể sử dụng phương thức kế thừa .

Nếu bạn không (không phải là trường hợp nhưng nó có thể hữu ích cho bất kỳ ai tìm thấy bài đăng này), bạn chỉ có thể viết:

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

Xin lỗi nếu tôi bỏ lỡ cú pháp nhưng ý tưởng sẽ rõ ràng (tôi không có quyền truy cập vào ruby ​​tại thời điểm này).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.