Google
I've been using Monk with the armonk skeleton for the last couple of weeks to build a little app that monitors Craig's List searches. Bebop aside, I've found it to be a really refreshing experience, but I knew when I started building the app that the vanilla routing DSL wasn't going to meet my needs (it wasn't built too!). I also knew I would end up missing the path helpers, targeted filters, resource nesting, and printable routing information from Rails. So! after mocking up a simple DSL and consulting some friends I built and tested this little Sinatra routing extension and dubbed it Bebop in honor of Thelonius Monk.

Examples

Once registered Bebop provides the resource class method on Sinatra::Base as the starting point for building your routes.
require 'sinatra'
require 'bebop'

class MyApp < Sinatra::Base
  register Bebop

  resource :foos do |foos|

    # GET /foos/hello
    foos.get '/hello' do
      'hello world'
    end

    # GET '/foos/goodbye'
    foos.get '/goodbye' do
      'goodbye'
    end
  end
end

Routing Helpers

As illustrated, Bebop can be used for nothing more than prefixing the routes generated by the normal dsl methods with a resource name ('/foos' in this case). It also provides route helper methods that build RESTful routes for the actions index, new, create, show, edit, update, and destroy.
class MyApp < Sinatra::Base
  register Bebop

  resource :foos do |foos|

    # Get /foos
    foos.index {}

    # GET /foos/:foo_id
    foos.show {}

    # GET /foos/new
    foos.new {}

    # POST /foos
    foos.create {}

    # PUT /foos/:foo_id
    foos.update {}

    # GET /foos/:foo_id/edit
    foos.edit {}

    # DELETE /foos/:foo_id
    foos.destroy {}
  end
end
And if you want to nest your resources bebop handles prepending the routes correctly:
class MyApp < Sinatra::Base
  register Bebop

  resource :foos do |foos|

    foos.resource :bars do |bars|

      # Get /foos/:foos_id/bars
      foos.index {}

      # GET /foos/:foo_id/bars/:bar_id
      foos.show {}
    end
  end
end

Extras

Bebop also extends some of the basic Sinatra functionality like before and after filters and provides new functionality in the form of path helper methods. Filters can target specific routes based on identifier, all routes, or nested resource routes. The only stipulation is that the filters must be defined _before_ the filtered route in the body of the resource block.
class MyApp < Sinatra::Base
  register Bebop

  resource :foos do |foos|

    # To run a filter before all routes
    #
    foos.before :all do
      @all = 'all'
    end

    # To have your filter run before a specific route, the route must be one of the seven helper
    # routes (see example/routes.rb) or specify the :identifier parameter
    #
    foos.before :new do
      @new = 'new'
    end

    foos.new do
      "#{@all} #{@new}" # => 'all new'
    end

    # You can target the vanila methods by providing the :identifier hash option
    #
    # GET /foos/baz
    foos.get '/baz', :identifier => :new do
      @new # => 'new'
    end

    # You can also specify a before filter for nested routes by using the child resource name
    # 
    foos.before :bars do
      @bars = 'some bars'
    end

    foos.resource :bars do |bars|
      bars.get '/some_bars' do
        @bars # => 'some bars'
      end
    end

    foos.get '/bak' do
      @bars # => nil
    end

    # Finally you can specify many different methods in your filters by passing many identifiers
    # 
    foos.before :bak, :baz do
      @bak_baz = "bak 'n' baz"
    end

    foos.get('/something', :identifier => :bak) { @bak_baz }
    foos.get('/anything', :identifier => :baz) { @bak_baz }
  end
end
Path helpers follow a simple naming convention that starts with the original parent resource, all the child resources ordered by nesting, and finishing with the action identifier.
class MyApp < Sinatra::Base
  register Bebop

  resource :foos do |foos|

    foos.resource :bars do |bars|

      # GET /foos/:foo_id/bars/:bar_id/edit
      bars.edit do
        "foo: #{params[:foo_id]} bar: #{params[:bar_id]}"
      end

      bars.get '/redirect' do

        # Redirects to /foos/1/bars/2/edit
        redirect foos_bars_edit_path(1, 2)
      end
    end
  end
end

Code

Most of the above examples are borrowed from the examples directory in the gem if you are interested. The repo can be found at github, and you can install the gem right now as long as gemcutter.org is one of your sources. As suggested by Damian Janowski I'll be adding some template helpers and some other goodies soon. As always feedback and suggestions are welcome!

Published

10 Jan 2010

Tags