Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Ruby HTTP router for Lotus
Ruby

README.md

Lotus::Router

Rack compatible, lightweight and fast HTTP Router for Ruby and Lotus.

Status

Gem Version Build Status Coverage Code Climate Dependencies Inline docs

Contact

Rubies

Lotus::Router supports Ruby (MRI) 2+, JRuby 1.7 (with 2.0 mode)

Installation

Add this line to your application's Gemfile:

gem 'lotus-router'

And then execute:

$ bundle

Or install it yourself as:

$ gem install lotus-router

Getting Started

require 'lotus/router'

app = Lotus::Router.new do
  get '/', to: ->(env) { [200, {}, ['Welcome to Lotus::Router!']] }
end

Rack::Server.start app: app, Port: 2300

Usage

Lotus::Router is designed to work as a standalone framework or within a context of a Lotus application.

For the standalone usage, it supports neat features:

A Beautiful DSL:

Lotus::Router.new do
  get '/', to: ->(env) { [200, {}, ['Hi!']] }
  get '/dashboard',   to: Dashboard::Index
  get '/rack-app',    to: RackApp.new
  get '/flowers',     to: 'flowers#index'
  get '/flowers/:id', to: 'flowers#show'

  redirect '/legacy', to: '/'

  mount Api::App, at: '/api'

  namespace 'admin' do
    get '/users', to: Users::Index
  end

  resource 'identity' do
    member do
      get '/avatar'
    end

    collection do
      get '/api_keys'
    end
  end

  resources 'robots' do
    member do
      patch '/activate'
    end

    collection do
      get '/search'
    end
  end
end

Fixed string matching:

router = Lotus::Router.new
router.get '/lotus', to: ->(env) { [200, {}, ['Hello from Lotus!']] }

String matching with variables:

router = Lotus::Router.new
router.get '/flowers/:id', to: ->(env) { [200, {}, ["Hello from Flower no. #{ env['router.params'][:id] }!"]] }

Variables Constraints:

router = Lotus::Router.new
router.get '/flowers/:id', id: /\d+/, to: ->(env) { [200, {}, [":id must be a number!"]] }

String matching with globbing:

router = Lotus::Router.new
router.get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] }

String matching with optional tokens:

router = Lotus::Router.new
router.get '/lotus(.:format)' to: ->(env) { [200, {}, ["You've requested #{ env['router.params'][:format] }!"]] }

Support for the most common HTTP methods:

router   = Lotus::Router.new
endpoint = ->(env) { [200, {}, ['Hello from Lotus!']] }

router.get    '/lotus', to: endpoint
router.post   '/lotus', to: endpoint
router.put    '/lotus', to: endpoint
router.patch  '/lotus', to: endpoint
router.delete '/lotus', to: endpoint
router.trace  '/lotus', to: endpoint

Redirect:

router = Lotus::Router.new
router.get '/redirect_destination', to: ->(env) { [200, {}, ['Redirect destination!']] }
router.redirect '/legacy', to: '/redirect_destination'

Named routes:

router = Lotus::Router.new(scheme: 'https', host: 'lotusrb.org')
router.get '/lotus', to: ->(env) { [200, {}, ['Hello from Lotus!']] }, as: :lotus

router.path(:lotus) # => "/lotus"
router.url(:lotus)  # => "https://lotusrb.org/lotus"

Namespaced routes:

router = Lotus::Router.new
router.namespace 'animals' do
  namespace 'mammals' do
    get '/cats', to: ->(env) { [200, {}, ['Meow!']] }, as: :cats
  end
end

# and it generates:

router.path(:animals_mammals_cats) # => "/animals/mammals/cats"

Mount Rack applications:

Lotus::Router.new do
  mount RackOne,                             at: '/rack1'
  mount RackTwo,                             at: '/rack2'
  mount RackThree.new,                       at: '/rack3'
  mount ->(env) {[200, {}, ['Rack Four']]},  at: '/rack4'
  mount 'dashboard#index',                   at: '/dashboard'
end
  1. RackOne is used as it is (class), because it respond to .call
  2. RackTwo is initialized, because it respond to #call
  3. RackThree is used as it is (object), because it respond to #call
  4. That Proc is used as it is, because it respond to #call
  5. That string is resolved as Dashboard::Index (Lotus::Controller integration)

Duck typed endpoints:

Everything that responds to #call is invoked as it is:

router = Lotus::Router.new
router.get '/lotus',      to: ->(env) { [200, {}, ['Hello from Lotus!']] }
router.get '/middleware', to: Middleware
router.get '/rack-app',   to: RackApp.new
router.get '/method',     to: ActionControllerSubclass.action(:new)

