inherited, ObjectSpace, and Test::Unit
As a part of working on a submission for the homework assignment for Ruby magazine The Rubyist, I wrote a game server so my co-workers and I could test our bots against each other.
Part of what’s needed for a good server is the ability to automatically load classes and instantiate them. If you’re familiar with ObjectSpace and you have a way to identify the bot classes, it’s not too hard. The only thing that identifies a bot class is a move! instance method, but that should be enough:
player_classes = [] ObjectSpace.each_object(Class) do |klass| player_classes << klass if klass.instance_methods.include?('move!') end player_classes
It’s a little nicer in Ruby 1.9, because if you don’t pass a block to ObjectSpace.each_object, you get an Enumerable::Enumerator, so you can do something like this:
ObjectSpace.each_object(Class).select do |klass| klass.instance_methods.include?(:move!) end
(Ruby 1.9 uses symbols for method names here instead of strings.)
Of course, if you knew that all the player classes would be subclasses of, say, TreasuredRuby::Bot, you could forgo using ObjectSpace altogether and use Class#inherited instead:
class TreasuredRuby::Bot PLAYER_CLASSES = [] def inherited(klass) PLAYER_CLASSES << klass end end
Where else are descendant classes treated specially? Test::Unit, specifically Test::Unit::TestCase subclasses. However, Test::Unit uses ObjectSpace to collect its descendant classes. There have been discussions about the problems it creates for JRuby, although JRuby handles ObjectSpace.each_object(Class) because of Test::Unit.
There was recently a patch suggested on the Ruby Core mailing list to replace using ObjectSpace with Class#inherited (and perhaps not the first), but it’s still unapplied. There are arguments against using Class#inherited: if you want to use Class#inherited in your TestCase classes, for example, you had better call super.
We’ll see if Test::Unit changes.