Get a list/array of child classes from Single Table Inheritance in Rails?
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)
Comments
-
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 almost 12 yearsThis 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 almost 12 yearsAh. Instead try the aptly named "subclasses" call: Object.subclasses
-
thoughtpunch almost 12 yearsInterestingly, 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 almost 12 yearsit is protected? i can do
Parent.subclases #=> [class,class]
without any problem -
Will Tomlins over 11 years2 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 about 11 yearsIt 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 almost 11 yearsAlso, I believe you'll have to preload your classes in development because lazy loading is turned on.
-
Ajedi32 over 9 yearsThis 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 over 8 years
Object.pluck(:type).uniq
would be less expensive in resource costs, but still the best answer isObject.subclasses
-
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 ofdistinct
which alters theall
and causes the SQL toSELECT DISTINCT
. If there's an index ontype
that will be very efficient query but it's still an unnecessary and hacky database query. -
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 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 almost 8 yearsThis won't work unless there is at least one instance of each child class stored in the database.
-
MarsAtomic almost 8 yearsThis answer would benefit from a brief explanation of how it addresses the OP's issue.
-
David Hempy over 6 yearsSurprisingly, 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 over 4 yearsis there a way to get subclasses that haven't been instantiated yet?
-
Anson over 4 yearsThat much more efficient SQL way is available through Rails with
Object.select(:type).distinct.map(&:type)
. You get just thedistinct
types from the DB up front. Quite a bit more efficient as your table grows. -
aaronbartell almost 4 yearsThis would only return the types that exist in the database vs. all the types that actually exist.