A Survey of Non-Rails Frameworks in Ruby: Cuba, Sinatra, Padrino, Lotus

DevelopmentIndustry

Reading Time: 11 minutes

It’s common for a Ruby developer to describe themselves as a Rails developer. It’s also common for someone’s entire Ruby experience to be through Rails. Rails began in 2003 by David Heinemeier Hansson, quickly becoming the most popular web framework and also serving as an introduction to Ruby for many developers.

Rails is great. I use it every day for my job, it was my introduction to Ruby, and this is the most fun I’ve ever had as a developer. That being said, there are a number of great framework options out there that aren’t Rails.

This article intends to highlight the differences between Cuba, Sinatra, Padrino, Lotus, and how they compare to or differ from Rails. Let’s have a look at some Non-Rails Frameworks in Ruby.

The Format

First things first, we’re going to take a quick look at Rack, which all of these frameworks (including Rails) are built on top of. After that we’ll compare them using MVC as the basis of comparison, but we will also talk about their router. Finally we will quickly discuss when it might be appropriate to choose one over the other.

The Foundation: Rack

Rack provides a minimal interface between web servers that support Ruby and Ruby frameworks.

Rack has really been what has helped the Ruby community have such a large number of both webservers and frameworks. It allows them to communicate with each other in a standard way. A webserver like Puma, Unicorn, or Passenger only has to be built for just one interface: Rack. As long as the framework is also built upon Rack, it’s able to work with any one of the webservers out there. All of the frameworks I’ll be discussing in this article are built on top of Rack, and Rails is no different.

A minimal Rack application contains an object which responds to the call method. This method should return an array with three elements: an HTTP status code, a hash of HTTP headers, and the body of the response. Essentially what a framework does is to help you organize and manage creating responses that get converted into this format.

Here is the smallest Rack application:

# config.ru
run Proc.new { |env| ['200', {'Content-Type' => 'text/html'}, ['Hello, Rack']] }

By running the rackup command (which will start a webserver), we can actually reach this endpoint from the browser or via curl.

rackup config.ru

Now we can reach it from curl:

-> curl 127.0.0.1:9292
Hello, Rack

Cuba

The tagline for Cuba is “Ceci n’est pas un framework,” which translates to “This isn’t a framework.” You might ask yourself why it’s even in the list, but luckily just below that phrase it says that Cuba is in fact a micro framework. That’s why I have it listed first, just above Rack.

Cuba was written by Michel Martens with the goal of following a minimalist philosophy and only providing what’s needed rather than some of the bloat and unused features that come along with much larger frameworks. Cuba is small, lightweight, and fast.

Router

Cuba provides a small router that allows you to define routes using its DSL.

Cuba.define do
  on root do
    res.write("Hello World!")
  end
end

This code is small but surprisingly powerful. The on method is looking for a clause that returns true. So in this case, when a request is made to /, or root, it will yield the block of code. This block of code has access to res, which is a Cuba::Response class. Here you can tell it what status code to use, which text to render, etc.

Because of how on works, we can nest routes together like so, where we group all of our GET requests together:

Cuba.define do 
  on get do 
    on "about" do 
      res.write("About us") 
    end

    on root do
      res.redirect("/about")
    end
  end 
end

Model and controller

Cuba, as mentioned at the outset is a micro framework, and it has chosen not to include what might traditionally be the model or controller. If you are in need of a model for your app, you are welcome to bring in a solution from another framework, like ActiveRecord or Lotus::Model. What would be in the controller in Rails is put inside of the router directly.

View

Cuba comes with a plugin called Cuba::Render which helps you with the View layer. It allows you to use ERB templates by default but can easily be configured to support Haml, Sass, CoffeeScript, etc. by using the Tilt gem.

To take advantage of the views you can use three methods: partial, view, or render. In this example, I’ll use render, which looks for an ERB file inside of the views folder and passes that as a content variable to a layout.erb file.

require "cuba" 
require "cuba/render" 
require "erb"

Cuba.plugin(Cuba::Render)

Cuba.define do 
  on get do 
    on "about" do 
      render("about") 
    end 
  end 
end

Here is our layout file (views/layout.erb):

<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <%= content %>
</body>
</html>

And lastly our actual view template (views/about.erb):

<h1>About Us</h1>
<h2>Welcome, friends</h2>

Sinatra

