Sinatra’s Little Helpers

While I’ve been working on my little book about Sinatra, I’ve picked up a few helper methods that Sinatra provides. Some of these I already knew about, but hadn’t really used much and some I didn’t even know about.

Sinatra has a bucket load of helper methods that contribute to making it such a nice experience for the developer. I’ve collected a few of them here for with some examples. Some of these may be new to you, or like me you may have heard of them before and just be grateful of the reminder.

Enable and Disable

You can make app-wide settings (or module-wide settings if you’re using a modular-style app) easily in Sinatra using the set method:

set :title, "My Amazing Blog"
set :comments, true
set :tags ,false

But if the settings is a boolean value, you can use the enable and disable methods instead. This not only uses less code, but also reads much better.

enable :comments
disable :tags

You can also use this for built-in settings:

enable :logging
enable :sessions

Sinatra also creates a handy helper method for any boolean settings to help check if they are true. Basically it’s the name of the setting followed by a question mark. This allows us to write easy to read and follow code that checks if a settings is true or not:

if settings.comments? then display @page.comments
redirect to('/') unless settings.?

Which Environment

The following methods make checking which environment is being used a cinch:

production?
development?
test?

This means you can write some nice looking conditional statements, such as:

require 'sinatra/reloader' if development?
run! unless test?

Halt and Pass

These two methods can be used in route handlers to control the HTTP request cycle. halt will stop a request in its tracks, with an optional message or view and status code. It can be used if a process is taking too long or a critical error has occurred. Other uses include if somebody is trying to view an unauthorized page.

get '/important-process' do
  halt 500 if something_bad_happened?
  "Everything is okay"
end

get '/secret-page' do
  halt 401,"This page is secret" unless logged_in?
  "For your eyes only"
end

The pass method can be used to pass the method down the chain to the next matching route. In the example below we have a standard route that says hello, but if the name entered in the URL is ‘daz’ then I want to pass the request on to the more specific route handler that will display a much nicer message:

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

get '/daz' do
  "Well hello there DAZ!"
end

Of course in the example above it would just be easier to change the order of the route handlers, but there may be instances where you don’t have control over this.

Request.xhr?

If you want to know if a request was made using Ajax, then the Request.xhr? helper method comes in handy. This will return true if the XML HTTP Request object was used. I often use this to do redirect if Ajax wasn’t used, like in the example below which is used to delete a page. If Ajax is used then the page will just be deleted and we want the user to stay on the ‘/pages’ page, but if Ajax isn’t used then we will have to manually redirect them back to the ‘/pages’ page:

delete '/page/:id' do
  page = Page.get params[:id]
  page.destroy
  redirect to('/pages') unless request.xhr?
end

Redirecting Back

If you have a route handler that basically performs an operation and then sends the user back to the page they came from then the back helper method is just what you need to make you code read nicely. Used along with the redirect helper method this will simply send the user back to the page from whence they came. For example, the delete page route handler that we saw :

delete '/page/:id' do
  page = Page.get params[:id]
  page.destroy
  redirect back unless request.xhr?
end

Splats

In Sinatra you can can create named parameters by placing a colon in front of them in the route definition, like so:

get '/hello/:name' do

end

This will store whatever is entered in the place of :name in params[:name]. We can be more general than this and use * in the route. This allows any amount of text to be entered. It is accessible through params[:splat], for example:

get '/goodbye/*/hello/*' do

end

The URL ‘/goodbye/shoes/hello/flipflops’ would give params[:splat] = ["shoes","flipflops"]

You can also use it with block parameters if you don’t like using the the word splat. This also makes your code more readable and self-explanatory:

get '/*.*' do |path,ext|

end

In the example above, the route ‘/application.js’ would result in variables path = 'application' and ext = 'js', which could then be used inside the route handler block.

You can also use the splat to grab urls of indeterminate length:

get '/blog/ *' do |path|
  @page = Page.first(:slug => path.first)
end

In the example above the route entered after ‘/blog’ can be accessed using path.first. For example, a route such as ‘/blog/all/about/sinatra’, would result in path.first = '/all/about/sinatra'. This can be useful when dealing with things like pretty URLs.

Cache Control

You can control how pages are cached by setting the headers directly, but there are also the cache_control, expires, last_modified and etag helpers that make cache control a piece of cake.

For example, if you had a page model in a simple CMS system, you could use the following:

get '/page/:id'
  @page = Page.get(params[:id])
  last_modified @page.updated_at
  etag @page.content
  markdown @page.content
end

For quick and dirty cache control for all pages, you can use the application start time to set the last_modified and etag headers globally. While the application start time may not strictly be the actual time all the files were last modified, we can usually assume that if the application has been restarted then something must have changed. In this case, It’s worth flushing the cache out and reloading the pages anyway. To set the application start time, place the following code in a configure block:

configure do
  set :start_time, Time.now
end

This works because the code in a configure block is only run once at start up. Now we can use this setting to set the last_modified and etag headers. The settings is a Time object, so it is fine to use for the last_modified header, but it will need to be converted to a string in order to be used for the etag header. If the following code is placed in a before block then it will be applied to all pages:

before do
  last_modified settings.start_time
  etag settings.start_time.to_s
  cache_control
end

Now you should get a 304 status code instead of 200 if you refresh a page that hasn’t been modified. This is a useful way of reducing the load on the server in an application by avoiding any needless round trips to the server.

That’s All Folks

What all these methods do is make the code much easier to read and maintain. If you look at some of the code samples above, they read almost like a full sentence of English and make it very obvious what is being done. These methods also let you perform neat tricks and use less code. This is the beauty of using Sinatra – the code is short and sweet. Most of these helper methods are covered in more detail in the book. Have you used any of these and found them useful, have I missed any? Leave your answers in the comments below.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Martin Streicher

    What is the best way to run a modular Sinatra app to have it reload with each request?

  • http://fatgeek.in/ Sairam

    There are many ways. I used ‘rerun’ gem. It need not be part of the package. I have had my share of problems with sinatra reloader and guard .

  • http://daz4126.com/ Darren Jones

    Hi Martin,

    I’ve used Sinatra Reloader like so:

    require “sinatra/base”
    require “sinatra/reloader”

    class MyApp < Sinatra::Base
    configure :development do
    register Sinatra::Reloader
    end

    # Your modular application code goes here…
    end

    you need to remember to register the extension when building a modular style app.

    it has worked fine for me so far