Introduzione a Sinatra

Il logo di SinatraNegli ultimi anni c’è stato un generale proliferare di framework dedicati allo sviluppo web scritti in Ruby. Il più celebre è senza dubbio Ruby on Rails, che si distingue sia per numero di utenti sia per la sua maturità e completezza.

Una delle principali ragioni di esistere per questi framework è che garantiscono lo sviluppo di applicazioni web in maniera rapida e pragmatica. Questo è possibile grazie al gran numero di funzionalità offerte e alle astrazioni che permettono al programmatore di concentrare gli sforzi sul proprio progetto senza doversi preoccupare di tutte quelle attività di basso livello, necessarie, ma che non hanno nulla a che vedere con la logica dell’applicazione (parsing delle richieste HTTP, controllo delle route, gestione degli errori, eccetera).


Cos’è Sinatra?

Sinatra è uno di questi framework, caratterizzato dalle sue dimensioni ridotte, ottime prestazioni e dall’abilità di consentire la scrittura di applicazioni web con pochissime righe di codice. Nonostante il suo approccio minimale, Sinatra è molto modulare ed è possibile utilizzarlo senza per questo rinunciare a tutte le comodità derivanti da ORM, un sistema di templating, l’utilizzo di filtri, helper, eccetera.

A differenza di altri framework (RoR in primis) Sinatra lascia lo sviluppatore libero di scegliere quali altri strumenti utilizzare per lo sviluppo. Per esempio se avete esperienza con ActiveRecord, Datamapper o Sequel potrete continuare ad utilizzarli semplicemente includendoli nel vostro codice.

Lo stesso discorso vale per il sistema di templating: Sinatra supporta nativamente Haml, Sass, ERb e Builder in maniera del tutto trasparente, dando così la possibilità di scegliere quali utilizzare nel proprio stack di sviluppo. I lettori più attenti riconosceranno che questo è proprio uno dei punti di forza del framework Merb, che sarà unito a Ruby on Rails dalla versione 3.0.

Come installare Sinatra

Sinatra è disponibile come gem, quindi se avete RubyGems installato sul vostro computer sarà sufficiente eseguire un semplice:

$ sudo gem install sinatra

Chi usa Windows per sviluppare, potrà ovviamente rimuovere sudo e installare la gem.

Hello Sinatra

Abbiamo detto che Sinatra permette lo sviluppo di qualcosa di funzionante con pochissime righe di codice; una semplice applicazione ‘Hello World’ richiede solamente 5 righe di codice!

# esempio1/hello.rb
require 'rubygems'
require 'sinatra'

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

Ora non rimane che far partire la nostra applicazione con un altrettanto intuitivo:

$ ruby hello.rb

Questo comando farà eseguire la nostra piccola applicazione e la metterà in ascolto sulla porta 4567, infatti visitando l’indirizzo http://127.0.0.1:4567 col nostro browser preferito vedremo apparire il messaggio “Hello, world!” (se preferite un’altra porta potete aggiungere l’argomento -p [numero porta]).

Leggibilità del codice

La prima cosa che si nota quando si guarda il codice di un’applicazione che utilizza Sinatra è come sia immediatamente comprensibile la struttura dell’applicazione stessa.

Prendiamo ad esempio il codice che abbiamo appena eseguito, la porzione di codice da get ad end si spiega da sola in maniera intuitiva, ovvero il metodo HTTP a cui rispondere è GET, la route a cui rispondere (in questo caso la root) e cosa si debba fare quando necessario (il codice tra do e end).

Metodi HTTP

Come è facile aspettarsi sono utilizzabili tutti i metodi HTTP: GET, POST, PUT e DELETE, rendendo Sinatra un framework ideale per esporre RESTful web service.

Avendo la necessità di specificare un metodo per ricevere i dati da un form, sarà sufficiente scrivere qualcosa del tipo:

post '/ricevi' do
# Codice per la gestione e il salvataggio dei dati
end

Per un esempio più concreto, prendiamo in considerazione il codice presente nel file esempio2/get_and_post.rb:

require 'rubygems'
require 'sinatra'

# Questo metodo presenta il form per l'invio dei dati
#
get '/' do
erb " <html>
<head>
<title>Esempio 2 - Invio dati</title>
</head>
<body>
<form name=\"invio\" method=\"POST\" action=\"ricevi\">
Nome: <input type=\"text\" name=\"nome\" />
<input type=\"submit\" value=\"Invia!\" />
</form>
</body>
</html>"
end

# Questo metodo riceve i dati dal form e li mostra
#
post '/ricevi' do
erb " <html>
<head>
<title>Esempio 2 - Dati ricevuti</title>
</head>
<body>
<p>I dati inviati dal form sono stati ricevuti correttamente: '#{params[:nome]}'</p>
</body>
</html>"
end

Come potete vedere l’applicazione è composta da due route, la prima che utilizza il metodo GET e che si occuperà di mostrare il form e la seconda che utilizza POST che riceverà i dati dal form prima di mostrarli.

Route