Going up the ladder of complexity a little bit, we have Sinatra. Sinatra was created by Blake Mizerany in 2007, about four years after Rails began. Sinatra is probably the second most popular Ruby framework out there; it’s used by many coding bootcamps to give students their first introduction to building a Ruby app.

Router

Sinatra comes with a very capable router which is centered around two things:

  • the HTTP verb (GET, PUT, POST, etc.)
  • the path of the HTTP request.

Using these, you can match the home page by using get and "/".

The main job of one of these blocks of code is to either respond with the text (or JSON or HTML) to be rendered, or by redirecting to another page. In this example, I’m redirecting the root URL to "/hello", which renders some text.

get "/" do 
  redirect "/hello" 
end

get "/hello" do 
  "Hello, World" 
end

Model and controller

Like Cuba, Sinatra doesn’t come with a model or controller layer out of the box, and you’re welcome to use Active Record, Lotus::Model, or another ORM of your choosing.

View

Sinatra comes with a View layer which is built-in and allows you to use ERB, Haml, and Sass, among other templating engines. Each of these different templating engines exposes itself to you in the router via its own rendering method. To render an ERB file, we can simply say erb :hello, to render the views/hello.erb file which will be embedded into a views/layout.erb file via a yield.

get "/hello" do
  erb :hello
end

Another feature that Sinatra has is the ability to define helper methods which can be used inside of the templates. To do that, you use the helpers method and define your own methods inside of the block.

helpers do
  def title
    "Sinatra Demo App"
  end
end

In our views/layout.erb file we can access the title method we defined as a helper.

<!doctype html>
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Padrino

Our goal with Padrino is to stay true to the core principles of Sinatra while at the same time creating a standard library of tools, helpers, and functions that will make Sinatra suitable for increasingly complex applications.

Padrino and Sinatra go hand in hand. Padrino is based on Sinatra but adds many additional tools such as having generators, tag helpers, caching, localization, mailers, etc. It takes Sinatra, which could be said is on the lighter side of what a framework is, and adds some of the things missing from making it a full-stack framework.

Router

In Padrino apps, the routes and controllers are combined in one place. Instead of having a routes file where all the routes go for the whole application (although this too is possible), the controllers essentially contain the routes. The users controller contains all routes related to a User. Anything you can do with routing in Sinatra you can do here, plus some extra features like route nesting.

Bookshelf::App.controllers :books do
  get :index do # /books
    @books = Book.all
    render 'index', layout: 'application'
  end
end

Model

Padrino doesn’t have its own model layer but rather comes with support for a large number of established ORMs that either live on their own or come from other frameworks.

Padrino supports Mongoid, Active Record, minirecord, DataMapper, CouchRest, mongomatic, MongoMapper, OHM, Ripple, SEQUEL, Dynamoid. While generating an app you can choose which one you’re going to use (if any), and then the model generators will help you generate models according to the ORM that you chose.

padrino g model Book title:string code:string published_on:date

View

Padrino has a view layer which is rendered from within the controller. It supports ERB, Haml, Slim, and Liquid out of the box. There isn’t much to say here other than it works well and as expected. Variables can be passed to the view by setting an instance variable in the controller. There are also helpers generated for each controller which can also be used in the view.

Controller

Controllers and routes in Padrino are essentially the same thing. The controller defines the routes and decides how to handle the response: whether to find data to render or to redirect elsewhere.

New Call-to-action

Lotus

Lotus is a Ruby MVC web framework comprised of many micro-libraries. It has a simple, stable API, a minimal DSL, and prioritizes the use of plain objects over magical, over-complicated classes with too much responsibility.

Lotus is a full-stack MVC framework created by Luca Guidi that began in 2013. The philosophy behind it, as mentioned in a blog post by Luca, is that its goal is simplicity, aiming to be built in a modular way, relying more on plain old Ruby objects (POROs) rather than DSLs.

Lotus is actually comprised of seven different modules (or “micro-libraries”):

  • Lotus::Model
  • Lotus::Router
  • Lotus::Utils
  • Lotus::View
  • Lotus::Controller
  • Lotus::Validations
  • Lotus::Helpers

These can be used individually or brought together in a complete full-stack framework under the Lotus gem itself.

Lotus::Router

Lotus comes with a very clean and capable router. It feels very similar to the router that comes with Rails.

To make a get request to the Index action of our Home controller, we put:

