Singing With Sinatra
I attended a couple Ruby sessions at CodeMash this year which really piqued my interest level in Ruby.
For many people (especially web developers) when you hear about Ruby they think Ruby on Rails. While Rails is an impressive framework, there are some other interesting options available. One of them that recently caught my attention (and admiration) is called Sinatra.
Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort.
Sinatra is a drop dead simple way to create a small site, blog, service, or even a prototype. Here is all it takes to serve a web request with Sinatra:
require 'rubygems'
require 'sinatra'
get '/hi' do
"Hello World!"
end
While the code above returns the UI inline, there is full support for templates, layouts, and much more.
If you are new to Ruby and want to see what you can do with the language with almost no effort, Sinatra is a great place to start.
I have been hacking around on it for a couple of days and decided to write a simple plugin. While I am sure I will cringe at this code a couple of months from now, I figured I would post it see if it is useful to others.
Goal:
Ensure urls served by the application are consistent by requiring no trailing slash and requiring all urls to be lower cased. If either one of these conditions are not met, the plugin will do a 301 redirect to the proper url.
Here is the actual plugin:
require 'sinatra/base'
module Sinatra
module ConsistentUrls
def validate_url_requests()
before {
path = request.path.downcase
path_to_redirect = nil
#if the path ends in '/' remove it
path_to_redirect = path[0..-2] if /.+\/$/.match(path)
#if the original path was not lower case. NOTE: we lowercase this above to minimize steps
path_to_redirect = path if !path_to_redirect && path != request.path
#if we need to redirect, build a query_string
if(path_to_redirect)
query_string = hash_to_querystring(request.params)
path_to_redirect << ("?" + query_string) if query_string
end
redirect path_to_redirect, 301 if path_to_redirect
}
end
#borrowed from http://justanothercoder.wordpress.com/2009/04/24/converting-a-hash-to-a-query-string-in-ruby/
def hash_to_querystring(hash)
hash.keys.inject('') do |query_string, key|
query_string << '&' unless key == hash.keys.first
query_string << "#{URI.encode(key.to_s)}=#{URI.encode(hash[key])}"
end
end
end
register ConsistentUrls
end
A minimal application skeleton. The key line is the validate_url_requests which is what invokes the plugin.
require 'rubygems'
require 'sinatra'
require 'consistenturls'
#plugin
validate_url_requests
get '/' do
"Hello World"
end
get %r{(.+)} do |catch_all|
"I am the catch all #{catch_all}#{request.url}"
end
not_found do
"The path #{request.url} does not exist"
end
A set of tests.
require 'demo_app'
require 'test/unit'
require 'rack/test'
set :environment, :test
class ConsistentUrlsTests < Test::Unit::TestCase
include Rack::Test::Methods
def app
@app || Sinatra::Application
end
def test_urls_with_trailing_slashes_will_be_redirected
execute_url_test('/some-random-url/', '/some-random-url')
execute_url_test('/some-random-url/second-path/', '/some-random-url/second-path')
end
def test_urls_with_trailing_slashes_and_querystrings_will_be_redirected
execute_url_test('/some-random-url/?abc=123', '/some-random-url?abc=123')
execute_url_test('/some-random-URL/?abc=123&z=abc', '/some-random-url?abc=123&z=abc')
end
def test_case_sensitive_url_will_be_redirected
execute_url_test('/CAPS-LOCK-ROCKS', '/caps-lock-rocks')
end
def test_case_sensitive_url_with_trailing_slash_will_be_redirected
execute_url_test('/CAPS-LOCK-ROCKS/', '/caps-lock-rocks')
end
def test_case_sensitive_url_with_query_string_will_be_redirected
execute_url_test('/CAPS-LOCK-ROCKS?abc=123&def=HIJ', '/caps-lock-rocks?abc=123&def=HIJ')
execute_url_test('/CAPS-LOCK-ROCKS/ROCKS/It/ROCKs/?abc=123&def=HIJ', '/caps-lock-rocks/rocks/it/rocks?abc=123&def=HIJ')
end
def execute_url_test(url_to_test, expected_redirect)
get url_to_test
follow_redirect!
assert_equal last_request.url, 'http://example.org' + expected_redirect
end
end
Getting started:
Setup steps for Sinatra can be found here.
Two things to note.
1. To run the tests, you will need to get rack-test. I mistakenly assumed this part of Rack which tripped me up a bit.
2. You need to grab Shotgun if you want to have the site reloaded anytime you make a change. This requirement was apparently something that changed recently. It is documented nicely on the Sinatra site, but seems to catch people off guard.
