New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

lotus-router

Package Overview
Dependencies
Maintainers
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

lotus-router

  • 0.5.1
  • Rubygems
  • Socket score

Version published
Maintainers
3
Created
Source

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 9k+

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:

VerbPathActionNameNamed Route
GET/identityIdentity::Show:show:identity
GET/identity/newIdentity::New:new:new_identity
POST/identityIdentity::Create:create:identity
GET/identity/editIdentity::Edit:edit:edit_identity
PATCH/identityIdentity::Update:update:identity
DELETE/identityIdentity::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
Nested Resources

We can nest resource(s):

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:

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

It will map:

VerbPathActionNameNamed Route
GET/flowersFlowers::Index:index:flowers
GET/flowers/:idFlowers::Show:show:flower
GET/flowers/newFlowers::New:new:new_flower
POST/flowersFlowers::Create:create:flowers
GET/flowers/:id/editFlowers::Edit:edit:edit_flower
PATCH/flowers/:idFlowers::Update:update:flower
DELETE/flowers/:idFlowers::Destroy:destroy:flower
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:

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_flower, id: 23)  # => /flowers/23/toggle
router.path(:search_flowers)         # => /flowers/search

Configure controller:

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):

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

Body Parsers

Rack ignores request bodies unless they come from a form submission. If we have a JSON endpoint, the payload isn't available in the params hash:

Rack::Request.new(env).params # => {}

This feature enables body parsing for specific MIME Types. It comes with a built-in JSON parser and allows to pass custom parsers.

JSON Parsing
require 'lotus/router'

endpoint = ->(env) { [200, {},[env['router.params'].inspect]] }

router = Lotus::Router.new(parsers: [:json]) do
  patch '/books/:id', to: endpoint
end
curl http://localhost:2300/books/1    \
  -H "Content-Type: application/json" \
  -H "Accept: application/json"       \
  -d '{"published":"true"}'           \
  -X PATCH

# => [200, {}, ["{:published=>\"true\",:id=>\"1\"}"]]

If the json can't be parsed an exception is raised:

Lotus::Routing::Parsing::BodyParsingError
Custom Parsers
require 'lotus/router'

# See Lotus::Routing::Parsing::Parser
class XmlParser
  def mime_types
    ['application/xml', 'text/xml']
  end

  # Parse body and return a Hash
  def parse(body)
    # parse xml
  rescue SomeXmlParsingError => e
    raise Lotus::Routing::Parsing::BodyParsingError.new(e)
  end
end

endpoint = ->(env) { [200, {},[env['router.params'].inspect]] }

router = Lotus::Router.new(parsers: [XmlParser.new]) do
  patch '/authors/:id', to: endpoint
end
curl http://localhost:2300/authors/1 \
  -H "Content-Type: application/xml" \
  -H "Accept: application/xml"       \
  -d '<name>LG</name>'               \
  -X PATCH

# => [200, {}, ["{:name=>\"LG\",:id=>\"1\"}"]]

Testing

require 'lotus/router'

router = Lotus::Router.new do
  get '/books/:id', to: 'books#show', as: :book
end

route = router.recognize('/books/23')
route.verb      # "GET"
route.action    # => "books#show"
route.params    # => {:id=>"23"}
route.routable? # => true

route = router.recognize(:book, id: 23)
route.verb      # "GET"
route.action    # => "books#show"
route.params    # => {:id=>"23"}
route.routable? # => true

route = router.recognize('/books/23', method: :post)
route.verb      # "POST"
route.routable? # => false

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 © 2014-2016 Luca Guidi – Released under MIT License

FAQs

Package last updated on 19 Jan 2016

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc