Loading Multiple Ruby Files and Finding Descendants of a Class
I was playing around with writing a little Ruby program to run simulations of the Prisoner’s Dilemma. I wanted to be able to program various strategies by creating multiple classes that descended from a common Player class:
class FirstPlayer < Player
...
endI put my player classes in little ruby files and wrote a “supervisor” program to load all my classes and then play them against each other, round-robin fashion. I run the supervisor like this:
ruby supervisor.rb player1.rb player2.rb- Load the player files specified on the command line, and
- Find all the player classes
I noticed that my command line looks a lot like what happens when you run e.g. rake test:units in a Rails project. This rake task loads all your unit tests and then finds all the test cases (i.e. the classes that are descendants of Test::Unit::TestCase).
The job of loading all the test files is handled by rake_test_loader.rb, which is part of the rake gem. The relevant code is trivial:
ARGV.each { |f| load f unless f =~ /^-/ }ARGV contains all the command line arguments (after supervisor.rb). The unless part rejects arguments that start with a dash, since those would presumably be option flags.
So I can use that code as-is to load my player classes.
In order to find my player classes, I needed to borrow a technique from Test::Unit to find all classes that are descendants from my base Player class.
The relevant code is in test/unit/autorunner.rb in your standard Ruby library directory. The Test::Unit is more complex than what I needed, but I was able to distill it down to this:
players = []
ObjectSpace.each_object(Class) do |klass|
players << klass if klass < Player
endObjectSpace is a handy Ruby gizmo that, among other things, lets you iterate over all the objects in your current process. By specifying Class in the call to each_object, we iterate over all classes. To find those that are descendants of my Player class, we use a handy < operator defined in Module. This operator returns true if the left-hand argument is a descendant of the right-hand argument.
Can It Be Done in One Line?
You may be wondering why I’m using an array here and then appending to the array as I go. Isn’t there some way to use collect()/select() to extract this data in one call?
The problem is that methods like select() are in module Enumerable, but ObjectSpace is not. However, Ruby provides a handy class called Enumerable::Enumerator that can turn any object with a method that yields to a block into an Enumerable object.
To use this for our class collector, you would write:
require 'enumerator'
players = Enumerator::Enumerable.new(ObjectSpace, :each_object, Class).select {|klass| klass < Player})I’ll let you decide which form is more readable.
Minimize or disable WEBrick logging
I wrote a simple application today that used WEBrick servlets to serve up some content, and I wanted to minimize the logging that WEBrick puts out.
There are two kings of logging used by WEBrick:
- Server logging, which is controlled by the
:Serverparameter passed toWEBrick::HTTPServer.new. This uses syslog-style log levels. - Access logging, which is controlled by the
:AccessLogparameter. This logs each request, and is similar to the Apache access log.
The default server log level is INFO, but I wanted to change it to WARN. I also wanted to disable Access logging altogether.
include WEBrick
server = HTTPServer.new(
:Port => 8000,
:Logger => Log.new(nil, BasicLog::WARN),
:AccessLog => []
)