One of the (imho) lesser used pieces of the Ruby language is ObjectSpace
. In this article I’ll show one of the things you can do with it — get all the subclasses of a class.
ObjectSpace
is almost a red-headed stepchild. In JRuby you can even turn it off. However, it does have its uses.
ObjectSpace
has the following methods (apart from those in Object):
1 2 |
>> ObjectSpace.my_methods => ["_id2ref", "add_finalizer", "call_finalizer", "define_finalizer", "each_object", "finalizers", "garbage_collect", "remove_finalizer", "undefine_finalizer"] |
ObjectSpace
does allow you to take an object’s id and change it to a reference:
1 2 3 4 5 6 7 8 9 10 |
>> ObjectSpace._id2ref(ObjectSpace.object_id) => ObjectSpace >> String.object_id => -605676558 >> ObjectSpace._id2ref(String.object_id) => String >> "a".object_id => -607204778 >> ObjectSpace._id2ref(-607204778) => "a" |
The each_object
method allows one to step through all the objects in memory, performing an operation with each. This is how we’re going to get a list of subclasses:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
module Subclasses # return a list of the subclasses of a class def subclasses(direct = false) classes = [] if direct ObjectSpace.each_object(Class) do |c| next unless c.superclass == self classes << c end else ObjectSpace.each_object(Class) do |c| next unless c.ancestors.include?(self) and (c != self) classes << c end end classes end end Object.send(:include, Subclasses) |
The code steps through the objects in ObjectSpace
and returns an array of either the direct descendents, that is, ones directly subclassed from our object, or the entire tree of children. By default it returns all the children (and their children (and …)).
So we can do the following:
1 2 |
>> Enumerable.subclasses => [Struct::Tms, Dir, File, IO, Range, Struct, Hash, Array, String, StringIO, Socket, UNIXServer, UNIXSocket, UDPSocket, TCPServer, TCPSocket, IPSocket, BasicSocket, Struct::Group, Struct::Passwd, Zlib::GzipReader, Gem::SourceIndex] |
So why would we want to do this? Well, for one, it’s a good learning tool. For another, imagine a case where we don’t know all of the classes which exist in an application, however, we do know that a good number of them are subclasses of the mysterious class X. We could pass a message to all of them — telling them it’s time for dinner, perhaps, by asking X about his children. Then we message the children, letting them know it’s dinner time:
1 |
>> X.subclasses.each {|klass| klass.send(:dinner_time)} |
Ok, so that last bit was contrived and I’m tuning into _why (not quite there, it’s still a bit static around the edges).
Anyone else have any uses for ObjectSpace
?
3 comments
2 pings
Pit Capitain
August 22, 2008 at 5:14 pm (UTC -5) Link to this comment
Hi Matt, if you search the ruby-talk archives you’ll find other, more efficient implementations for the direct == false case.
Tomte
February 21, 2009 at 4:18 am (UTC -5) Link to this comment
Hi there,
I found your code very helpful for my work. I’m working on a modular tool and I want to create one instance of all classes that are subclasses of another class. Since I don’t know of the existence of all those classes (I would need a configuration file or something like that, where each single one is specified), I searched exactly for the solution you provided here :). It works very well!!!
Thanks for the good work 🙂
Matt Williams
February 21, 2009 at 3:09 pm (UTC -5) Link to this comment
I’m glad I was able to help!
Nome do Jogo » Blog Archive » Rails Podcast Brasil - Episódio 29
August 26, 2008 at 5:39 am (UTC -5) Link to this comment
[…] Ruby’s ObjectSpace: Subclasses […]
Transparent Development » Blog Archive » How can I get a list of subclasses in Ruby?
November 21, 2008 at 8:43 am (UTC -5) Link to this comment
[…] Ruby’s ObjectSpace: Subclasses […]