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.
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:
The practical result of this is that your app is mounted at a specific directory, such as
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.
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
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.
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
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!