July 2008

Camping Explained: The beginning

Today I’m going to start a new series on the cool stuff behind Camping. There’s a lot to cover, but I will try to keep each part short and concise so you can jump right in without reading the previously parts.

It should also be noted that I will only cover 2.0. So you better grab your own copy from GitHub:

git clone git://github.com/why/camping.git 

Camping.goes :Everywhere

I’m starting with what every Camping app starts with:

Camping.goes :Everywhere

Before 1.2, you didn’t use Camping.goes at all. You simply used the Camping module for all your apps:

require 'camping'

module Camping::Controllers
  class Index < R '/'
    def get
      "Hello World!"
    end
  end
end

It worked great in the beginning, but if you tried to load several apps in one process they started overwriting each other; fighting for survival.

Camping.goes was the solution.

The implementation

Let’s take a look at this method:

module Camping
  S = IO.read(__FILE__) rescue nil
  
  def self.goes(m)
    eval S.gsub(/Camping/,m.to_s), TOPLEVEL_BINDING
  end
end

First of all we’re saving the content of camping.rb in Camping::S, and we don’t really care if we encounters any errors.

Next up, #goes replaces every occurrence of “Camping” with <insert your app here> in Camping::S.

Then we simply evaluate this string as it was in the top of our program, not inside the Camping module (hence TOPLEVEL_BINDING).

The result: A module which is an exact copy of Camping.

The real magic

The real magic is that the four-oh-four page still shows Camping Problem! Didn’t we just replaced every occurrence of “Camping?” Why didn’t this also get replaced?

module Camping
  P = "<h1>Cam\ping Problem!</h1><h2>%s</h2>"
end

See? When Ruby reads this code it will include the backslash and won’t be replaced. But when Ruby evaluates it, it will simply return “Camping”, since “\p” isn’t a special character:

>> "Cam\ping"
=> "Camping"

A warning

Due to this nasty use of eval, not everything would work as we expect it to do:

require 'camping'

def Camping.rules!
  "Let's go shopping"
end

Camping.goes :Shopping
Shopping.rules!         # => it hits Camping#method_missing

Camping doesn’t copy the object, so it doesn’t matter what you do with it. Instead you have to modify Camping::S

require 'camping'

Camping::S << <<EOF
def Camping.rules!
  "Let's go shopping"
end
EOF

Camping.goes :Shopping
Shopping.rules!         # => "Let's go shopping"

Oh, and one more thing

For those of you who haven’t subscribed to the mailing list, I think you should know this:

Magnus Holm: Come on, you lazy interrogative! Are your shoes too heavy? :-(

Why the Lucky Stiff: Bzzt! (Drawing on backup camping power grids…) Very sorry to be bad about this. Yeah, lots of Shoes stuff going on this week as we’re shooting for a July 31st release and there many bothersome bugs to smash.

You’ve all done a lot of fanastic work and I say we keep working a bit longer. I just merged zimbatm’s week-old patches and I think it would be great if we could get more campers to mess with judofyr’s gems, as I think camping 2.0 will inspire some breakages.

Augusttime would be better for me, but let’s talk about what you guys want to do.