Micro Frameworks

Micro Frameworks

  • Rubygems, Gemas, Bundler
  • HTTP, REST
  • Sinatra
  • Cuba/Syro/Roda
  • Redis

Rubygems

Gestor de librerías (gemas) y dependencias oficial de ruby
https://rubygems.org/

Catálogos de gemas

Comando 'gem'

  • gem install GEMNAME
  • gem install GEMNAME -v VERSION
  • gem uninstall GEMNAME
  • gem list Lista las gemas instaladas
  • gem list --remote REGEXP Lista gemas disponibles
  • gem search REGEXP (Idem)

Estructura de una gema

gem_name/
├── bin/
│   └── gem_name
├── lib/
│   └── gem_name.rb
├── test/
│   └── test_gem_name.rb
├── README
├── Rakefile
└── gem_name.gemspec

Ejemplo: cuba.gemspec

Gem::Specification.new do |s|
  s.name              = "cuba"
  s.version           = "3.9.2"
  s.summary           = "Microframework for web applications."
  s.description       = "Cuba is a microframework for web applications."
  s.authors           = ["Michel Martens"]
  s.email             = ["michel@soveran.com"]
  s.homepage          = "https://github.com/soveran/cuba"
  s.license           = "MIT"

  s.files = `git ls-files`.split("\n")
  s.add_dependency "rack", ">= 1.6.0"
  s.add_development_dependency "cutest"
  s.add_development_dependency "rack-test"
  s.add_development_dependency "tilt"
end

Bundler

  • Gestiona las dependencias de una aplicación
    https://bundler.io/

  • Instalación:
    $ gem install bundler

Bundler

  • Especificar las dependencies en Gemfile:
source 'https://rubygems.org'
gem 'nokogiri'
gem 'rack', '~> 2.0.1'
gem 'rspec', '>= 3.6'
  • SemVer: MAJOR.MINOR.PATCH
    rack ~> 2.0.1 (2.0.1 ≤ rack < 2.1.0)
    rack ~> 2.0 (2.0 ≤ rack < 3.0)

Bundler

  • Instalar las gemas y sus dependencias:
$ bundle install        # Instala todas las gemas indicadas en Gemfile.lock
$ bundle update rack    # Actualiza la versión de `rack` y sus dependencias
$ bundle update         # Actualiza todas las gemas
$ bundle exec SCRIPT    # Ejecuta un script con las gemas indicadas en Gemfile.lock

⚠️ En el repositorio se guardan Gemfile & Gemfile.lock

Sinatra

Sinatra (~2000 loc)

require 'sinatra'

get '/frank-says' do
  'Put this in your pipe & smoke it!'
end
$ ruby my-app.rb

localhost:4567/frank-says

Pero...

¿cómo funciona?
¿Qué es una aplicación rack?

Rack Interface

  • Debe responder al método call
  • Recibe un argumento (env). Contiene toda la información del request
  • El método call debe devolver un array con tres elementos:
    1. Status code
    2. Headers
    3. Body

Rack - Hello world

require "rack"
require "thin"

class HelloWorld
  def call(env)
    [ 200, { "Content-Type" => "text/plain" }, ["Hello World"] ]
  end
end

Rack::Handler::Thin.run HelloWorld.new

Middleware en config.ru

use FirstMiddleware
use SecondMiddleware
# ...
use LoggingMiddleware
use AuthenticationMiddleware

run OurApp.new

y se levanta el servidor mediante el comando rackup

ver rack-contrib para más ejemplos de Rack Middlewares

Middleware - Casos de uso

Los Middleware son perfecto para la lógica no específica de la aplicación. Cosas como configurar caching headers, logging, parsear el request object, etc. Por ejemplo, en Rails, el manejo de cookies, las sesiones y el parseo de parámetros se hace a través de Middleware.

Sinatra

require 'sinatra'

get '/hello' do
  'Hello World!'
end

get '/hello/:name' do
  "Hello #{params['name']}!"
end

get '/bye/:name' do |name|
  "Goodbye #{name}!"
end

Sinatra

# matches "GET /posts?title=foo&author=bar"
get '/posts' do
  # uses title and author variables; query is optional to the /posts route
  title = params['title']
  author = params['author']
end

# admin.example.com/
get '/', host_name: /^admin\./ do
  "Admin Area, Access denied!"
end

Sinatra

# This renders views/index.erb.
get '/'
  erb :index
end

# This will render views/index.erb embedded in the views/post.erb
# (default is views/layout.erb, if it exists).
get '/' do
  erb :index, layout: :post
end

ERB (Embedded Ruby)

<h1>Esto es HTML</h1>

<% if @user %>
  Hola <%= @user.name %>
<% else %>
  <a href="/login">Login</a>
<% end %>

Sinatra

post '/login'
  email = params['email']
  password = params['password']
  
  if @user = User.authenticate(email, password)
    redirect to('/home')
  else
    erb :index, :layout => :post
  end
end

Sinatra

# Nested routes
get '/post/:id/comments'
  @post = Post.find params['id']
  @comments = @post.comments
  erb :comments
end

# Local params
get '/post/:id/comments'
  post = Post.find params['id']
  comments = post.comments
  erb :comments, locals: {post: post, comments: comments}
end

Sinatra

# Modular apps
require 'sinatra/base'

class MyApp < Sinatra::Base
  set :sessions, true
  set :foo, 'bar'

  get '/' do
    'Hello world!'
  end
end

# y en config.ru ...
require './my_app'
run MyApp

Cuba (349 loc)

Documentación

The Guide to Cuba

require "cuba"

Cuba.define do
  on "hello" do
    res.write "hello, world"
  end
end

run Cuba

Syro (382 loc)

Presentación

Documentación

require "syro"

App = Syro.new do
  get do
    res.text "hello, world"
  end
end

Roda

Repositorio

Documentación

Comparación con Rails

The Little Redis Book

Redis

Documentación: SET, GET

require "redis"

redis = Redis.new(host: "localhost", port: 12345)

redis.set("mykey", "hello world")
# => "OK"

redis.get("mykey")
# => "hello world"

Redis

Documentación: HMSET, HMGET

# user creation
username = "joaquin"
name = "Joaquin Vicente"
password = "1234"
redis.hmset("user:#{username}", "name", name, "password", password)

# user login
name, password = redis.hmget("user:#{username}", "name", "password")
password = redis.hget("user:#{username}", "password")