The beauty of Lotus is the composition of its frameworks. Each of them is well designed to achieve one and only one goal. The main advantage of this architecture is that delevopers can easily use and reuse those frameworks in countless ways.
Lotus::Router accepts anonymous functions as endpoints. This feature can be used to build Sinatra with it.
Initial setup
We need to setup a Gemfile
with:
source 'https://rubygems.org'
gem 'lotus-router'
As second step, we create an Hello World application with Lotus::Router (run with rackup application.rb
):
require 'rubygems'
require 'bundler/setup'
require 'lotus/router'
Application = Rack::Builder.new do
app = Lotus::Router.new do
get '/' do
[200, {}, ['Hello, World!']]
end
end
run app
end.to_app
Return value of the block as response body
You may have noticed a discrepancy between the typical Sinatra usage and the example above: the framework sets the return value of that endpoint as the body of the response, here we’re returning a serialized Rack response.
Internally, Lotus::Router uses Lotus::Routing::Endpoint to wrap application’s endpoints.
They can be any type of object that respond to #call
, and it’s up to us to return a Rack::Response
.
In our case, we have just a string, if we inherit from that class, we can wrap the body in a proper response:
class Endpoint < Lotus::Routing::Endpoint
def call(env)
[200, {}, [super]]
end
end
The next step is to use this endpoint.
Lotus::Router uses a specific set of rules to understand which endpoint needs to be associated with a given path.
For instance, when you write get '/dashboard', to: 'dashboard#index'
, that :to
option is processed and the router will look for a DashboardController::Index
class.
Those conventions are implemented by Lotus::Routing::EndpointResolver, which is used as default resolver.
If you want to use a different policy, or customize the way it works, pass your own resolver to the router constructor (:resolver
option).
We want to use the defaults, and only specify to usa of our custom endpoint.
require 'rubygems'
require 'bundler/setup'
require 'lotus/router'
class Endpoint < Lotus::Routing::Endpoint
def call(env)
[200, {}, [super]]
end
end
r = Lotus::Routing::EndpointResolver.new(endpoint: Endpoint)
Application = Rack::Builder.new do
app = Lotus::Router.new(resolver: r) do
get '/' do
'Hello, World!'
end
end
run app
end.to_app
Request params
Now that we have mimicked the simplest Sinatra usage, let’s have a look at the next example: request params.
Endpoint
is agnostic, it’s part of an HTTP router, that’s why it passes the complete Rack env
to the real endpoint that it wraps.
Instead, we want to use only the tokens coming from the URL. This is really simple to do:
require 'rubygems'
require 'bundler/setup'
require 'lotus/router'
class Endpoint < Lotus::Routing::Endpoint
def call(env)
[200, {}, [super(params(env))]]
end
private
def params(env)
env.fetch('router.params')
end
end
r = Lotus::Routing::EndpointResolver.new(endpoint: Endpoint)
Application = Rack::Builder.new do
app = Lotus::Router.new(resolver: r) do
get '/' do
'Hello, World!'
end
get '/greet/:planet' do |params|
"Hello from the #{ params[:planet] }!"
end
end
run app
end.to_app
A step further
What we did until now it’s great but noisy.
We want to extract the boilerplate code into a separated file.
I’ve prepared a microgem to be used with our Gemfile
.
source 'https://rubygems.org'
gem 'lotus-sinatra', git: 'https://gist.github.com/8665228.git'
Now we can leave that beautiful DSL alone.
require 'rubygems'
require 'bundler/setup'
require 'lotus-sinatra'
get '/' do
'Hello, World!'
end
get '/greet/:planet' do |params|
"Hello from the #{ params[:planet] }!"
end
Conclusion
This example confirms how valuable is the separation between Lotus frameworks and that Dependency Injection is a virtue.
{% include _lotusml.html %}