LJ Archive

At the Forge

Simple Apps with Sinatra

Reuven M. Lerner

Issue #214, February 2012

Writing a small Web app? Consider Sinatra, a micro-framework that makes Web development quick and easy.

For the past month or two, I have been teaching my nine-year-old daughter to program in Ruby. She has enjoyed it a great deal, as have I. To be honest, I've been a bit surprised by how much she has gotten out of the experience and how excited she is to be discovering the joy of programming.

Not surprisingly, I'm planning to introduce her to the wonderful world of Web development. But given that we're working with Ruby, I was a bit concerned. After all, the de facto standard for Web development in Ruby is Rails, and although I think Rails is a wonderful framework, it always has assumed that newcomers were at least familiar with the basics of Web development. Modern versions of Rails, with CoffeeScript, Sass stylesheets, an asset pipeline and many other features (which are wonderful in their own right) would pose a pedagogical challenge for both my daughter and me.

Just as I was trying to figure out how to introduce Rails or whether I should give up on the idea of teaching her Web development at all, I realized I had been overlooking a gem (no pun intended) of software that is perfect for these purposes—Sinatra. Sinatra has been around for a few years already, but I never really gave it much thought. It turns out, however, that Sinatra is as easy (or easier!) than the CGI programs I wrote back in the early days of the Web, supports a huge number of plugins and features, can be hosted easily on Heroku or Phusion Passenger and is a great deal of fun to work with.

So in this article, I'm taking a look at Sinatra. I describe how to configure and install it and explain why I'm convinced that whenever I now need to do a quick-and-dirty Web application, Sinatra (and not Rails) will be my tool of choice.

Sinatra Origins and Installation

Sinatra first was written by Blake Mizerany, a longtime Ruby programmer who currently works at GitHub. Sinatra is an example of both a micro-framework—that is, a Web development framework that handles just the bare minimum of what you might want to do—and also a domain-specific language (DSL). A DSL gives you a narrow, but deep, vocabulary for writing programs in a particular domain, allowing you to concentrate on the high-level development process, rather than the nitty-gritty details.

Sinatra makes it ridiculously easy to put together Web applications, because that's all it is meant to do. At the same time, it's still written in Ruby, meaning that you have access to the language's core functionality, along with gems, ranging from ActiveRecord (from Rails) to templates to network-communication systems.

To create a simple Sinatra application, you first have to install the Sinatra gem, either as root or (if you are using rvm, the wonderful Ruby version manager) as a nonroot user:

gem install sinatra

With that in place, you're ready to get started! You can create a file that looks like this:

#!/usr/bin/env ruby

require 'rubygems'
require 'sinatra'

get '/' do
  "Hello"
end

It doesn't get easier than this, right? The first line indicates that you want to execute Ruby. The second and third lines load the Gem libraries and also the “sinatra” gem itself. The third line defines a route, such that if a Web browser uses the GET method to request /, the method defined in the “do” block is executed. Run this program, and you see this:

 ~/Desktop $ ./atf-sinatra.rb
   == Sinatra/1.3.1 has taken the stage on 4567 for 
      development with backup from Thin
   >> Thin web server (v1.3.1 codename Triple Espresso)
   >> Maximum connections set to 1024
   >> Listening on 0.0.0.0:4567, CTRL+C to stop

In other words, you're now running a live Web application, via an HTTP server (Thin, if you have installed it on your system—otherwise, Sinatra tries to find something else). As I mentioned previously, Sinatra makes the development of Web applications easier and faster than ever was the case, even with CGI programs, and with many more capabilities.

You can stop the application by pressing Ctrl-C. Note that unlike Rails in development mode, Sinatra doesn't (by default, at least) notice when the program has changed and update itself accordingly. If you want to have such functionality, you can install the “shotgun” gem, which restarts the application with each request.

Sinatra uses the common Rack interface for Web applications under Ruby. This means that a Sinatra application, even one as simple as what I have written here, can be deployed anywhere that supports Rack, such as the various Ruby-flavored HTTP servers (for example Phusion Passenger) or on hosting sites like Heroku.

Of course, Web applications tend to receive input from the user. Simple parameters can come as part of the URL, following a question mark (?) character. A POST request also can contain parameters, but they are sent in a separate channel. Sinatra can handle both of these:

get '/' do
  "Hello, #{params[:name]}"
end

If I invoke this as:

http://localhost:4567/?name=Reuven

my Web browser happily prints the text:

Hello, Reuven

Views

Now, it's certainly possible to produce a working Web application using just the above. But, the output in this example is fairly limited. It's safe to assume that in anything more sophisticated than a “Hello, world” program, you'll want to use a templating system to display your views. Fortunately, Sinatra supports not just ERb templates, but also a number of other types, including popular Haml templates.