If it's a string, it tries to instantiate a class from it:

class RackApp
  def call(env)
    # ...
  end
end

router = Lotus::Router.new
router.get '/lotus', to: 'rack_app' # it will map to RackApp.new

It also supports Controller + Action syntax:

module Flowers
  class Index
    def call(env)
      # ...
    end
  end
end

router = Lotus::Router.new
router.get '/flowers', to: 'flowers#index' # it will map to Flowers::Index.new

Implicit Not Found (404):

router = Lotus::Router.new
router.call(Rack::MockRequest.env_for('/unknown')).status # => 404

Controllers:

Lotus::Router has a special convention for controllers naming. It allows to declare an action as an endpoint, with a special syntax: <controller>#<action>.

Lotus::Router.new do
  get '/', to: 'welcome#index'
end

In the example above, the router will look for the Welcome::Index action.

Namespaces

In applications where for maintainability or technical reasons, this convention can't work, Lotus::Router can accept a :namespace option, which defines the Ruby namespace where to look for actions.

For instance, given a Lotus full stack application called Bookshelf, the controllers are available under Bookshelf::Controllers.

Lotus::Router.new(namespace: Bookshelf::Controllers) do
  get '/', to: 'welcome#index'
end

In the example above, the router will look for the Bookshelf::Controllers::Welcome::Index action.

RESTful Resource:

router = Lotus::Router.new
router.resource 'identity'

It will map:

Verb Path Action Name Named Route
GET /identity Identity::Show :show :identity
GET /identity/new Identity::New :new :new_identity
POST /identity Identity::Create :create :identity
GET /identity/edit Identity::Edit :edit :edit_identity
PATCH /identity Identity::Update :update :identity
DELETE /identity Identity::Destroy :destroy :identity

If you don't need all the default endpoints, just do:

router = Lotus::Router.new
router.resource 'identity', only: [:edit, :update]

#### which is equivalent to:

router.resource 'identity', except: [:show, :new, :create, :destroy]

If you need extra endpoints:

router = Lotus::Router.new
router.resource 'identity' do
  member do
    get 'avatar'           # maps to Identity::Avatar
  end

  collection do
    get 'authorizations'   # maps to Identity::Authorizations
  end
end

router.path(:avatar_identity)         # => /identity/avatar
router.path(:authorizations_identity) # => /identity/authorizations

Configure controller:

router = Lotus::Router.new
router.resource 'profile', controller: 'identity'

router.path(:profile) # => /profile # Will route to Identity::Show

RESTful Resources:

router = Lotus::Router.new
router.resources 'flowers'

It will map:

Verb Path Action Name Named Route
GET /flowers Flowers::Index :index :flowers
GET /flowers/:id Flowers::Show :show :flowers
GET /flowers/new Flowers::New :new :new_flowers
POST /flowers Flowers::Create :create :flowers
GET /flowers/:id/edit Flowers::Edit :edit :edit_flowers
PATCH /flowers/:id Flowers::Update :update :flowers
DELETE /flowers/:id Flowers::Destroy :destroy :flowers
router.path(:flowers)              # => /flowers
router.path(:flowers, id: 23)      # => /flowers/23
router.path(:edit_flowers, id: 23) # => /flowers/23/edit

If you don't need all the default endpoints, just do:

router = Lotus::Router.new
router.resources 'flowers', only: [:new, :create, :show]

#### which is equivalent to:

router.resources 'flowers', except: [:index, :edit, :update, :destroy]

If you need extra endpoints:

router = Lotus::Router.new
router.resources 'flowers' do
  member do
    get 'toggle' # maps to Flowers::Toggle
  end

  collection do
    get 'search' # maps to Flowers::Search
  end
end

router.path(:toggle_flowers, id: 23)  # => /flowers/23/toggle
router.path(:search_flowers)          # => /flowers/search

Configure controller:

router = Lotus::Router.new
router.resources 'blossoms', controller: 'flowers'

router.path(:blossoms, id: 23) # => /blossoms/23 # Will route to Flowers::Show

Testing

require 'lotus/router'
require 'rack/request'

router = Lotus::Router.new do
  get '/', to: ->(env) { [200, {}, ['Hi!']] }
end

app = Rack::MockRequest.new(router)
app.get('/') # => #<Rack::MockResponse:0x007fc4540dc238 ...>

Versioning

Lotus::Router uses Semantic Versioning 2.0.0

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Acknowledgements

Thanks to Joshua Hull (@joshbuddy) for his http_router.

Copyright

Copyright © 2014-2015 Luca Guidi – Released under MIT License

Something went wrong with that request. Please try again.