Loading Multiple Ruby Files and Finding Descendants of a Class

Posted by Bob Showalter Wed, 26 Sep 2007 15:35:00 GMT

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
 ...
end

I 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
In order to create my supervisor, I had to do two things:
  1. Load the player files specified on the command line, and
  2. 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
end
ObjectSpace 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

Posted by Bob Showalter Mon, 25 Jun 2007 02:22:00 GMT

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 :Server parameter passed to WEBrick::HTTPServer.new. This uses syslog-style log levels.
  • Access logging, which is controlled by the :AccessLog parameter. 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.

Here’s what I ended up using:
include WEBrick
server = HTTPServer.new(
  :Port => 8000,
  :Logger => Log.new(nil, BasicLog::WARN),
  :AccessLog => []
)
Now the server is silent unless an unexpected problem occurs.