What's New in Sinatra 0.9.0

By Ryan Tomayko on Sunday, January 18, 2009

This is the first in a series of 0.9.x releases designed to move Sinatra toward a rock solid 1.0. While we were able to add a touch of new hotness in this release, the major focus has been on getting the codebase shaped up for future development. Many longstanding bugs and minor annoyances were corrected along the way.

Sinatra’s internal classes and methods have changed significantly in this release. Most apps written for 0.3.x will work just fine under the new codebase but we’ve begun adding deprecation warnings for things slated to be ripped out completely in 1.0. Please test your apps before upgrading production environments to the 0.9.0 gem. We’re committed to keeping existing apps running through 0.9.x so report compatibility issues through the normal channels.

With that out of the way, here are some of the new features to look out for in 0.9.0.

Sinatra::Base and Proper Rack Citizenship

Sinatra can now be used to build modular / reusable Rack applications and middleware components. This means that multiple Sinatra applications can now be run in isolation and co-exist peacefully with other Rack based frameworks.

Requiring 'sinatra/base' instead of 'sinatra' causes a subset of Sinatra’s features to be loaded. No methods are added to the top-level and the command-line / auto-running features are disabled. Subclassing Sinatra::Base creates a Rack component with the familiar Sinatra DSL methods available in class scope. These classes can then be run as Rack applications or used as middleware components.

Proper documentation on this feature is in the works but here’s a quick example for illustration:

require 'sinatra/base'

class Foo < Sinatra::Base
  # all options are available for the setting:
  enable :static, :session
  set :root, File.dirname(__FILE__)

  # each subclass has its own private middleware stack:
  use Rack::Deflator

  # instance methods are helper methods and are available from
  # within filters, routes, and views:
  def em(text)
    "<em>#{text}</em>"
  end

  # routes are defined as usual:
  get '/hello/:person' do
    "Hello " + em(params[:person])
  end
end

That thing can be plugged in anywhere along a Rack pipeline. For instance, once Rails 2.3 ships, you’ll be able to use Sinatra apps to build Rails Metal.

Jesse Newland and Jon Crosby are already experimenting. Very hot.

Nested Params

Form parameters with subscripts are now parsed into a nested/recursive Hash structure. Here’s a form:

<form method='POST' action='/guestbook/'>
  <input type='text' name='person[name]'>
  <input type='text' name='person[email]'>
  <textarea name='note'></textarea>
</form>

It looks like this on the wire:

person[name]=Frank&person[email]=frank@theritz.com&message=Stay%20cool

Sinatra turns it into a nested Hash structure when accessed through the params method:

post '/guestbook/' do
  params[:person]    # => { :name => 'Frank', :email => 'frank@theritz.com' }
  "Hi #{person[:name]}! Thanks for signing my guestbook."
end

This was a massively popular feature requests. Thanks to Nicolás Sanguinetti for the patch and Michael Fellinger for the original implementation.

Routing with Regular Expressions

The route declaration methods (get, put, post, put, delete) now take a Regexp as a pattern. Captures are made available to the route block at params[:captures]:

get %r{/foo/(bar|baz)/(\d+)} do
  # assuming: GET /foo/bar/42
  params[:captures]  # => ['bar', 42]
end

Passing on a Route

We added a new request-level pass method that immediately exits the current block and passes control to the next matching route. For example:

get '/shoot/:person' do
  pass unless %w[Kenny Sherrif].include?(params[:person])
  "You shot #{params[:person]}."
end

get '/shoot/*' do
  "Missed!"
end

If no matching route is found after a pass, a NotFound exception is raised and the application 404s as if no route had matched in the first place.

Refined Test Framework

Sinatra’s testing support no longer depends on Test::Unit (or any specific test framework for that matter). Requiring 'sinatra/test' brings in the Sinatra::Test module and the Sinatra::TestHarness class, which can be used as necessary to simulate requests and make assertions about responses.

You can also require sinatra/test/unit, sinatra/test/spec, sinatra/test/rspec, or sinatra/test/bacon to setup a framework-specific testing environment. See the section on “Testing” in the README for examples.

More

See the CHANGES file for a comprehensive list of enhancements, bug fixes, and deprecations.