Building Sinatra with Lotus


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 %}

Luca Guidi

Family man, software architect, Open Source indie developer, speaker.

Rome, Italy https://lucaguidi.com