For example, let's say you want your “GET /” handler to invoke an ERb template, and that you will call the template “index”. Create a subdirectory named views in the same directory where the Sinatra application is located. Then, put a file named index.erb in the views directory, and put some text into it:


<!DOCTYPE html>
<html>
  <head>
    <title>Sinatra index</title>
  </head>
  <body>
    <h1>Sinatra</h1>
    <p>Hello, <%= @name %> </p>
  </body>
</html>

Finally, modify the “get” handler method such that it both uses the ERb template and so that it assigns the instance variable @name with something:

get '/' do
  erb :index
  @name = params[:name]
end

As is the case in all ERb templates, such as in Rails, instance variables (that is, those starting with @) are passed untouched to the template. Your ERb template may contain any HTML you want, as well as Ruby code inside of the <% %> delimiters. One bug (or feature?) that kept biting me was the lack of <%= -%> delimiters, as in Rails. I quickly trained my fingers not to type it, but I admittedly was confused when Sinatra gave me a stack backtrace that was less than obvious.

A Simple Application

So, what kinds of simple applications can you create with Sinatra? You can, of course, use a database on the back end or any number of other features, such as sessions or authentication. But, there are a lot of little applications I've written during the years that didn't require much in the way of server-side programming. One example is a simple math-practice program I wrote for my daughters several years ago using Rails. How easy would it be to produce such a program in Sinatra?

First, you need two different handlers, one for GET requests and one to POST the answers. The GET handler will choose two random numbers and a math operation to perform on them. This handler then will display the results as a math problem on a page of HTML, providing a form in which the user can submit the answer. My GET handler looked like this:

get '/' do
  @first_number = rand(5000)
  @second_number = rand(@first_number)
  @operation = %w(+ - *).sample
  erb :index
end

This code sets three instance variables, the values of which then are available for display in the ERb template, which will be index.erb in the views directory. The first number can be anything up to 5,000, and the second number must be smaller than the first. I then allowed for addition, subtraction and multiplication. Doing division with two 4-digit numbers seemed like a stretch for my children at this stage, even by my tough standards. By giving the first number as the parameter to the second call to rand(), you ensure that the second number will be no larger than the first—an important consideration when producing elementary-school problems.

index.erb (Listing 2) is, as you would expect, rather plain. The most interesting part is the HTML form, into which users will provide their answers:


<form method="POST" action="/answer">
  <input type="text" name="user_answer" />
  <input type="submit" value="Submit answer" />
  <input type="hidden" name="first_number" 
   value="<%= @first_number %>" />
  <input type="hidden" name="second_number" 
   value="<%= @second_number %>" />
  <input type="hidden" name="operation" 
   value="<%= @operation %>" />
</form>

Notice how the original operands and operation are kept as hidden fields in the form. This allows you to avoid putting them in the session and makes debugging easier.

The answer-checking program, which expects to receive three parameters (first_number, second_number and operation), then grabs them and calculates the right answer:

post '/answer' do
  @first_number = params[:first_number].to_i
  @second_number = params[:second_number].to_i
  @operation = params[:operation].to_s

  @user_answer = params[:user_answer].to_i
  @right_answer = @first_number.send(@operation, @second_number)

  erb :answer
end

There is a bit of magic in the above code. Because operators (such as +) are methods in Ruby, you can turn the operator into a symbol and then send that symbol to the first operand. In other words, the Ruby calculation:

5 + 3

is always turned into:

5.send(:+, 3)

But in this case, you do it explicitly, turning the string into a symbol.

It took me just a few minutes to throw together this program. With a bit more time, it's hard to say just what the limit would be.

Other Features

Sinatra is packed with features. It's hard to exaggerate just what this tiny framework can do. For example, if you want to use sessions, such that you can carry user data from one HTTP request to the next, you can enable the “sessions” feature:

enable :sessions

This creates a hash-like object (“session”) into which you can store or retrieve any Ruby object—although it's generally considered to be a good idea to store only simple objects, keeping more serious ones inside of a database or object store.

Sinatra also provides sophisticated routing, such that you can tell it to accept requests for a particular URL template and then put one or more elements of the template into parameters. Sinatra provides before-and-after filters, which can be activated on some or all of the handlers. Really, the number of simple applications that are appropriate for Sinatra is almost limitless.

Conclusion

If you want to throw together a simple Web application, but don't want the overhead of Rails, you seriously should try Sinatra. It has a huge number of features, great documentation, lots of community support, and it can be deployed just about everywhere Rails can be. I'm planning to use Sinatra for many small projects in the future, both with and without my kids. It's not as powerful as Rails, but that's just the point!

Reuven M. Lerner is a longtime Web developer, architect and trainer. He is a PhD candidate in learning sciences at Northwestern University, researching the design and analysis of collaborative on-line communities. Reuven lives with his wife and three children in Modi'in, Israel.

LJ Archive