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 setting 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.