sinatra-trails
Is a very thin Rails inspired route naming DSL for using with Sinatra apps. It doesn't monkeypatch or overrides any Sinatra::Base methods It provides helpers for generating routes for resources and single resource, namespaces and named routes.
Instalation
$ [sudo] gem install sinatra-trails
Usage
Basic
Named routes are generated with map, not passing :to option sets the path to be the same as the name:
require 'sinatra/trails'
class MyApp < Sinatra::Base
register Sinatra::Trails
map :dashboard
map :home, :to => '/'
map :post, :to => '/posts/:id'
end
MyApp.print_routes
# prints
# dashboard => /dashboard
# home => /
# post => /posts/:id
Routes can be define beforehand:
map :dashboard
# GET '/dashboard'
get route_for(:dashboard) do
...
end
Or when defining the action:
# GET '/dashboard'
get map(:dashboard) do
...
end
All defined routes will be available in the views and action blocks:
get map(:posts, :to => '/posts/:id') do
...
end
get map(:dashboard) do
path_for(:dashboard) # => '/dashboard'
url_for(:dashboard) # => 'http://www.example.com/dashboard'
path_for(:dashboard, :option => 'hi') # => '/dashboard?option=hi'
# all required params must be meet, an object can be passed to meet :id but it must respond to #to_param
path_for(:posts, 1) # => '/posts/1'
end
Namespaces
Passing a symbol namespaces both the path and the route name:
namespace :admin do
map(:dashboard)
end
route_for(:admin_dashboard) # => '/admin/dashboard'
Passing a string only namespaces the path:
namespace 'admin' do
map(:dashboard)
end
route_for(:dashboard) # => '/admin/dashboard'
Passing nil to namespace only sets a context:
namespace nil do
map(:dashboard)
end
route_for(:dashboard) # => '/dashboard'
Resources
Restful routes for plural resources can be generated as follows, inside the resource definition block a route can be accessed by its name as a method call or using the path_for method:
resources :users do
# GET /users
get users do
...
end
# POST /users
post users do
...
end
# GET /users/new
get new_user do
...
end
# GET /users/:id
get user do
...
end
# GET /users/:id/edit
get edit_user do
...
end
# PUT /users/:id
put user do
...
end
# DELETE /users/:id
delete user do
...
end
# generates new route with name :aprove_user
# GET /users/:id/aprove
get member(:aprove) do
...
end
# generates new route with name :aproved_users
# GET /users/aproved
get collection(:aproved) do
...
end
end
Route definition order for sinatra has precedence, in this case get(new_user)
must be defined before get(user)
As with previous examples routes can be defined beforehand:
resources :users
# GET '/users'
get path_for(:users) do
...
end
Nested Resources
Resources can be nested in a similar way as with Rails:
resources :users do
...
resources :comments do
# GET /users/:user_id/comments
get user_comments do
...
end
# GET /users/:user_id/comments/:id
get user_comment do
...
end
end
end
print_routes
# users => /users
# new_user => /users/new
# user => /users/:id
# edit_user => /users/:id/edit
# user_comments => /users/:user_id/comments
# new_user_comment => /users/:user_id/comments/new
# user_comment => /users/:user_id/comments/:id
# edit_user_comment => /users/:user_id/comments/:id/edit
And for actions that don't need to load the parent resource the route generation can be shallow:
resources :users, :shallow => true do
resources :comments
end
print_routes
# users => /users
# new_user => /users/new
# user => /users/:id
# edit_user => /users/:id/edit
# user_comments => /users/:user_id/comments
# new_user_comment => /users/:user_id/comments/new
# comment => /comments/:id
# edit_comment => /comments/:id/edit
Some keys to the params hash are added when routes are defined using sinatra-trails:
params[:resource] # the name of the resource in REST actions
params[:action] # the name of the action in REST actions
params[:namespace] # the name of the current namespace
Singleton Resource
Same principles apply for singleton resource:
resource :user
print_routes
# user => /user
# new_user => /user/new
# edit_user => /user/edit
Before and After Filters
Defining a filter within a context (namespace, resources, resource) without passing any path will execute that filter for all actions defined in that context:
namespace :admin do
before do
@admin = true
end
get map(:dashboard) do
@admin # => true
end
end
get map(:home, :to => '/') do
@admin # => nil
end
Within a context named routes, strings or regexps can be used as arguments for before and after filters:
resources :users do
before new_user, edit_user do
end
end
A symbol can be passed to the filter definition and it will be lazily evaluated against the routes within the context:
namespace :admin do
before :dashboard do
...
end
get map(:dashboard) do
...
end
end
Accessing Routes from Outside the App
On registering Sinatra::Trails a dynamic module for the Sinatra app is created and it is assigned to the constant Routes
including that module in another class gives that class access to the app's paths, a single class can access paths for
several Sinatra apps:
class MyApp < Sinatra::Base
register Sinatra::Trails
get map(:users) do
...
end
get map(:user, :to => '/users/:id') do
...
end
end
class OtherApp < Sinatra::Base
include MyApp::Routes
get 'index' do
redirect to path_for(:users)
end
end
class User < Sequel::Model
include MyApp::Routes
def to_param() id end
def route
path_for(:user, self) # => '/users/1'
end
end