get '/', to: 'home#index'

The Lotus Router also supports RESTful resources right out of the box.

resources :books, except: [:destroy]

Because Lotus is Rack compatible, we can respond with a Proc (because it has a call method), and we can even mount an entire Sinatra application inside our routes.

get '/proc', to: ->(env) { [200, {}, ['Hello from Lotus!']] }
mount SinatraApp.new, at: '/sinatra'

Lotus::Model

Lotus::Model follows a Domain Driven Design approach (see Domain Driven Design by Eric Evans), which implements the following concepts:

  • Entity
  • Repository
  • Data Mapper
  • Adapter
  • Query

An Entity is an object that is defined by its identity. In other terms, it’s the “noun” or “thing” in your app: a User, a Book, a Library, etc.

class Book
  include Lotus::Entity
  attributes :author_id, :price, :title, :code
end

A Repository is the next major object in Lotus::Model, whose job it is to mediate between an Entity and the persistence layer (PostgreSQL, MySQL, etc.). Entities aren’t responsible for querying themselves or persisting themselves to the database. That’s the job of the Repository, and it allows for the separation of concerns.

The Repository is where you’ll put all of your queries. They are actually private methods of this object, meaning you’re forced to keep them organized in one place. Controllers (or even worse, Views) no longer have intimate knowledge of how to query data.

In Active Record, which is an implementation of the Active record pattern, you might write your query like this, which could exist anywhere in your application (and is quite commonly found in Controllers):

Book.where(author_id: author.id).order(:published_at).limit(8)

But in Lotus it would live inside the repository.

class BookRepository
  include Lotus::Repository

  def self.most_recent_by_author(author, limit: 8) 
    query do 
      where(author_id: author.id). 
        order(:published_at) 
    end.limit(limit) 
  end 
end

The job of the Data Mapper is to map our fields in the database to attributes on our Entity.

collection :books do
  entity Book 
  repository BookRepository

  attribute :id, Integer 
  attribute :author_id, Integer 
  attribute :title, String 
  attribute :price, Integer 
  attribute :code, String 
end

Lotus also comes with migrations to help you manage the schema of your database. These are quite similar in Lotus as they are in Rails. You have a series of command line commands which will help you generate a new migration. Once you’re done writing it, you can run the migration, get the current migration the database is on, or roll it back.

bundle exec lotus generate migration create_books
# db/migrations/20150724114442_create_books.rb
Lotus::Model.migration do 
  change do 
    create_table :books do 
      primary_key :id 
      foreign_key :author_id, :authors, on_delete: :cascade, null: false

      column :code,  String,  null: false, unique: true, size: 128
      column :title, String,  null: false
      column :price, Integer, null: false, default: 100
    end
  end 
end

Lotus didn’t feel the need to reinvent the wheel here and is using SEQUEL under the hood to help it with migrations and communicating with the database.

bundle exec lotus db migrate
bundle exec lotus db version # 20150724114442

Here is how you would create and persist a Book:

author = Author.new(name: "George Orwell") 
author = AuthorRepository.persist(author)

book = Book.new(title: "1984", code: "abc123", author_id: author.id, price: 1000) book = BookRepository.persist(book)

Lotus::View

In Lotus, the View is an actual object which is responsible for rendering a template. This varies from Rails where the controller renders the template directly.

# web/views/books/index.rb
module Web::Views::Books 
  class 
    Index include Web::View

    def title
      "All the books"
    end
  end
end

Inside of the template, we are now able to call books (which was exposed to us from the Controller/Action) and title to get the page title. Lotus comes with ERB templates by default, but it supports many different rendering engines such as Haml and Slim.

<h1><%= title %></h1>

<ul>
  <% books.each do |book| %>
    <li><%= book.title %></li>
  <% end %>
</ul>

Lotus::Controller

One major difference between Lotus and Rails in the Controller layer is that each Action in Lotus is its own file and class. Another difference is that @ (instance) variables aren’t exposed to the View by default. We must explicitly tell the Action which variables we want to expose.

# web/controllers/books/index.rb
module Web::Controllers::Books 
  class Index 
    include Web::Action 
    expose :books

    def call(params)
      @books = BookRepository.all
    end
  end 
end

Summary

It should be said that all of these frameworks have uses and things that differentiate them one from the other. So which is the best one? Here’s an answer that you’ll hate:

