Get a list/array of child classes from Single Table Inheritance in Rails?

10,900

Solution 1

Assuming there is at least one of each of object extant in the table:

Object.all.uniq{|x| x.type}.collect(&:type)

Solution 2

Rails extends Ruby Class with the subclasses() method.

In Rails 3 you can call it directly:

YourClass.subclasses

In Rails 2.3, ".subclasses" is protected, so we use have to call it using send():

YourClass.send(:subclasses)

Solution 3

You need to eager load the classes, as stated in: https://github.com/rails/rails/issues/3364

ActionDispatch::Reloader.to_prepare do
  Rails.application.eager_load!
end

Then you will be able to use:

YourClass.subclasses

or

YourClass.descendants

Solution 4

In your config/environments/development.rb

Rails.application.configure do
  config.eager_load = false
end

U can change false to true, and then in your console to do

Class.subclasses

or

Class. descendants

here is the difference between subclasses and descendants

subclasses:

class Foo; end
class Bar < Foo; end
class Baz < Bar; end

Foo.subclasses # => [Bar]

descendants:

class C; end
C.descendants # => []

class B < C; end
C.descendants # => [B]

class A < B; end
C.descendants # => [B, A]

class D < C; end
C.descendants # => [B, A, D]

Solution 5

ParentClass.subclasses.map(&:name)
Share:
10,900
thoughtpunch
Author by

thoughtpunch

ruby on rails, ruby, css, powershell!

Updated on June 19, 2022

Comments

  • thoughtpunch
    thoughtpunch almost 2 years

    I have a whole bunch of child classes that inherit from a parent class via single-table-inheritance in my Rails app. I'd like a way to get an array of all the child classes that inherit from the main class.

    I tried the following single-link command that I found in another SO answer, but it only returns the parent class.

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

    Is there any clean way to do this?

    EDIT: Apparently Rails only lazy-loads child classes when called in Dev mode, and possibly production depending on the Rails version. However, the first answer should work on Rails 3.1 and higher in Prod mode.

  • thoughtpunch
    thoughtpunch almost 12 years
    This works as long as one of each child class has an associated record in the DB. In this circumstance, I want to get a list of all child classes wether or not they have a record yet.
  • Dave G
    Dave G almost 12 years
    Ah. Instead try the aptly named "subclasses" call: Object.subclasses
  • thoughtpunch
    thoughtpunch almost 12 years
    Interestingly, this only works if the subclasses have been loaded into memory, either through a instantiation or a db query. I'm guessing that Rails doesn't know about subclasses at run time unless explicitly called.
  • Orlando
    Orlando almost 12 years
    it is protected? i can do Parent.subclases #=> [class,class] without any problem
  • Will Tomlins
    Will Tomlins over 11 years
    2 years late, but it's worth noting here that the issue with subclasses not being loaded is only the case when config.cache_classes is set to false as it is only in development mode.
  • Pavling
    Pavling about 11 years
    It was protected in Rails 2.3 (which is what I was probably using when I tried it while writing this post), but is not protected in Rails 3.2 (and I've just tested it with both versions again). So in Rails 3.2, you can happily call Parent.subclasses.
  • ifightcrime
    ifightcrime almost 11 years
    Also, I believe you'll have to preload your classes in development because lazy loading is turned on.
  • Ajedi32
    Ajedi32 over 9 years
    This is a terrible, terrible idea for production! Instantiate objects for every entry in the objects table (which could have thousands or even millions of rows), then iterate through them to discover their types? Cringe
  • MrYoshiji
    MrYoshiji over 8 years
    Object.pluck(:type).uniq would be less expensive in resource costs, but still the best answer is Object.subclasses
  • Adamantish
    Adamantish almost 8 years
    @Ajedi32 Yeah, it's not good but not as bad as that. uniq is not the array method it appears to be but an active record alias of distinct which alters the all and causes the SQL to SELECT DISTINCT. If there's an index on type that will be very efficient query but it's still an unnecessary and hacky database query.
  • Ajedi32
    Ajedi32 almost 8 years
    @Adamantish You sure about that? I mean, it's certainly possible, but I see nothing in the docs about that. I did find ActiveRecord::Associations::CollectionProxy#uniq, but according to that, uniq shouldn't take a block at all. Also, if your assertion is true then that does seem like a rather leaky abstraction, since the block then wouldn't be able to contain more complicated operations like, for example {|x| /([a-z]+).*/.match(x.type)[1] }
  • Adamantish
    Adamantish almost 8 years
    @Ajedi32 a very good point. My first guess was that the block is simply ignored so I tried it out in pry and indeed that's the case. This accepted answer would work exactly the same if it were just Object.all.uniq.collect(&:type)
  • Max Wallace
    Max Wallace almost 8 years
    This won't work unless there is at least one instance of each child class stored in the database.
  • MarsAtomic
    MarsAtomic almost 8 years
    This answer would benefit from a brief explanation of how it addresses the OP's issue.
  • David Hempy
    David Hempy over 6 years
    Surprisingly, if you have ` config.autoload_paths << Rails.root.join('app', 'models', 'superclass')` in config/application.rb, then .subclasses returns an empty array. Remove that config, and you get the subclasses. No idea why.
  • stackjlei
    stackjlei over 4 years
    is there a way to get subclasses that haven't been instantiated yet?
  • Anson
    Anson over 4 years
    That much more efficient SQL way is available through Rails with Object.select(:type).distinct.map(&:type). You get just the distinct types from the DB up front. Quite a bit more efficient as your table grows.
  • aaronbartell
    aaronbartell almost 4 years
    This would only return the types that exist in the database vs. all the types that actually exist.