A tiny vendorable router that makes it easy to try routes from a number of different modular Sinatra applications.
The motivation behind the project is to provide an easy way of composing a larger application that's been split out into a number of discrete Sinatra apps for purposes of code modularity and isolation.
In your Gemfile
:
gem 'sinatra-router'
Now as part of a builder or rackup (i.e. config.ru
):
require "sinatra"
require "sinatra/router"
module API
class Apps < Sinatra::Base
get "/apps" do
200
end
end
class Users < Sinatra::Base
get "/users" do
200
end
end
end
# config.ru
run Sinatra::Router.new do
mount API::Apps # /apps
mount API::Users # /users
end
Or mount it as middleware:
use Sinatra::Router do
mount API::Apps
mount API::Users
end
run Sinatra::Application
Why not just mount Sinatra apps as middleware?
An alternative is to just mount Sinatra apps as middleware, which is supported by Sinatra out of the box:
run Rack::Builder.new do
use API::Apps
use API::Users
end
This does get you most of the way there, but may have undesirable side effects. For example, a request always gets passed through each middleware, whether that middleware can handle the route or not. So before
filters in all your apps will be run until one app in the stack successfully handles the request. This can make it somewhat more difficult to modularize your app.
Conditional Routing
Add routing conditions with arguments or blocks:
run Sinatra::Router.new do
with_conditions(lambda { |e| e["HTTP_X_VERSION"] == "2" }) do
mount API::V2::Apps
mount API::V2::Users
end
mount API::V1::Users, lambda { |e| e["HTTP_X_VERSION"] == "1" }
end
Or extend the router class to create your own concise DSL:
module API
class Router < Sinatra::Router
def version(version, &block)
condition = lambda { |e| version.to_s == e["HTTP_X_VERSION"] }
if block
with_conditions(condition, &block)
else
condition
end
end
end
end
# config.ru
run API::Router do
version 2 do
mount API::V2::Apps
mount API::V2::Users
end
mount API::V1::Users, version(1)
end
Passing and X-Cascade
Sinatra and sinatra-router support Rack's X-Cascade
standard so that modules are able to transparently pass from one to the other as if they were part of the same application:
module API
module V1
class Apps < Sinatra::Base
get "/apps" do
# drops through to AppsV2 GET / unless request is version 1
pass unless version == 1
200
end
end
end
module V2
class Apps < Sinatra::Base
get "/apps" do
200
end
end
end
end
# config.ru
run Sinatra::Router.new do
mount API::V1::Apps
mount API::V2::Apps
end
Development
Run the tests:
bundle exec ruby test/sinatra/router_test.rb
Release
-
Update the version in
sinatra-router.gemspec
as appropriate for semantic versioning and add details toCHANGELOG
. -
Run the
release
task:bundle exec rake release