echo "hey, it works" > /dev/null

just enough to be dangerous

Variable class names using Markaby


Markaby is a Ruby library that lets you write HTML in Ruby. It's built into the Camping framework as the templating engine. To create elements, you call a method of that name.

# In Camping, you don't have to set up the Builder. # I'll leave it out in the other examples. mab = Markaby::Builder.new mab.p "This is a paragraph." mab.div "I can do the same thing with a div."

To nest your HTML, you can pass a block.

form :method => 'post' do legend 'Import stuff' textarea :name => 'queries', :rows => '25', :cols => '100' input :type => 'submit', :value => 'Import!' end

[A side note, the textarea method inserts some whitespace by default. Very annoying, so if you know how to get rid of it in a non-hacky way, please let me know.]

It uses lots of metaprogramming trickery to make your life a whole lot easier. For example, you can add an arbitrary class to an element by calling the class name as a method.

div.example "This div will be of class example."

The class methods can also be chained.

div.example.special "This div will be of class example _and_ special."

You can do the same thing with an id by simply appending a bang (!) to the class name.

div.main_content! "This div will have the id main_content."

But what if you want a variable class name? I can't just tack the variable name on because the magic will mean that the class will be the name of the variable, not the value.

%w[one two three].each {|c| puts mab.p.c.to_s}

That's not going to work. It will output this.

<p class="c"/> <p class="c"/> <p class="c"/>

Ruby's send method to the rescue.

%w[one two three].each {|c| puts mab.p.send(c)}

This correctly outputs this.

<p class="one"/> <p class="two"/> <p class="three"/>

Camping seems more interesting


Turns out I don't go through the most rigorous process to decide what technology to use. I've been using Camping for quite a while now, on a couple of small internal projects. I know there are other lightweight Ruby frameworks out there, such as Sinatra and Ramaze. So, why did I choose Camping? Why? Simply because Camping seems more interesting, and things should be fun. And maybe that question is part of the answer, _why.

Taking AJAX on a Camping trip


Now that you've got JQuery in your Camping application, one of the things you might want to do is take advantage of JQuery's AJAX capabilities. I'm going to assume you're familiar with AJAX, and JQuery's implementation of it, and just talk about how to get it going in Camping. If it's foreign to you, you might want to look at a handy AJAX and JQuery tutorial.

To save me from having to make up an actual interesting example, I'm going to write a hideously incomplete todo list application, much of it ripped off and JQuery-fied from Jan Lehnardt's CouchDB tutorial. Though not the CouchDB stuff, because that's not what I'm writing about. Though it's pretty cool, and you should check out the tutorial. Okay, back to the tuscan ham.

First we set up a Camping application and deliver JQuery. I'll use the more general method of serving static files to do this.

Camping.goes :Ajax module Ajax::Controllers class Index < R '/' def get render :index end end class Resource < R '/resources/(.+)' def get resource types = { '.css' => 'text/css', '.js' => 'text/javascript', '.jpg' => 'image/jpeg', '.jpeg' => 'image/jpeg', '.gif' => 'image/gif', '.png' => 'image/png' } full_path = File.expand_path(File.dirname(__FILE__)) + "/resources/#{resource}" if resource.include? ".." # prevent directory traversal attacks @status = "403" "403 - Forbidden" elsif not File.exists?(full_path) @status = "404" "404 - File not found" else @headers['Content-Type'] = types[resource[/\.\w+$/]]||"text/plain" @headers['X-Sendfile'] = full_path end end end end

The root of the server simply renders an index page. We've also set up the route for serving static files, which we'll use in our views to send JQuery.
module Ajax::Views Camping::Mab.set(:indent, 2) def index html do head do title 'Taking AJAX Camping' script :src => R(Resource, 'jquery.js'), :type => 'text/javascript' script :type => 'text/javascript' do " $(document).ready(function() { $('#todo').focus(); $('#add').submit(function() { var value = $('#todo').val(); $('#todo').val(''); // reset input box if (value) { // don't add empty todos $.ajax({ type: 'POST', url: '/add', data: 'todo=' + value, success: function(msg){ // Add the todo to the top of the list $('#todos').prepend('<li>' + msg + '</li>'); } }); } // focus the input field when the document loads $('#todo').focus(); return false; }); });" end end body do h1 'Taking AJAX Camping' form.add! do legend "Another todo list that doesn't work" input.todo! :type => 'text', :name => 'todo' end ul.todos! "" end end end end

A few things are going on in the view. First we make a call to Camping::Mab.set(:indent, 2) which makes the output pretty. While not required, it does make things easier if you're debugging. We then use a script element to include JQuery, which is served statically through the Resource route, and set up an AJAX call to the URL /add. Entering some text in the textbox and hitting enter triggers the AJAX call, and adds whatever was returned to the list.

Of course, we don't have a route that will respond to a request to /add, so we'd better add that now. Whack this in your controller.
class Add < R '/add' def get "#{@input.todo}: #{Time.now.to_s}" end end

The current time will now be returned in response to the AJAX call. It's not a very interesting use of AJAX. We should be saving the todo to a database, but that's not the point of this piece and would just clutter up the code. So there.

But hang on, we're not rendering a view? That's right, you don't need to render a view at all, simply return text straight from the controller and everything is hunky dory. This is AJAX option one. Of course, this isn't just an AJAX peculiarity, any controller can just print out a slab of text or HTML. This pretty much defeats the purpose of an MVC framework, as we should be separating the display logic.

So, even though it's a trivial example, let's put our presentation in a view. Change the Add route in your controller.
class Add < R '/add' def get @todo = @input.todo render :add end end
Now add the add view.
def add text "#{@todo}: #{Time.now.to_s}" end
Great, now that's separated out and we're using a view. The issue with this is that when your application gets a bit bigger, adding a couple of other views, you're going to want to use a layout. If you do, the AJAX call will now return the time, all wrapped up in the layout HTML. Probably not what you want. The way around this is to render the AJAX as a partial. So, a subtle change to the controller, calling a partial.
class Add < R '/add' def get @todo = @input.todo render :_add end end
And we have to rewrite the views to use a layout and the partial.
module Ajax::Views Camping::Mab.set(:indent, 2) def layout html do head do title 'Taking AJAX Camping' script :src => R(Resource, 'jquery.js'), :type => 'text/javascript' end body { self << yield } end end def index script :type => 'text/javascript' do " $(document).ready(function() { $('#todo').focus(); $('#add').submit(function() { var value = $('#todo').val(); $('#todo').val(''); // reset input box if (value) { // don't add empty todos $.ajax({ type: 'GET', url: '/add', data: 'todo=' + value, success: function(msg){ // Add the todo to the top of the list $('#todos').prepend('<li>' + msg + '</li>'); } }); } // focus the input field when the document loads $('#todo').focus(); return false; }); });" end h1 'Taking AJAX Camping' form.add! do legend "Another todo list that doesn't work" input.todo! :type => 'text', :name => 'todo' end ul.todos! "" end def _add text "#{@todo}: #{Time.now.to_s}" end end

Of course, you don't have to just return text. You could also return JSON or XML (setting the correct Content-type header) or any other kind of structured data you want.

Serving static files in Camping, with a single route


I've written here and here about serving static files with Camping, but each time I've set up a new route for each resource type. You can serve all static files with a single route by following the instructions on the wiki.

Here's the code that's presented there.
module Camping::Controllers class Static < R '/static/(.+)' TYPES = {'.css' => 'text/css', '.js' => 'text/javascript', '.jpg' => 'image/jpeg'} PATH = File.expand_path(File.dirname(__FILE__)) def get(path) @headers['Content-Type'] = TYPES[path[/\.\w+$/, 0]] || "text/plain" unless path.include? ".." # prevent directory traversal attacks @headers['X-Sendfile'] = "#{PATH}/static/#{path}" else @status = "403" "403 - Invalid path" end end end end

You would then create a path by calling, for example, R(Static, 'style.css') from your view. The X-Sendfile header tells the server to send the specified file. If you want to support other kinds of resources, simply add an entry into the TYPES hash.

If you're using LIGHTTPD, apparently some old versions don't support the X-Sendfile header, but they do support a custom X-LIGHTTPD-send-file header, so modify the code above accordingly. I haven't tested it but the current version should work fine with X-Sendfile.

This is one of those lines of code that just shines with Ruby magic.
@headers['Content-Type'] = TYPES[path[/\.\w+$/, 0]] || "text/plain"
A couple of things are happening here. The path string passed in to the function, which is the name of the resource, is accessed using a regular expression, path[/\.\w+$/, 0], a dot followed by one or more word characters. The 0 says which atom of the match data to return, and is actually redundant, I believe, as no atoms are defined, and 0 is the entire match anyway. There are more details in the String API.

The regular expression grabs the extension of the file, which is then used as the key into the TYPES hash. If the extension is in the hash, the appropriate content type will be returned and the header set. If it isn't in the hash, nil will be returned, Ruby's tasty || operator will come into effect, and the content type will be set to a sensible default, text/plain.

Using JQuery with Camping


Camping is great, yeah. JQuery too. Maybe you want to use them together? Here's how.

First, you need to set up a route for your JQuery. I've talked about sending static files with Camping before, so this is just a modification of that. [You can serve static files more sensibly than setting up a route for each type. Maybe one day I'll write about that.]

In your controller:
module MyApp::Controllers class Index < R '/' def get render :index end end class JQuery < R '/resources/jquery.js' def get current_dir = File.expand_path(File.dirname(__FILE__)) @headers['Content-Type'] = "text/javascript" @headers['X-Sendfile'] = "#{current_dir}/resources/jquery.js" end end end

Now you have to get the JQuery library in your view.
module MyApp::Views def layout html do head do title 'Using JQuery in Camping' end body { self << yield } end end def index script :src => R(JQuery), :type => 'text/javascript' script do ' $(document).ready(function() { alert("Hello world!"); }); ' end end end

The call to the R function creates the URL for the JQuery library. Now you can embed any JQuery yumminess that you want, right there in your view.

Serving static files with Camping


Camping interprets every request to the server using the paths in the controllers, so if you want to serve static files, like external stylesheets or images, you need to provide a controller for them. Take this bit of Markaby:
img :src => 'images/logo.png'
This will get interpreted by Camping and end up with this HTML:
<img src='images/logo.png' />
When the browser reads that, it will send a GET request to the Camping server looking for logo.png, that Camping will try to match against the controller paths. If you haven't set one up, you'll get an error.

So, in your Controllers module:
class StaticImage < R '/images/(.*)' def get(static_name) current_dir = File.expand_path(File.dirname(__FILE__)) @headers['Content-Type'] = "image/png" @headers['X-Sendfile'] = "#{current_dir}/images/#{static_name}" end end
and in your View module, create the link like this:
img :src => R(StaticImage, 'logo.png')

Shake and bake for any kind of static resource you want to send.

Passing information to Camping


If you want to pass information to a Camping controller, all you have to do is define the class like this:
class Index < R '/(\d+)' get(id)
Now whatever was matched by the regex \d+ can be retrieved by the variable id. If you want to pass more than one variable, just chain them like this:
class Index < R '/(\d+)/(\w+)' get(id, action)
Each of your regex groups will be passed as a variable.

If you forget to add the arguments to the get method, you'll get an error like this:

Camping Problem!

MyApp::Controllers::Index.GET

ArgumentError wrong number of arguments (1 for 0):