Object Browser (#8)

by Jim Menard

Recently on ruby-talk, itsme123 asked if there was a generic object browser that will "interactively browse a graph of connected objects by showing their instance variables and letting me click through to browse".

The quiz challenge: write such a browser. It should be able to start at any object or, if none is given to it, start at the main object ("self" at the top level of any Ruby script).

The interface to the browser can be text-based or graphical.

I'm thinking of something like the Squeak Explorer (the new inspector). It's a window that displays the object with an open/close triangle next to it. Click the triangle, and the ivars are exposed.

V root: an OrderedCollection(a MyClass, a Number)
V 1: a MyClass
> name: 'the name'
> anotherIvar: 42
> 2: a Number

That's just one possible UI, of course.

Bonus points for allowing modification of instance variable values and for allowing inspection of classes (remember, classes are objects, too!).


Quiz Summary

Brian Schröder said:

I always wanted to learn more about the reflection capabilites of ruby,
and indeed there is quite a lot to learn. This quiz was not too
complicated, but the design of a good gui takes a lot of time.
(Especially if you're not accustomed to the toolkit).

I agree and I think that about summarizes this quiz.

The main point of the quiz is using "reflection". What is reflection? To quote the Pragmatic Programmers:

...to examine aspects of the program from within the program itself.

Obviously, to write an Object Browser, we would need to be able to learn things about objects we didn't design and create. This technique has a lot of practical applications though and odds are even a new Ruby programmer has seen it in action.

Used tab completion in irb? Built test cases with Test::Unit? Both are great examples of tools that figure out your intentions by looking at Ruby code. That's reflection.

So, how do we do all that? I don't want to recreate a bunch of documentation here, but here are some methods to look up if you're new to Ruby's reflection capabilities:

ObjectSpace.each_object

Object.class
Object.methods
Object.instance_variables
# and many more in Object

Module.ancestors
Module.constants
Module.class_variables
# and many more in Module

Putting such methods to use is pretty trivial. Here's Brian's (original) code for building an object tree:

ruby
# Class Tree
class ClassTreeNode
attr_accessor :klass, :subclasses, :objects

def initialize(klass)
@klass = klass
@subclasses = {}
@objects = []
end

def add_class(klass)
@subclasses[klass] ||= ClassTreeNode.new(klass)
end

def add_object(object)
@objects << object
self
end
end

# Creates or updates a klass_tree.
# When updating no classes or objects are removed
def object_browser(classtree = ClassTreeNode.new(Kernel))
ObjectSpace.each_object do | x |
classnode = classtree
x.class.ancestors.reverse[1..-1] \
.inject(classtree){ | classnode, klass |
classnode.add_class(klass)
}.add_object(x)
end
classtree
end

object_browser() just assembles a "Class Tree" by walking ObjectSpace.each_object() and using ancestors() to find parent Classes/Modules. (Note the clever use of inject(). Isn't that iterator cool?!)

The rest of Brian's solution (not shown) hands that Class Tree off to GUI routines, to build suitable displays. These routines use more reflection methods to fetch method and variable lists.

Both solutions use gtk2 to manage their GUIs. That involves no small amount of code and is a little beyond the scope of this summary, so I'll simply refer you to the solutions if you want to dig deeper.

I owe a big thanks to Jamis and Brian for pushing the quiz along this weekend while I was horribly busy.

All teasers for the next quiz have been censored out of this summary.