Building Backbone.js Apps With Ruby, Sinatra, MongoDB and Haml
Introduction
In this chapter we’re going to explore writing Backbone.js applications with a Ruby back-end. To assist with this, we’re going to use Sinatra - a DSL (domain specific language) for rapidly creating web applications in Ruby. Similar to the section on writing an application with Node.js, our server-side language (Ruby) will be used to power an API whilst Backbone.js will be the client consuming it.
What Is Sinatra?
In the past, you’ve likely come across or used Ruby on Rails (RoR) - a popular web application framework for the Ruby programming language that helps organize applications using the MVC pattern. Sinatra is a much smaller, more light-weight alternative to it.
Whilst a very basic Rails application may require a more strict project structure (such as requiring the use of controllers, views and routing etc.), Sinatra doesn’t require as many of these dependencies, sacrificing the helpers needed to connect to databases, tools to create forms or any of the other utilities Rails comes with out of the box.
What Sinatra does have is a minimal set of features most useful for tying specific URLs and RESTful HTTP actions to blocks of Ruby code and returning this code’s output as a response. Sinatra is particularly useful for getting projects up and running quickly where we don’t have a need for the extra pieces RoR provides.
For those who are familiar with more Rails, you probably know that it requires a separate routes file to define how an application should be responding to requests. These are then piped into the relevant models and controllers as needed.
Sinatra takes a more straight-forward approach, providing us with the most simple path to handling routing. By declaring get
,post
, put
or delete
actions, we can inform Sinatra to add a new route, which we can then have respond to requests.
The framework is particularly useful for writing APIs, widgets and small-scale applications that can power the backend of a client-heavy application. As mentioned, we will be using it to power our API.
Getting Started With Sinatra
Let’s review how to write and run a very basic Sinatra application. As most programming languages and frameworks typically start with some variation of Hello World
, we’ll start with a similar example.
Note: Before beginning this section, I recommend installing Sinatra on your system. A guide to doing this can be found in the prerequisites section lower down in the article.
Routes
As mentioned, Sinatra allows us to define new routes using HTTP actions. Semantically, a route follows quite a simple structure:
<a HTTP action> <the desired route> do
# some behaviour
end
A tiny route that outputs a Hello World
-like message when we attempt to get
the root could thus be written as follows:
require 'sinatra'
get '/' do
"Hello World! Is it me you're looking for?"
end
To run this snippet, we can can simply save it to a local ’.rb’ file and execute it as follows:
ruby -rubygems example.rb
If we now navigated to http://localhost:4567 in our browser we could now see the application running successfully.
The HTTP verbs we commonly work with when writing RESTful web services are: get
, post
, delete
and put
. As we now know, all Sinatra routes are basically HTTP actions (get
etc.) that are paired with a URL-matching pattern. We associate a pair of an action and route with code we would like sent back to the browser (executed)if the route is reached. Sinatra doesn’t enforce much in the way of architectural structure, instead relying on simplicity to supporting writing powerful APIs.
Here’s an example of a skeleton service we could put together supporting four common HTTP actions:
get '/items' do
# list all items available
end
get '/item/:id' do
# get a single item
end
post '/item' do
# create a new item
end
put '/item/:id' do
# update an existing item
end
delete '/item/:id' do
# delete an item
end
Sinatra’s routing is both easy for beginners to get started with but is also flexible enough for those wishing to define more complex routes. As you probably noticed in the above example, routes can include named parameters (e.g /item/:id
). We can actually access the content of these routes using the params
hash as follows:
get '/item/:id' do
# this matches "GET /item/10" and "GET /item/11"
# params[:id] is "10" or "11"
"You reached #{params[:id]}"
end
Sinatra also supports route matching via splats, wildcards and regular expressions. For more information on this I recommend reading the official docs. Let’s now take a look at handlers.
Sinatra includes convenient handler methods for tasks such as redirection, halting and passing.
Redirection
A simple route supporting redirection which returns a 302 response can be written as follows:
get '/items' do
redirect '/items/welcome'
end
And if we wish to pass additional parameters such as arguments we can do so like this: redirect http://site.com/
, Oops! I think we have a problem!
Halting
To immediately stop a request (halting) we can use halt
. Heres an example of halting a request where we specify the message body:
halt "who goes there!?"
Passing
Passing
is the concept of deferring processing of a block to the next matching route. We do this using pass
. In the following example if a parameter isnt the username we expect (rick-astley) we simply pass it on:
get '/members/:username' do
pass unless params[:username] == 'rick-astley'
'Never gonna give you up, never gonna let you down'
end
get '/members/*' do
'Welcome!'
end
There are also handler methods that can assist with sessions (specifically, cookie-based session handling). To use Sinatra’s session handling, first enable it in your application with:
enable :sessions
You can then use the session handling capabilities as follows:
get '/items' do
session['visitCounter'] ||= 0;
session['visitCounter'] += 1;
"This page has been accessed #{session['visitCounter']} times"
end
Note: By default enable:sessions will store all data in cookies. If this is not desired, you can not call this and instead use some Rack middleware instead. For more on this see here.
This only touches the surface of what can be done using routes and handlers, but is sufficient for us to write the Sinatra-powered API service we require in the practical section of this chapter.
Templating And HAML
Let’s now discuss templating.Out of the box, we can begin using templates in our Sinatra applications with ERB. ERB is included with Ruby and allows Ruby code to be added to any plain text document for the purpose of generating information or flow control. In the following example using an ERB template, note that views are by default located in the views
directory of our application.
get '/items' do
erb :default
# renders views/default.erb
end
A useful Sinatra convention worth noting is how layouts are handled. Layouts automatically search for a views/layout template which is rendered before any other views are loaded. With ERB, our views/layout.erb file could look as follows:
<html>
<head></head>
<body>
<%= data %>
</body>
</html>
Haml is a popular alternative to ERB which offers an abstract syntax for writing application templates. It has been said to be:
- Straight-forward to learn
- Very easy to read and use for visually expressing a hierarchy of DOM elements
- Popular with web designers as it builds on top of CSS syntax
- Well documented with a large community backing it
- Almost as fast as ERB
For the purpose of comparison, below we can see an ERB template compared to its Haml equivalent.
ERB
<div class="todo" id="content">
<h2 class="entry_title"><%= h @todo.title %></h2>
<div class="entry_link"><%= link_to('link', @todo.link) %></div>
</div>
Haml
.todo#content
%h2.entry_title= @todo.title
.entry_link= link_to('link', @todo.link)
One of the first things we notice is that the Haml snippet looks significantly more like CSS than it does traditional markup. It’s much easier to read and we no longer need to be concerned with divs, spans, closing tags or other semantic rules that usually mean more keystrokes. The approach taken to making whitespace a part of the syntax also means it can be much easier to compare changes between multiple documents (especially if you’re doing a diff).
In the list of Haml features, we briefly mentioned web designers. As developers, we regularly need to communicate and work with designers, but we always have to remember that at the end of the day, they are not programmers. They’re usually more concerned with the look and the feel of an application, but if we want them to write mark-up as a part of the templates or skins they create, Haml is a simpler option that has worked well for teams at a number of companies.
%h1 This is some h1 text
%h2 This is some h2 text.
%p Now we have a line containing a single instance variable: @content
%p= @content
%p Embedding Ruby code in the middle of a line can be done using ==.
%p== Here is an example: #{@foobar}
%p We can also add attributes using {}
%p{:style => "color:green"} We just made this paragraph green!
%p You'll want to apply classes and ids to your DOM, too.
%p.foo This has the foo class
%p.bar This has the bar class
%p#foobar This has the foobar id
%p.foo#foobar Or you can combine them!
%p Nesting can be done like this
%p
Or even like this
Note: Haml is whitespace sensitive and will not correctly work if it isn’t indented by an even number of spaces. This is due to whitespace being used for nesting in place of the classic HTML markup approach of closing tags.