In this 4-part series of tutorials, I’m going to take you through the process of creating a fully functioning To Do List app called ‘Just Do It’, using Sinatra and DataMapper. Hopefully, this will help to demonstrate just how quick and easy it easy to use Sinatra. Let’s begin with the basics of Sinatra.
Installing Sinatra
To get started with Sinatra, you’ll need to have Ruby installed. I would recommend using RVM for this (you can follow this great guide by Glenn Goodrich if you need help). After you have Ruby and Rubygems successfully installed, it’s time to install Sinatra. This is done using Rubygems and is simply a case of opening up a command line and typing:
$ gem install sinatra
A Basic Application
To start with, open up your favorite text editor and save the following as main.rb. Now type the following lines.
require 'sinatra'
get '/' do
"Just Do It"
end
Note – If you are using a version of Ruby less than 1.9, you will need to put the line require 'rubygems'
at the top of this file.
This is about as basic as a Sinatra application can get: You have to require the sinatra gem at the top, then the real action starts on line 3. This is called a handler because it handles routes and actions. The first part (get) states which HTTP method is being used, in this case, HTTP GET, because we are ‘getting’ a page. The next part is a string that corresponds to the route, which is ‘/’ – the root url of the application. The code block specifies what happens when the user visits this url. In this case, we return a simple text string (“Just Do It” ) which will be rendered on the page. The last line of a handler’s code block is always what will be rendered by the browser.
To check that it’s working, we need to start a Sinatra server. Open up a command line, navigate to where your file is saved and type:
$ ruby main.rb
After a couple of seconds, you should see the following message:
== Sinatra/1.2.6 has taken the stage on 4567 for development with backup from Thin
Open up your browser and go to http://localhost:4567. You should see the inspirational phrase”Just Do It”. Well done, you’ve successfully created your first Sinatra app. See, I told you it was easy!
Inline Templates
The Just Do It application won’t get very far by just displaying short strings of text, of course. We’re going to have to create some template files that will contain HTML as well as some dynamic content using Ruby. Slim is a fantastic template engine that makes this a much easier task. Before we go on, we need to install the slim gem:
gem install slim
Now, go back to main.rb and add the following lines:
require 'sinatra'
require 'slim'
get '/' do
slim :index
end
__END__
@@layout
doctype html
html
head
meta charset="utf-8"
title Just Do It
link rel="stylesheet" media="screen, projection" href="/styles.css"
/[if lt IE 9]
script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"
body
h1 Just Do It
== yield
@@index
h2 My Tasks
ul.tasks
li Get Milk
First, we make sure that we require the slim gem and then we make a few changes to the handler so the last line now returns a view called ‘index’ that is generated by slim. This view can be seen at the bottom of the file, after ‘@@index’. This is an example of Sinatra’s inline templates, something I consider to be a killer feature as it allows you to keep all your code in the same file – perfect for putting things together quickly. Inline templates always come after the __END__
declaration, and each template begins with @@
.
I’ve also included a template called ‘@@layout’. This will automatically be rendered with every view and provides a basic HTML5 scaffolding. The key line in the layout template is right at the end (==yield
). The yield
statement renders the content from the whichever template was requested by the handler (in this case, ‘index’).
Both of these views use Slim’s minimal syntax. I find this makes writing HTML a much more pleasant experience, but be warned – Slim is white-space sensitive. Indentations of 2 spaces are used to nest elements within each other and Slim is very strict about this being consistent. If you don’t like Slim, there’s a whole variety of other templating languages that can be used with Sinatra, including ERB, Haml and Markaby.
Let’s see how this looks: Kill the server by pressing Ctrl+C and restart it again by typing ruby main.rb
. The server will need to be restarted every time you make any changes to your code (if this starts to become a hassle, you might want to try using Shotgun). Reload the page at http://localhost:4567 to see our new layout.
External Views
Now that you are familiar with how Sinatra uses handlers to render views, let’s move away from the inline templates and look at organizing our views into folders.
Before we do that, we need to remove the inline templates: open up main.rb and delete the __END__
declaration and all the templates that come after.
In the same directory where you saved the ‘main.rb’ file, create two folders, one called ‘public’ and the other called ‘views’. The public
folder will be used to keep any public facing assets, such as images and stylesheets. The views folder will keep all of our Slim templates. Our existing views need transferring to separate files. Save the following in the views
folder as layout.slim:
doctype html
html
head
meta charset="utf-8"
title Just Do It
link rel="stylesheet" media="screen, projection" href="/styles.css"
/[if lt IE 9]
script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"
body
h1 Just Do It
== yield
And also save the following as index.slim:
h2 My Tasks
ul.tasks
li Get Milk
It’s a good idea to restart your Sinatra server and make sure our views are still rendered.
Dynamic Content
Now that we are a bit more organized we can have a look at a few more of Sinatra’s features. Let’s create a new handler that takes some dynamic input (in main.rb
):
get '/:task' do
@task = params[:task]
slim :task
end
You may have noticed that the route contains the string ‘:task’ – this is a named parameter, identifiable by the leading colon(‘:’). Named parameters are values taken from the url that are accessible through the ‘params’ hash. In the first line of the code block, I set an instance variable called ‘@task’ equal to the value of params[:task], which will be whatever is written after the forward slash in the url. Instance variables are useful as they can be referenced in views.
Speaking of views, the new route specifies a ‘task’ view, which doesn’t exist. Copy the following code into a new text file and save it in the views folder as ‘task.slim’:
h2 My Tasks
= @task
This uses the ‘=’ sign to evaluate a Ruby variable. Slim will output the result of whatever Ruby is placed after the ‘=’ sign. In this case it is the value of the @task instance variable, which should match the url. Test this out – kill and restart the server, then go to ‘/http://localhost:4567/get-milk’. You should see the following:
Simple task route
This is okay, but we can do better. Let’s add a bit of logic into the handler to make it look nicer:
get '/:task' do
@task = params[:task].split('-').join(' ').capitalize
slim :task
end
Now when you go to ‘/http://localhost:4567/get-milk’, you should see the following:
Our better task page
Forms
Before we finish part one of the tutorial, lets have a look at adding a task using a form. Open up index.slim and replace the contents with the following:
form action="/" method="POST"
input type="text" name="task"
input.button type="submit" value="New Task "
We now need a handler to deal with the form after it has been posted. If you have a look at the code, the action attribute tells the form to submit itself to the url ‘/’ and the method attribute tells it to use the POST method. This leads very nicely on to what I consider to be another one of Sinatra’s killer features – the way the request method is specified in the handler. Add the following handler to ‘main.rb’:
post '/' do
@task = params[:task]
slim :task
end
This code is very similar to the handler we used earlier to list the tasks, but now we are using the form’s input to get a task. The new handler is defined as a POST route, meaning that it only reacts to HTTP POST requests. So, we can define two handlers for the same route – ‘/’ – but give different blocks of code based on the type of request.
When the form is submitted, it sets the value of params[:task] to whatever was entered in the form task
text input. You can access any values set in forms by referencing the params hash in a similar way.
Go to http://localhost:4567/ and have a play around adding some tasks with the form. This is a start, but we can’t create a list of tasks and there’s certainly no way of completing and deleting them. We need a way of storing the tasks, and that is what we’ll be covering in part 2!