Ogni applicazione web è composta da varie route che associano determinati URI e verbi HTTP con del codice da eseguire. Sinatra è molto flessibile da questo punto di vista, permettendo la creazione di ogni sorta di route grazie alle varie tipologie di route disponibili.

  • Route semplici (esplicite). Ad esempio: ‘/about’

    Questa tipologia di route è la più semplice ed è di immediata comprensione

  • Named route. Ad esempio: ‘/nome/:name’

    Questo tipo di route permette di definire una route in modo più astratto; se ad esempio arriverà una richiesta tipo http://127.0.0.1:4567/nome/simone nel nostro codice potremo utilizzare params[:name] che avrà un valore pari a ‘simone’.

  • Splat route. Ad esempio: ‘/download/*.*’

    Questa route specifica che ci sono due ‘gruppi’; quindi se la richiesta fosse http://127.0.0.1:4567/download/simone.txt nel nostro codice avremo la posibilità di utilizzare params[:splat] che conterrà un array valorizzato [‘simone’, ‘txt’]

    NB: L’attuale versione pacchettizzata di Sinatra è affetta da un bug nella gestione di questo tipo di route quindi non otterrete ciò che vi aspettate. Il problema è già stato risolto nella versione di sviluppo quindi a breve potrete utilizzare queste route senza problemi semplicemente aggiornando la vostra versione di Sinatra (‘sudo gem update sinatra’)

  • Regular expression route. Ad esempio: %r{/(\d+)}

    In questo caso abbiamo utilizzato una regular expression come route, questa tipologia di route è molto malleabile, infatti è possibile utilizzare regex solamente per effettuare il match o anche per ottenere parte dell’indirizzo grazie ai gruppi presenti nella regex. Se nella regex si indica un gruppo (come nell’esempio soprastante) nel codice si potrà utilizzare params[:captures] per ottenere il valore del gruppo, quindi da una richiesta tipo http://127.0.0.1:4567/123 otterremo con params[:captures] il valore ‘123’.

Il file esempio3/routes.rb nell’allegato, contiene un esempio completo:

require 'rubygems'
require 'sinatra'

# Route esplicita
#
get '/about' do
'Questa route è il tipo più semplice, semplicemente autoesplicativa!'
end

# Named route
#
get '/nome/:name' do
"Questa route rende la creazione delle route più astratta e fornisce una variabile col contenuto della route stessa, in questo caso '#{params[:name]}'"
end

# Splat route
#
get '/download/*.*' do
"Questa route permette un ulteriore grado di libertà nel definire la route e fornisce un array con le varie parti che formano la route. In questo caso '#{params[:splat].inspect}' (nella attuale versione pacchettizzata di Sinatra c'è un bug nella gestione di questo tipo di route, è già stato risolto nella versione di sviluppo quindi a breve con un 'sudo gem update sinatra' la vedrete funzionare a dovere)"
end

# Regex route
#
get %r{/(\d+)} do
"Questo tipo di route permette il massimo grado di libertà e fornisce una variabile con tutti i match effettuati, in questo caso '#{params[:captures]}'"
end

Una volta avviato il server con ruby routes.rb, per vedere ogni route in azione sarà sufficiente visitare i seguenti indirizzi:

  • Route semplice: http://127.0.0.1:4567/about
  • Named route: http://127.0.0.1:4567/nome/Simone
  • Splat route: http://127.0.0.1:4567/download/Simone.txt
  • Regex route: http://127.0.0.1:4567/123

View

Come abbiamo detto in precedenza Sinatra permette allo sviluppatore di scegliere quali template engine utilizzare per le proprie view. Haml, Sass, ERb e Builder sono tutte scelte che coprono un ampio spettro di requirement, al di là dei gusti personali.

Nel file di esempio esempio4/views.rb nell’allegato, potete osservare degli esempi di utilizzo di ERb e Haml sia nel caso in cui si voglia usare il codice ERb (o Haml) direttamente inline sia nel caso (più comune) in cui si voglia mantenere il codice in un file separato.

Helper e Filter

Come ogni altro framework che si rispetti Sinatra permette l’utilizzo sia di helper che di filtri.

I primi sono utilissimi per ridurre ripetizioni di codice (aderendo al cosiddetto principio DRY) nella vostra applicazione, mentre i secondi permettono di eseguire delle operazioni prima che il codice relativo ad una route venga eseguito.

Potete vedere un esempio di helper nel file esempio5/helpers.rb:

require 'rubygems'
require 'sinatra'

# In questo blocco potete definire i vostri helper
helpers do
# Questo è un helper che sarà utilizzabile nelle varie view
def mostra_luogo(nome)
"Attualmente sei in #{nome}!"
end
end

get '/paese/:paese' do
mostra_luogo(params[:paese])
end

get '/continente/:continente' do
mostra_luogo(params[:continente])
end

get '/universo/:universo' do
mostra_luogo(params[:universo])
end

Come potete notare ci sono 3 route che fanno sostanzialmente la stessa cosa ma grazie all’helper che abbiamo definito il codice relativo non è presente 3 volte ma solamente una.

Per un semplice esempio di filtro potete aprire il file esempio5/filters.rb nel quale il filtro setta una stringa che poi verrà utilizzata nella pagina restituita:

require 'rubygems'
require 'sinatra'

# In questo blocco potete definire il codice che va eseguito prima di quello della route,
# le variabili d'istanza settate nel filtro sono disponibili all'interno delle view
before do
@saluto = (Time.now.hour > 12 ? 'Buonasera' : 'Buongiorno')
end

get '/:nome' do
"#{@saluto} #{params[:nome]}"
end

Errori

Un’applicazione web che si rispetti non è formata solamente dalle nostre pagine ma anche da quelle che vengono mostrate nel caso si presenti un problema come ad esempio la classica 404 nel caso non esista quello che si è richiesto.

Nel file esempio6/errors.rb viene mostrato l’utilizzo sia di not_found (nel caso in cui si verifichi un errore 404) sia di error (nel caso in cui si verifichi un errore nell’esecuzione):

require 'rubygems'
require 'sinatra'

get '/about' do
'Pagina about'
end

get '/error' do
# Il metodo 'a' non esiste, viene chiamato appositamente per creare un errore
a
end

# Il contenuto di questo blocco verrà eseguita tutte le volte che si verificherà un errore 404
not_found do
'Quello che cerchi non esiste!'
end

# Il contenuto di questo blocco verrà eseguito quando si verificherà un errore nel codice di una route o in un filtro
error do
'Si è verificato un errore!'
end

Versione di sviluppo

Come menzionato nella parte relativa alle route la versione attuale di Sinatra distribuita tramite RubyGems ha un problema con le Splat route (anche noto come Route Globbing). Chi non potesse farne a meno o volesse provare le ultime funzionalità introdotte dalla versione in via di sviluppo, può installarla in questo modo:

$ git clone git://github.com/sinatra/sinatra.git

E poi sostituire nel proprio codice:

require 'sinatra'

con:

require '[path to sinatra]/lib/sinatra.rb'

Ruby 1.9

Al momento non è possibile utilizzare Sinatra con Ruby 1.9, questo perchè Sinatra sfrutta Rack che non è ancora utilizzabile con Ruby 1.9 (anche se i lavori sono in corso).

Passenger

Le applicazioni in Sinatra possono essere servite da Apache2 e Passenger, la cui installazione è piuttosto semplice. Se qualcuno fosse interessato ai dettagli, lo scriva nei commenti e vi dirò come fare.

Conclusione

Come spero di aver dimostrato con questa introduzione, Sinatra è un ottimo framework per lo sviluppo di applicazioni web. Personalmente non lo considero un sostituto di RoR, ma credo che possa costituire una valida scelta in tutte quelle situazioni dove v’è la necessità di avere un’interfaccia web non troppo complessa.

Dategli una chance!

Allegato: codice_sinatra.zip

Comments

  1. Ottimo articolo. Credo Sinatra sia un ottima scelta per continuare ad usare Ruby anche in situazioni dove usare Rails sarebbe un po’ come nuclearizzare una mosca 🙂 Ad esempio conosco un ragazzo che lo usato per un sito web personale (http://hellotherecanary.com/), lista di pagine statiche ed un contact form (il blog e’ tumblr).

  2. Bell’articolo, complimenti!
    Simpatico questo framework, adatto a progetti poco complessi che non richiedono le potenzialità del sistema mvc …o meglio che ne farebbero volentieri a meno 🙂
    Bisognerebbe capire poi che grado di complessità riuscirebbe a gestire, se e come si riesce a separare le viste dalla logica

  3. Marco, nessuno ti vieta di costruirtelo da te uno strato MVC sopra a Sinatra. O MVP. O… Non e’ che abbiamo bisogno per forza del framework per questo… a me piacciono i framework non invasivi.

  4. @ TheBox: ecco perche` PHP e` infinitamente piu` diffuso di RoR o Django 😉

  5. Oh certo, e’ noto che il mondo PHP e’ pieno di virtuosi dell’MVC… 😀

    Ci riprovo: molti framework sono piu’ un ostacolo, un danno, che un vantaggio e impongono una visione distorta e appesantita (vogliamo fare un nome? struts, ad esempio). Per una volta ben venga un framework minimalista e poi ognuno si assuma le sue responsabilita’. Non ho bisogno di Rails o Django per fare MVC, anche se magari potrebbero essere una mia prima scelta, ci mancherebbe.

  6. sasuke says:

    Ciao,
    piacere mi chiamo Francesco e questo è il mio primo post. In arte mi chiamo Sasuke,

    RIguardo alla complessità ,che Sinatra può gestire, l’ultima versione permettere di inserire regole di routing dentro alle classi e poi di lanciarle con Rack.

    Immaginate voi la potenza di una tale possibilità (Eredità delle classi, o anche inclusione di moduli).

    Inoltre con la version 2.3 di Rails Sinatra può girare all’interno di Rails bypassando i controller e l’active resource grazie all’uso di Metal.

Policy per i commenti: Apprezzo moltissimo i vostri commenti, critiche incluse. Per evitare spam e troll, e far rimanere il discorso civile, i commenti sono moderati e prontamente approvati poco dopo il loro invio.