I try to expand my knowledge of Ruby by reading code from other developers I admire. In the past few weeks, that has meant reading the meager source of Camping, Why the Lucky Stiff’s tiny website framework.

It also helped to learn by writing a test framework for Camping. It works pretty well so far and has taught me a lot about Ruby, testing, and Camping. I hope to package it as a gem in the next few weeks.

Last night at the Seattle.rb, Evan Webb answered a few questions about the finer points of Ruby and metaprogramming. (Note: This is not a Camping tutorial.)

Metaprogramming via Search and Replace

I was initially confused by the way Camping controllers are constructed. Your controllers are all in a module and don’t inherit from anything. How does it work?

In camping, you start an application by sending it the namespace of your app:

Camping.goes :Blog

The practical result of this is that your app is mounted at a specific directory, such as http://my_camping_app.com/blog.

Behind the scenes, Camping actually reads its own source file (with the __FILE__ handle) and does a search and replace on all instances of Camping. It then evals the result and runs your app with the modified code!

If you read the source of camping-unabridged.rb, you will see something like this:

module Camping
  module Base
    def render(m)
      # etc.

After calling Camping.goes :Blog you get this (in memory only, not on disk):

module Blog
  module Base
    def render(m)
      # etc.

As an interesting side effect, this technique also performs the replacement on the comments in the source file. As an exercise for the reader, you could add a method to Camping that generates rdoc documentation based on the newly modified comments.

Honestly, if I were clever enough to think of this technique, I would immediately dismiss it as a hack. But if it’s good enough for why…

(I seem to remember that Perl has a preprocessor hook that makes this possible there as well.)

Class, or not a Class?

Another thing Camping does is this:

class Page < R '/(w+)'
  def get(page_name)
    # ...

What’s happening here?

Practically, we’re defining a controller and matching it up with a regular expression. If a URL matches that regular expression, it will be passed as an argument to the get or post method defined by that controller.

Technically, it gets a little weird. To start with, the left side sets up a new class called Page that inherits from…something. The right side turns out to be a method named R that takes a string as a parameter. The R method actually creates a new class and returns it back as the value for the right side.

So Page inherits from a dynamically generated class, based on a regular expression.

To get even more weird, Camping defines a class named R and also defines a method named R. Ruby is smart enough to tell that the method R is being called, not the class R.

Wha?

I honestly have no idea how I would use these concepts in a production application. But, it does show how dynamic the Ruby language is.

Now you know! Let’s go Camping!