Getting started with Sinatra and test-driven development

Sinatra is a DSL (domain-specific language, or a language written for a specific purpose) for building web applications. It can be confusing. If you’re a beginner programmer fiddling around with Ruby, the single-user terminal apps you’ve been writing are suddenly up on the web for everyone to see, and they need to behave differently as a result.

require 'sinatra'
get '/' do
  'hello world!'
end

If you want to run this and get it working, save this into a file (say server.rb), go to that directory and run ruby server.rb (you’ll need Ruby installed on your machine, naturally). This code, specifically the bit between get and end (called the controller) will intercept an HTTP GET request sent to ‘/’, and reply with ‘hello world!’ in plain text. Simple.

Routes

In the above example, ‘/’ is the route. You can define a bunch of other routes, too. For example:

get '/hi' do
  "<h1>Hello Makers!</h1>"
end

Now, accessing the website path /hi will yield the above message (which in this case will appear big and bold on the page, as we have wrapped it in h1 tags.)

Testing with Cucumber

Cucumber is used for acceptance testing – it acts as if it were a person using the application to make sure it works. You can use Cucumber with Sinatra by using the cucumber-sinatra gem. Get started by running:

$ cucumber-sinatra init --app SampleApp lib/sample_app.rb
$ rackup

Run your Cucumber tests from the command line with $ cucumber. Now, let’s describe a feature using Cucumber:

$ mkdir feature $ touch homepage.feature
Feature: The homepage
  In order to show off Cucumber I want to present Cucumber

This will run, but doesn’t have any actual tests (or scenarios). Let’s add some in the same file.

Scenario: Being greeted
  Given I am a site visitor
  When I am on the homepage
  Then I should see "Hello hello!"

This Given/When/Then notation is particular to Cucumber (or specifically Gherkin, the language Cucumber is written in). For each step, you write a step definition in Ruby. cucumber-sinatra has some built in step definitions, so you can write some steps without needing to define them (like When I am on the homepage). These built-in definitions live in features/step_definitions/, and there are support files in support/ which do things like substituting ‘homepage’ for ‘/’. Cucumber also uses a language called Capybara, which can interact with a webpage – filling in forms, clicking links, looking for specific page content, and so on. Consider deleting features/step_definitions/web_steps.rb to get some practice in writing your own step definitions, which also makes the whole thing seem less like magic and gives you a better understanding of how Cucumber matches up your tests with Capybara methods behind the scenes.

Running the damn thing

Get your server up and running by typing

$ rackup

What this command actually does is read through your sample_app.rb file, looks at the various routes you’ve set, and creates a routing table from it. Then it starts an HTTP server on a particular port and makes it available for connections.

Config

Look at the following code from the Sinatra documentation:

set :views, Proc.new { File.join(root, "templates") }

Why do it this way? Seems kinda convoluted. The reason this is so important is that it bases the path to your views directory on the site root. If you ran rackup from a different directory and you’d hard-coded the views path, then it would break – it wouldn’t be able to find views relative to where you’re running the server file from. Using root gives a salient point that your site is always run from.

Sessions

HTTP by nature is stateless – so it doesn’t recognise repeat visitors, or even visitors across pages. A lot of basic things fail because of this – how could you log in to a site, for example, if it didn’t remember you? This is where sessions come into play. The server creates a unique ID (or UID) for each visitor and stores it in a hash table. This value is served back up to users to identify them, either in a cookie or (more rarely) in the URI itself. A cookie is returned as part of the header – it’s just a string inside the header. A cookie sent by a particular site can only be read from the browser by that site. So how do you use sessions in Sinatra? sample_app.rb:

class SampleApp < Sinatra::Base
  enable :sessions

  get '/' do
    @number = session
    erb :index
  end

  get '/a/:number' do |number|
    session['number'] = number
    redirect to '/'
  end
end

Bear in mind that sessions have a hard limit of 4KB! To store more than this, you would use a database – but that’s a topic for another time.