It depends.

It depends on the requirements of your project or in some cases it can be developer preference when all else is equal.

Here’s a summary of when it might be a good idea to choose one of these frameworks over another:

  • Cuba: Very close to Rack with very low overhead. I think its best use is for small endpoints where speed is crucial or for those who want full control over their entire stack, adding additional gems and complexity as needed.
  • Sinatra: Not as close to Rack, yet still far from being a full-stack framework such as Rails or Lotus. I think it’s best used when Cuba is too light, and Rails/Lotus are too heavy. It’s also a great teaching tool because of its small interface.
  • Padrino: For those who have an existing Sinatra app that is becoming more complex and warranting things that come in a full-stack framework. You can start with Sinatra and graduate to Padrino if needed.
  • Lotus: A great Rails alternative with a simple and explicit architecture. For those that find themselves disagreeing with “The Rails Way,” or for those that really enjoy the Domain Driven Design approach.

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles.
Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.



We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.

  • Great article.
    Also worth mentioning would be roda which is a fork from cuba by Jeremy Evans:

    https://github.com/jeremyevans/roda

  • One quibble: Sequel is not SQL, nor is it an acronym. And I agree with @BennyKlotz:disqus: Roda is Very Nice.

  • wschroer

    You folks should check out Pakyow, it is also a very modular ruby framework with a very different approach to the view layer. http://pakyow.org

  • I learned a lot from this article. It was a nice overview of the different frameworks, and interesting to see what they have in common with Rails.

  • spikeheap

    Nice article, and good to see a side-by-side comparison of the lighter frameworks. Having worked with Padrino for a year or so, I’m not sure I’d recommend it in the way your article does though – it pitches itself as being a halfway house between Sinatra and Rails, but as a result doesn’t have the community of either.

    My takeaway from that project is that if you outgrow Sinatra seriously consider jumping straight up to Rails (or Lotus), rather than moving to a slightly bigger framework, where you’re likely to be hit by many similar limitations. Rails has a lot of support and a huge community, and the lack of that has made working with Padrino painful at times, though mostly because Google is full of “this is easy in Rails” answers (the asset pipeline is a prime example).

  • MadBomber

    Just took a glance at volt. its worth taking a look.

    • Interesting. Which were the main things you liked?

      • MadBomber

        A little after the turn of the century I met several times with some smart people from MIT with interesting ideas. Mike Plusch was one. His ideas on programming languages were a little ahead of their time. He thought that there were too many languages involved in the development of large web applications. He proposed the Water programm language as both a client side and server side language.

        Today NodeJS has allowed JavaScript to become that language. I hate Javascript. I hate NodeJS. Okay mayne I’m just a hater; but I’m also a lover. I love Ruby. Why can’t Ruby unite the web world? We would all be much nicer people with less stress in our lives.

        Besides putting Ruby into the client side via Opal, Volt offers some interesting ideas with regard to data syncing between client and server via web-sockets.

        I really have the itch to see how easily RethinkDB can be integrated with Volt to develop dynamic reactive dashboards.

  • If Cuba is there, Grape should be as well.

  • Tony Ja

    Nice one

    It’s worthy to mention that Lotus is distinctively different to Rails for its capability to host many apps (aka slices) in container, this modular architecture brings great benefits for maintenance

  • Łukasz Ozimek

    really nice article, thanks for sharing!

  • Adham EL-Deeb

    so where is grape?

  • Pingback: Links & Reads from 2015 Week 40 | Martin's Weekly Curations()

  • jimothyGator

    In your discussion of Sinatra, you write, “In our views/layout.erb file we can access the title method we defined as a helper.”, but in the example that follows, you don’t actually use this helper. This looks like an oversight.

    • Leigh Halliday

      Good catch! Must have slipped when we were publishing the article… the example is missing the whole section. I’ll send a fix and get it updated. Thanks again :)

  • Nice post. Please note Lotus was renamed to Hanami in Jan, 2016 due to name collision with the Lotus trademark of IBM.

  • Adam Luzsi

    Try out rack-app too, it’s a minimalist framework.
    It’s blazing fast, it’s clean, it’s sexy. :D
    http://www.rack-app.com

  • Stan

    Here it is list with all/most ruby frameworks https://ruby.libhunt.com/categories/242-web-frameworks . They are ordered by popularity and you can see their development activity. Very useful.