Home

Awesome

sinatra-router Build Status

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

  1. Update the version in sinatra-router.gemspec as appropriate for semantic versioning and add details to CHANGELOG.

  2. Run the release task:

    bundle exec rake release