# Lotus::Router Rack compatible, lightweight and fast HTTP Router for Ruby and [Lotus](http://lotusrb.org). ## Status [![Gem Version](http://img.shields.io/gem/v/lotus-router.svg)](https://badge.fury.io/rb/lotus-router) [![Build Status](http://img.shields.io/travis/lotus/router/master.svg)](https://travis-ci.org/lotus/router?branch=master) [![Coverage](http://img.shields.io/coveralls/lotus/router/master.svg)](https://coveralls.io/r/lotus/router) [![Code Climate](http://img.shields.io/codeclimate/github/lotus/router.svg)](https://codeclimate.com/github/lotus/router) [![Dependencies](http://img.shields.io/gemnasium/lotus/router.svg)](https://gemnasium.com/lotus/router) [![Inline docs](http://inch-ci.org/github/lotus/router.png)](http://inch-ci.org/github/lotus/router) ## Contact * Home page: http://lotusrb.org * Mailing List: http://lotusrb.org/mailing-list * API Doc: http://rdoc.info/gems/lotus-router * Bugs/Issues: https://github.com/lotus/router/issues * Support: http://stackoverflow.com/questions/tagged/lotus-ruby * Chat: https://gitter.im/lotus/chat ## Rubies __Lotus::Router__ supports Ruby (MRI) 2+, JRuby 1.7 (with 2.0 mode) ## Installation Add this line to your application's Gemfile: ```ruby gem 'lotus-router' ``` And then execute: ```shell $ bundle ``` Or install it yourself as: ```shell $ gem install lotus-router ``` ## Getting Started ```ruby 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](http://lotusrb.org) application. For the standalone usage, it supports neat features: ### A Beautiful DSL: ```ruby 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: ```ruby router = Lotus::Router.new router.get '/lotus', to: ->(env) { [200, {}, ['Hello from Lotus!']] } ``` ### String matching with variables: ```ruby router = Lotus::Router.new router.get '/flowers/:id', to: ->(env) { [200, {}, ["Hello from Flower no. #{ env['router.params'][:id] }!"]] } ``` ### Variables Constraints: ```ruby router = Lotus::Router.new router.get '/flowers/:id', id: /\d+/, to: ->(env) { [200, {}, [":id must be a number!"]] } ``` ### String matching with globbing: ```ruby router = Lotus::Router.new router.get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] } ``` ### String matching with optional tokens: ```ruby 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: ```ruby 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: ```ruby router = Lotus::Router.new router.get '/redirect_destination', to: ->(env) { [200, {}, ['Redirect destination!']] } router.redirect '/legacy', to: '/redirect_destination' ``` ### Named routes: ```ruby 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: ```ruby 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: ```ruby 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](https://github.com/lotus/controller) integration) ### Duck typed endpoints: Everything that responds to `#call` is invoked as it is: ```ruby 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: ```ruby 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: ```ruby 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): ```ruby 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: `#`. ```ruby 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`. ```ruby 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: ```ruby 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: ```ruby 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: ```ruby 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: ```ruby router = Lotus::Router.new router.resource 'profile', controller: 'identity' router.path(:profile) # => /profile # Will route to Identity::Show ``` #### Nested Resources We can nest resource(s): ```ruby router = Lotus::Router.new router.resource :identity do resource :avatar resources :api_keys end router.path(:identity_avatar) # => /identity/avatar router.path(:new_identity_avatar) # => /identity/avatar/new router.path(:edit_identity_avatar) # => /identity/avatar/new router.path(:identity_api_keys) # => /identity/api_keys router.path(:identity_api_key) # => /identity/api_keys/:id router.path(:new_identity_api_key) # => /identity/api_keys/new router.path(:edit_identity_api_key) # => /identity/api_keys/:id/edit ``` ### RESTful Resources: ```ruby 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 :flower
GET /flowers/new Flowers::New :new :new_flower
POST /flowers Flowers::Create :create :flowers
GET /flowers/:id/edit Flowers::Edit :edit :edit_flower
PATCH /flowers/:id Flowers::Update :update :flower
DELETE /flowers/:id Flowers::Destroy :destroy :flower
```ruby router.path(:flowers) # => /flowers router.path(:flower, id: 23) # => /flowers/23 router.path(:edit_flower, id: 23) # => /flowers/23/edit ``` If you don't need all the default endpoints, just do: ```ruby 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: ```ruby 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_flower, id: 23) # => /flowers/23/toggle router.path(:search_flowers) # => /flowers/search ``` Configure controller: ```ruby router = Lotus::Router.new router.resources 'blossoms', controller: 'flowers' router.path(:blossom, id: 23) # => /blossoms/23 # Will route to Flowers::Show ``` #### Nested Resources We can nest resource(s): ```ruby router = Lotus::Router.new router.resources :users do resource :avatar resources :favorites end router.path(:user_avatar, user_id: 1) # => /users/1/avatar router.path(:new_user_avatar, user_id: 1) # => /users/1/avatar/new router.path(:edit_user_avatar, user_id: 1) # => /users/1/avatar/edit router.path(:user_favorites, user_id: 1) # => /users/1/favorites router.path(:user_favorite, user_id: 1, id: 2) # => /users/1/favorites/2 router.path(:new_user_favorites, user_id: 1) # => /users/1/favorites/new router.path(:edit_user_favorites, user_id: 1, id: 2) # => /users/1/favorites/2/edit ``` ## Testing ```ruby require 'lotus/router' require 'rack/request' router = Lotus::Router.new do get '/', to: ->(env) { [200, {}, ['Hi!']] } end app = Rack::MockRequest.new(router) app.get('/') # => # ``` ## Versioning __Lotus::Router__ uses [Semantic Versioning 2.0.0](http://semver.org) ## 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](https://github.com/joshbuddy)) for his [http_router](http://rubygems.org/gems/http_router). ## Copyright Copyright © 2014-2015 Luca Guidi – Released under MIT License