Off the rails

A journey to explore the alternative options when it comes to building web services 

@marjaimate - BP.RB 2016 March

We help forging your software, product and company

Rails all the things?

Rails is the standout option when people start to write a new application. But should we start here always?

Sinatra

Sinatra

  • Supports other engines (like thin) not just WEBrick
  • Built in support for templating languages
  • Error handling
  • Helpers and callbacks
  • Comprehensive guides

Sinatra

require "sinatra"
require 'sinatra/base'

class Libre < Sinatra::Base
  get "/" do
    redirect "/ciudades"
  end

  get "/ciudades" do
    ciudades = YAML.load_file("ciudades.yml")
    ciudades.to_json
  end

  get "/ciudades/:ciudad" do |ciudad|
    ciudades = YAML.load_file("ciudades.yml")
    ciudades[ciudad].to_json
  end

end

Sinatra

get '/' do
  .. show something ..
end

post '/' do
  .. create something ..
end

put '/' do
  .. replace something ..
end

patch '/' do
  .. modify something ..
end

delete '/' do
  .. annihilate something ..
end

options '/' do
  .. appease something ..
end
# Request param binding
get '/hello/:name' do
  # matches "GET /hello/foo" and "GET /hello/bar"
  "Hello #{params['name']}!"
end

# Splats
get '/download/*.*' do
  # matches /download/path/to/file.xml
  params['splat'] # => ["path/to/file", "xml"]
end

# Regular expressions
get %r{/hello/([\w]+)} do |c|
  # Matches "GET /meta/hello/world", etc.
  "Hello, #{c}!"
end

Grape

Grape

  • REST-like API micro-framework
  • Can be used alongside Sinatra or Rails
  • Geared towards APIs (routing engine, versioning)
  • Requirement definition per param, per resource
  • Exception and error handling
  • Comprehensive guides

Grape

require "grape"

class Libre < Grape::API
  format :json

  helpers do
    def ciudades
      YAML.load_file("ciudades.yml")
    end
  end

  desc "Ciudades"
  resource :ciudades do
    get do
      ciudades.to_json
    end

    route_param :ciudad do
      get do
        ciudades[params[:ciudad]].to_json
      end
    end
  end
end

Grape

# Describing methods
desc 'Returns your public timeline.' do
  detail 'more details'
  params  API::Entities::Status.documentation
  success API::Entities::Entity
  failure [[401, 'Unauthorized', 'Entities::Error']]
  named 'My named route'
  headers XAuthToken: {
            description: 'Validates your identity',
            required: true
          },
          XOptionalHeader: {
            description: 'Not really needed',
            required: false
          }

end
get :public_timeline do
  Status.limit(20)
end

# Parameter validation
params do
  requires :id, type: Integer
  optional :text, type: String, regexp: /\A[a-z]+\z/
  group :media do
    requires :url
  end
  optional :audio do
    requires :format, type: Symbol, values: [:mp3, :wav, :aac, :ogg], default: :mp3
  end
  mutually_exclusive :media, :audio
end
put ':id' do
  # params[:id] is an Integer
end

Webmachine

Webmachine

  • Port of webmachine, an Erlang library
  • More of a toolkit for building HTTP-friendly applications
  • Streaming/chunked response bodies
  • Implemented as a Finite State Machine - It uses the facts about your resource to determine the flow though the FSM in order to produce a response.
  • Comprehensive guides

Webmachine

class CiudadesResource < Webmachine::Resource
  def allowed_methods
    ['GET','HEAD']
  end

  def content_types_provided
    [['application/json', :to_json]]
  end

  # Return a Truthy or Falsey value
  def resource_exists?
    ciudades
  end

  def ciudades
    @ciudades = YAML.load_file("ciudades.yml")
  end

  def to_json
    ciudades.to_json
  end
end

Webmachine

# Routing
MyApp = Webmachine::Application.new do |app|
  # Configure your app like this:
  app.configure do |config|
    config.port = 9292
    config.adapter = :WEBrick
  end
  # And add routes like this:
  app.add_route ['ciudades'], CiudadesResource
  app.add_route ['ciudades', :ciudad], CiudadResource
end

# Authentication using HTTP Auth
class MySecureResource < Webmachine::Resource
  include Webmachine::Resource::Authentication

  def is_authorized?(authorization_header)
    basic_auth(authorization_header, "My Application") do |username, password|
      @user = User.find_by_username(username)
      !@user.nil? && @user.auth?(password)
    end
  end
end

Cuba

Cuba

  • Real micro framework - less than 200 lines of code
  • Cuba does one thing and does it well, handle HTTP requests
  • Easy to mount other engines for RACK (thin), or templates (jbuilder, haml)
  • Supports plugins and helpers
  • Comprehensive guides

Cuba

Cuba.define do
  on get do
    on "ciudades" do
      ciudades = YAML.load_file("ciudades.yml")
      # Index Cities
      on root do
        res.write ciudades.to_json
      end

      # Show City
      on ":ciudad" do |ciudad|
        res.write ciudades[ciudad].to_json
      end
    end

    on root do
      res.redirect "/ciudades"
    end
  end
end

Cuba

# CSRF 
Cuba.use Rack::Session::Cookie, :secret => "s3cr3t"
Cuba.plugin Cuba::Safe

Cuba.define do
  on csrf.unsafe? do
    csrf.reset!

    res.status = 403
    res.write("Not authorized")

    halt(res.finish)
  end

  # Here comes the rest of your application
  # ...
end
Cuba.define do
  on get do
    on "hello" do
      on root do
        res.write "hello world"
      end
    end
  end
end

# Requests:
#
# GET /            # 404
# GET /hello       # 200
# GET /hello/world # 404

Comparison

Total Time Req / sec Longest Req KB / sec 
Rails 6.444 155.19 151 ms 128.52
Sinatra 4.325 231.20 147 ms 151.95
Grape 4.092 244.41 104 ms 151.08
Webmachine 4.310 232.00 118 ms 134.35
Cuba 4.068 245.82 94 ms 196.13
  • 1000 requests concurrency of 10
  • ruby 2.2.0p0 [x86_64-darwin13]

Links to examples

  • https://github.com/marjaimate/libre-sinatra
  • https://github.com/marjaimate/libre-grape
  • https://github.com/marjaimate/libre-webmachine
  • https://github.com/marjaimate/libre-cuba
  • https://github.com/marjaimate/libre-rails

Off the rails

By Máté Marjai

Off the rails

Exploring alternative options on ruby frameworks

  • 662