mirror of
https://github.com/sinatra/sinatra
synced 2023-03-27 23:18:01 -04:00
e93951e3d1
Signed-off-by: Konstantin Haase <konstantin.mailinglists@googlemail.com>
1047 lines
29 KiB
Text
1047 lines
29 KiB
Text
= Sinatra
|
|
<i>Atención: Este documento es una traducción de la versión en inglés y puede estar desactualizado.</i>
|
|
|
|
Sinatra es un DSL para crear aplicaciones web rápidamente en Ruby con un mínimo
|
|
esfuerzo:
|
|
|
|
# miapp.rb
|
|
require 'sinatra'
|
|
|
|
get '/' do
|
|
'Hola mundo!'
|
|
end
|
|
|
|
Instalá la gem y ejecutá la aplicación con:
|
|
|
|
gem install sinatra
|
|
ruby -rubygems miapp.rb
|
|
|
|
Podés verla en: http://localhost:4567
|
|
|
|
== Rutas
|
|
|
|
En Sinatra, una ruta está compuesta por un método HTTP y un patrón de una URL.
|
|
Cada ruta se asocia con un bloque:
|
|
|
|
get '/' do
|
|
.. mostrar algo ..
|
|
end
|
|
|
|
post '/' do
|
|
.. crear algo ..
|
|
end
|
|
|
|
put '/' do
|
|
.. actualizar algo ..
|
|
end
|
|
|
|
delete '/' do
|
|
.. aniquilar algo ..
|
|
end
|
|
|
|
Las rutas son comparadas en el orden en el que son definidas. La primer ruta
|
|
que coincide con la petición es invocada.
|
|
|
|
Los patrones de las rutas pueden incluir parámetros nombrados, accesibles a
|
|
través de el hash <tt>params</tt>:
|
|
|
|
get '/hola/:nombre' do
|
|
# coincide con "GET /hola/foo" y "GET /hola/bar"
|
|
# params[:nombre] es 'foo' o 'bar'
|
|
"Hola #{params[:nombre]}!"
|
|
end
|
|
|
|
También podés acceder a los parámetros nombrados usando parámetros de bloque:
|
|
|
|
get '/hola/:nombre' do |n|
|
|
"Hola #{n}!"
|
|
end
|
|
|
|
Los patrones de ruta también pueden incluir parámetros splat (o wildcard),
|
|
accesibles a través del arreglo <tt>params[:splat]</tt>.
|
|
|
|
get '/decir/*/al/*' do
|
|
# coincide con /decir/hola/al/mundo
|
|
params[:splat] # => ["hola", "mundo"]
|
|
end
|
|
|
|
get '/descargar/*.*' do
|
|
# coincide con /descargar/path/al/archivo.xml
|
|
params[:splat] # => ["path/al/archivo", "xml"]
|
|
end
|
|
|
|
Rutas con Expresiones Regulares:
|
|
|
|
get %r{/hola/([\w]+)} do
|
|
"Hola, #{params[:captures].first}!"
|
|
end
|
|
|
|
O con un parámetro de bloque:
|
|
|
|
get %r{/hola/([\w]+)} do |c|
|
|
"Hola, #{c}!"
|
|
end
|
|
|
|
=== Condiciones
|
|
|
|
Las rutas pueden incluir una variedad de condiciones de selección, como por
|
|
ejemplo el user agent:
|
|
|
|
get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
|
|
"Estás usando la versión de Songbird #{params[:agent][0]}"
|
|
end
|
|
|
|
get '/foo' do
|
|
# Coincide con browsers que no sean songbird
|
|
end
|
|
|
|
Otras condiciones disponibles son +host_name+ y +provides+:
|
|
|
|
get '/', :host_name => /^admin\./ do
|
|
"Área de Administración, Acceso denegado!"
|
|
end
|
|
|
|
get '/', :provides => 'html' do
|
|
haml :index
|
|
end
|
|
|
|
get '/', :provides => ['rss', 'atom', 'xml'] do
|
|
builder :feed
|
|
end
|
|
|
|
Podés definir tus propias condiciones fácilmente:
|
|
|
|
set(:probabilidad) { |valor| condition { rand <= valor } }
|
|
|
|
get '/gana_un_auto', :probabilidad => 0.1 do
|
|
"Ganaste!"
|
|
end
|
|
|
|
get '/gana_un_auto' do
|
|
"Lo siento, perdiste."
|
|
end
|
|
|
|
=== Valores de retorno
|
|
|
|
El valor de retorno de un bloque de ruta determina al menos el cuerpo de la
|
|
respuesta que se le pasa al cliente HTTP o al siguiente middleware en la pila
|
|
de Rack. Lo más común es que sea un string, como en los ejemplos anteriores.
|
|
Sin embargo, otros valor también son aceptados.
|
|
|
|
Podés devolver cualquier objeto que sea una respuesta Rack válida, un objeto
|
|
que represente el cuerpo de una respuesta Rack o un código de estado HTTP:
|
|
|
|
* Un arreglo con tres elementos: <tt>[estado (Fixnum), cabeceras (Hash), cuerpo de la respuesta (responde a #each)]</tt>
|
|
* Un arreglo con dos elementos: <tt>[estado (Fixnum), cuerpo de la respuesta (responde a #each)]</tt>
|
|
* Un objeto que responde a <tt>#each</tt> y que le pasa únicamente strings al bloque dado
|
|
* Un Fixnum representando el código de estado
|
|
|
|
De esa manera podemos, por ejemplo, implementar fácilmente un streaming:
|
|
|
|
class Stream
|
|
def each
|
|
100.times { |i| yield "#{i}\n" }
|
|
end
|
|
end
|
|
|
|
get('/') { Stream.new }
|
|
|
|
== Archivos estáticos
|
|
|
|
Los archivos estáticos son servidos desde el directorio público
|
|
<tt>./public</tt>. Podés especificar una ubicación diferente ajustando la
|
|
opción <tt>:public</tt>:
|
|
|
|
set :public, File.dirname(__FILE__) + '/estaticos'
|
|
|
|
Notá que el nombre del directorio público no está incluido en la URL. Por
|
|
ejemplo, el archivo <tt>./public/css/style.css</tt> se accede a través de
|
|
<tt>http://ejemplo.com/css/style.css</tt>.
|
|
|
|
== Vistas / Plantillas
|
|
|
|
Se asume que las plantillas están ubicadas directamente bajo el directorio
|
|
<tt>./views</tt>. Para usar un directorio de vistas diferente:
|
|
|
|
set :views, File.dirname(__FILE__) + '/plantillas'
|
|
|
|
Es importante acordarse que siempre tenés que referenciar a las plantillas con
|
|
símbolos, incluso cuando se encuentran en un subdirectorio (en este caso tenés
|
|
que usar <tt>:'subdir/plantilla'</tt>). Tenés que usar un símbolo porque los
|
|
métodos de renderización van a renderizar directamente cualquier string que se
|
|
les pase como argumento.
|
|
|
|
=== Plantillas Haml
|
|
|
|
La gem/librería haml es necesaria para para renderizar plantillas HAML:
|
|
|
|
## Vas a necesitar requerir haml en tu app
|
|
require 'haml'
|
|
|
|
get '/' do
|
|
haml :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.haml</tt>.
|
|
|
|
Las {opciones de Haml}[http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html#options]
|
|
pueden ser ajustadas globalmente a través de las configuraciones de Sinatra,
|
|
ver {Opciones y Configuraciones}[http://www.sinatrarb.com/configuration.html],
|
|
y reemplazadas individualmente.
|
|
|
|
set :haml, :format => :html5 # el formato por defecto de Haml es :xhtml
|
|
|
|
get '/' do
|
|
haml :index, :format => :html4 # reemplazado
|
|
end
|
|
|
|
=== Plantillas Erb
|
|
|
|
## Vas a necesitar requerir erb en tu app
|
|
require 'erb'
|
|
|
|
get '/' do
|
|
erb :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.erb</tt>
|
|
|
|
=== Erubis
|
|
|
|
La gem/librería erubis es necesaria para renderizar plantillas erubis:
|
|
|
|
## Vas a necesitar requerir erubis en tu app
|
|
require 'erubis'
|
|
|
|
get '/' do
|
|
erubis :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.erubis</tt>
|
|
|
|
=== Plantillas Builder
|
|
|
|
La gem/librería builder es necesaria para renderizar plantillas builder:
|
|
|
|
## Vas a necesitar requerir builder en tu app
|
|
require 'builder'
|
|
|
|
get '/' do
|
|
builder :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.builder</tt>.
|
|
|
|
=== Plantillas Nokogiri
|
|
|
|
La gem/librería nokogiri es necesaria para renderizar plantillas nokogiri:
|
|
|
|
## Vas a necesitar requerir nokogiri en tu app
|
|
require 'nokogiri'
|
|
|
|
get '/' do
|
|
nokogiri :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.nokogiri</tt>.
|
|
|
|
=== Plantillas Sass
|
|
|
|
La gem/librería haml es necesaria para renderizar plantillas Sass:
|
|
|
|
## Vas a necesitar requerir haml o sass en tu app
|
|
require 'sass'
|
|
|
|
get '/stylesheet.css' do
|
|
sass :stylesheet
|
|
end
|
|
|
|
Renderiza <tt>./views/stylesheet.sass</tt>.
|
|
|
|
Las {opciones de Sass}[http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options]
|
|
pueden ser ajustadas globalmente a través de las configuraciones de Sinatra,
|
|
ver {Opciones y Configuraciones}[http://www.sinatrarb.com/configuration.html],
|
|
y reemplazadas individualmente.
|
|
|
|
set :sass, :style => :compact # el estilo por defecto de Sass es :nested
|
|
|
|
get '/stylesheet.css' do
|
|
sass :stylesheet, :style => :expanded # reemplazado
|
|
end
|
|
|
|
=== Plantillas Scss
|
|
|
|
La gem/librería haml es necesaria para renderizar plantillas Scss:
|
|
|
|
## Vas a necesitar requerir haml o sass en tu app
|
|
require 'sass'
|
|
|
|
get '/stylesheet.css' do
|
|
scss :stylesheet
|
|
end
|
|
|
|
Renderiza <tt>./views/stylesheet.scss</tt>.
|
|
|
|
Las {opciones de Scss}[http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options]
|
|
pueden ser ajustadas globalmente a través de las configuraciones de Sinatra,
|
|
ver {Opciones y Configuraciones}[http://www.sinatrarb.com/configuration.html],
|
|
y reemplazadas individualmente.
|
|
|
|
set :scss, :style => :compact # el estilo por defecto de Sass es :nested
|
|
|
|
get '/stylesheet.css' do
|
|
scss :stylesheet, :style => :expanded # reemplazado
|
|
end
|
|
|
|
=== Plantillas Less
|
|
|
|
La gem/librería less es necesaria para renderizar plantillas Less:
|
|
|
|
## Vas a necesitar requerir less en tu app
|
|
require 'less'
|
|
|
|
get '/stylesheet.css' do
|
|
less :stylesheet
|
|
end
|
|
|
|
Renderiza <tt>./views/stylesheet.less</tt>.
|
|
|
|
=== Plantillas Liquid
|
|
|
|
La gem/librería liquid es necesaria para renderizar plantillas Liquid:
|
|
|
|
## Vas a necesitar requerir liquid en tu app
|
|
require 'liquid'
|
|
|
|
get '/' do
|
|
liquid :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.liquid</tt>.
|
|
|
|
Como no vas a poder llamar a métodos de Ruby (excepto a +yield+) desde una
|
|
plantilla Liquid, casi siempre vas a querer pasarle locales:
|
|
|
|
liquid :index, :locals => { :clave => 'valor' }
|
|
|
|
=== Plantillas Markdown
|
|
|
|
La gem/librería rdiscount es necesaria para renderizar plantillas Markdown:
|
|
|
|
## Vas a necesitar requerir rdiscount en tu app
|
|
require "rdiscount"
|
|
|
|
get '/' do
|
|
markdown :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.markdown</tt> (+md+ y +mkd+ también son extensiones
|
|
de archivo válidas).
|
|
|
|
No es posible llamar métodos desde markdown, ni pasarle locales. Por lo tanto,
|
|
generalmente vas a usarlo en combinación con otro motor de renderizado:
|
|
|
|
erb :resumen, :locals => { :texto => markdown(:introduccion) }
|
|
|
|
Tené en cuenta que también podés llamar al método markdown desde otras
|
|
plantillas:
|
|
|
|
%h1 Hola Desde Haml!
|
|
%p= markdown(:saludos)
|
|
|
|
=== Plantilla Textile
|
|
|
|
La gem/librería RedCloth es necesaria para renderizar plantillas Textile:
|
|
|
|
## Vas a necesitar requerir redcloth en tu app
|
|
require "redcloth"
|
|
|
|
get '/' do
|
|
textile :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.textile</tt>.
|
|
|
|
No es posible llamar métodos desde textile, ni pasarle locales. Por lo tanto,
|
|
generalmente vas a usarlo en combinación con otro motor de renderizado:
|
|
|
|
erb :resumen, :locals => { :texto => textile(:introduccion) }
|
|
|
|
Tené en cuenta que también podés llamar al método textile desde otras
|
|
plantillas:
|
|
|
|
%h1 Hola Desde Haml!
|
|
%p= textile(:saludos)
|
|
|
|
=== Plantillas RDoc
|
|
|
|
La gem/librería RDoc es necesaria para renderizar plantillas RDoc:
|
|
|
|
## Vas a necesitar requerir rdoc en tu app
|
|
require "rdoc"
|
|
|
|
get '/' do
|
|
rdoc :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.rdoc</tt>.
|
|
|
|
No es posible llamar métodos desde rdoc, ni pasarle locales. Por lo tanto,
|
|
generalmente vas a usarlo en combinación con otro motor de renderizado:
|
|
|
|
erb :resumen, :locals => { :texto => rdoc(:introduccion) }
|
|
|
|
Tené en cuenta que también podés llamar al método rdoc desde otras
|
|
plantillas:
|
|
|
|
%h1 Hola Desde Haml!
|
|
%p= rdoc(:saludos)
|
|
|
|
=== Plantillas Radius
|
|
|
|
La gem/librería radius es necesaria para renderizar plantillas Radius:
|
|
|
|
## Vas a necesitar requerir radius en tu app
|
|
require 'radius'
|
|
|
|
get '/' do
|
|
radius :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.radius</tt>.
|
|
|
|
Como no vas a poder llamar a métodos de Ruby (excepto a +yield+) desde una
|
|
plantilla Radius, casi siempre vas a querer pasarle locales:
|
|
|
|
radius :index, :locals => { :clave => 'valor' }
|
|
|
|
=== Plantillas Markaby
|
|
|
|
La gem/librería markaby es necesaria para renderizar plantillas Markaby:
|
|
|
|
## Vas a necesitar requerir markaby en tu app
|
|
require 'markaby'
|
|
|
|
get '/' do
|
|
markaby :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.mab</tt>.
|
|
|
|
=== Plantillas CoffeeScript
|
|
|
|
La gem/librería coffee-script y el binario `coffee` son necesarios para
|
|
renderizar plantillas CoffeeScript:
|
|
|
|
## Vas a necesitar requerir coffee-script en tu app
|
|
require 'coffee-script'
|
|
|
|
get '/application.js' do
|
|
coffee :application
|
|
end
|
|
|
|
Renderiza <tt>./views/application.coffee</tt>.
|
|
|
|
=== Plantillas Inline
|
|
|
|
get '/' do
|
|
haml '%div.titulo Hola Mundo'
|
|
end
|
|
|
|
Renderiza el template contenido en el string.
|
|
|
|
=== Accediendo a Variables en Plantillas
|
|
|
|
Las plantillas son evaluadas dentro del mismo contexto que los manejadores de
|
|
ruta. Las variables de instancia asignadas en los manejadores de ruta son
|
|
accesibles directamente por las plantillas:
|
|
|
|
get '/:id' do
|
|
@foo = Foo.find(params[:id])
|
|
haml '%h1= @foo.nombre'
|
|
end
|
|
|
|
O es posible especificar un Hash de variables locales explícitamente:
|
|
|
|
get '/:id' do
|
|
foo = Foo.find(params[:id])
|
|
haml '%h1= foo.nombre', :locals => { :foo => foo }
|
|
end
|
|
|
|
Esto es usado típicamente cuando se renderizan plantillas como parciales desde
|
|
adentro de otras plantillas.
|
|
|
|
=== Plantillas Inline
|
|
|
|
Las plantillas pueden ser definidas al final del archivo fuente:
|
|
|
|
require 'rubygems'
|
|
require 'sinatra'
|
|
|
|
get '/' do
|
|
haml :index
|
|
end
|
|
|
|
__END__
|
|
|
|
@@ layout
|
|
%html
|
|
= yield
|
|
|
|
@@ index
|
|
%div.titulo Hola mundo!!!!!
|
|
|
|
NOTA: únicamente las plantillas inline definidas en el archivo fuente que
|
|
requiere sinatra son cargadas automáticamente. Llamá <tt>enable
|
|
:inline_templates</tt> explícitamente si tenés plantillas inline en otros
|
|
archivos fuente.
|
|
|
|
=== Plantillas Nombradas
|
|
|
|
Las plantillas también pueden ser definidas usando el método top-level
|
|
<tt>template</tt>:
|
|
|
|
template :layout do
|
|
"%html\n =yield\n"
|
|
end
|
|
|
|
template :index do
|
|
'%div.titulo Hola Mundo!'
|
|
end
|
|
|
|
get '/' do
|
|
haml :index
|
|
end
|
|
|
|
Si existe una plantilla con el nombre "layout", va a ser usada cada vez que
|
|
una plantilla es renderizada. Podés desactivar los layouts pasando
|
|
<tt>:layout => false</tt>.
|
|
|
|
get '/' do
|
|
haml :index, :layout => !request.xhr?
|
|
end
|
|
|
|
== Ayudantes
|
|
|
|
Usá el método top-level <tt>helpers</tt> para definir métodos ayudantes que
|
|
pueden ser utilizados dentro de los manejadores de rutas y las plantillas:
|
|
|
|
helpers do
|
|
def bar(nombre)
|
|
"#{nombre}bar"
|
|
end
|
|
end
|
|
|
|
get '/:nombre' do
|
|
bar(params[:nombre])
|
|
end
|
|
|
|
== Filtros
|
|
|
|
Los filtros before son evaluados antes de cada petición dentro del mismo
|
|
contexto que las rutas serán evaluadas y pueden modificar la petición y la
|
|
respuesta. Las variables de instancia asignadas en los filtros son accesibles
|
|
por las rutas y las plantillas:
|
|
|
|
before do
|
|
@nota = 'Hey!'
|
|
request.path_info = '/foo/bar/baz'
|
|
end
|
|
|
|
get '/foo/*' do
|
|
@nota #=> 'Hey!'
|
|
params[:splat] #=> 'bar/baz'
|
|
end
|
|
|
|
Los filtros after son evaluados después de cada petición dentro del mismo
|
|
contexto y también pueden modificar la petición y la respuesta. Las variables
|
|
de instancia asignadas en los filtros before y rutas son accesibles por los
|
|
filtros after:
|
|
|
|
after do
|
|
puts response.status
|
|
end
|
|
|
|
Los filtros aceptan un patrón opcional, que cuando está presente causa que los
|
|
mismos sean evaluados únicamente si el path de la petición coincide con ese
|
|
patrón:
|
|
|
|
before '/protegido/*' do
|
|
autenticar!
|
|
end
|
|
|
|
after '/crear/:slug' do |slug|
|
|
session[:ultimo_slug] = slug
|
|
end
|
|
|
|
== Interrupción
|
|
|
|
Para detener inmediatamente una petición dentro de un filtro o una ruta usá:
|
|
|
|
halt
|
|
|
|
También podés especificar el estado:
|
|
|
|
halt 410
|
|
|
|
O el cuerpo:
|
|
|
|
halt 'esto va a ser el cuerpo'
|
|
|
|
O los dos:
|
|
|
|
halt 401, 'salí de acá!'
|
|
|
|
Con cabeceras:
|
|
|
|
halt 402, { 'Content-Type' => 'text/plain' }, 'venganza'
|
|
|
|
== Paso
|
|
|
|
Una ruta puede pasarle el procesamiento a la siguiente ruta que coincida con
|
|
la petición usando <tt>pass</tt>:
|
|
|
|
get '/adivina/:quien' do
|
|
pass unless params[:quien] == 'Franco'
|
|
'Adivinaste!'
|
|
end
|
|
|
|
get '/adivina/*' do
|
|
'Erraste!'
|
|
end
|
|
|
|
Se sale inmediatamente del bloque de la ruta y se le pasa el control a la
|
|
siguiente ruta que coincida. Si no coincide ninguna ruta, se devuelve un 404.
|
|
|
|
== Accediendo al objeto de la petición
|
|
|
|
El objeto de la petición entrante puede ser accedido desde el nivel de la
|
|
petición (filtros, rutas y manejadores de errores) a través del método
|
|
`request`:
|
|
|
|
# app corriendo en http://ejemplo.com/ejemplo
|
|
get '/foo' do
|
|
request.body # cuerpo de la petición enviado por el cliente (ver más abajo)
|
|
request.scheme # "http"
|
|
request.script_name # "/ejemplo"
|
|
request.path_info # "/foo"
|
|
request.port # 80
|
|
request.request_method # "GET"
|
|
request.query_string # ""
|
|
request.content_length # longitud de request.body
|
|
request.media_type # tipo de medio de request.body
|
|
request.host # "ejemplo.com"
|
|
request.get? # verdadero (hay métodos análogos para los otros verbos)
|
|
request.form_data? # falso
|
|
request["UNA_CABECERA"] # valor de la cabecera UNA_CABECERA
|
|
request.referer # la referencia del cliente o '/'
|
|
request.user_agent # user agent (usado por la condición :agent)
|
|
request.cookies # hash de las cookies del browser
|
|
request.xhr? # es una petición ajax?
|
|
request.url # "http://ejemplo.com/ejemplo/foo"
|
|
request.path # "/ejemplo/foo"
|
|
request.ip # dirección IP del cliente
|
|
request.secure? # falso
|
|
requuest.env # hash de entorno directamente entregado por Rack
|
|
end
|
|
|
|
Algunas opciones, como <tt>script_name</tt> o <tt>path_info</tt> pueden
|
|
también ser escritas:
|
|
|
|
before { request.path_info = "/" }
|
|
|
|
get "/" do
|
|
"todas las peticiones llegan acá"
|
|
end
|
|
|
|
El objeto <tt>request.body</tt> es una instancia de IO o StringIO:
|
|
|
|
post "/api" do
|
|
request.body.rewind # en caso de que alguien ya lo haya leído
|
|
datos = JSON.parse request.body.read
|
|
"Hola #{datos['nombre']}!"
|
|
end
|
|
|
|
== Configuración
|
|
|
|
Ejecutar una vez, en el inicio, en cualquier entorno:
|
|
|
|
configure do
|
|
...
|
|
end
|
|
|
|
Ejecutar únicamente cuando el entorno (la variable de entorno RACK_ENV) es
|
|
<tt>:production</tt>:
|
|
|
|
configure :production do
|
|
...
|
|
end
|
|
|
|
Ejecutar cuando el entorno es <tt>:production</tt> o <tt>:test</tt>:
|
|
|
|
configure :production, :test do
|
|
...
|
|
end
|
|
|
|
== Manejo de errores
|
|
|
|
Los manejadores de errores se ejecutan dentro del mismo contexto que las rutas
|
|
y los filtros before, lo que significa que podés usar, por ejemplo,
|
|
<tt>haml</tt>, <tt>erb</tt>, <tt>halt</tt>, etc.
|
|
|
|
=== No encontrado <em>(Not Found)</em>
|
|
|
|
Cuando se eleva una excepción <tt>Sinatra::NotFound</tt>, o el código de
|
|
estado de la respuesta es 404, el manejador <tt>not_found</tt> es invocado:
|
|
|
|
not_found do
|
|
'No existo'
|
|
end
|
|
|
|
=== Error
|
|
|
|
El manejador +error+ es invocado cada vez que una excepción es elevada
|
|
desde un bloque de ruta o un filtro. El objeto de la excepción se puede
|
|
obtener de la variable Rack <tt>sinatra.error</tt>:
|
|
|
|
error do
|
|
'Disculpá, ocurrió un error horrible - ' + env['sinatra.error'].name
|
|
end
|
|
|
|
Errores personalizados:
|
|
|
|
error MiErrorPersonalizado do
|
|
'Lo que pasó fue...' request.env['sinatra.error'].message
|
|
end
|
|
|
|
Entonces, si pasa esto:
|
|
|
|
get '/' do
|
|
raise MiErrorPersonalizado, 'algo malo'
|
|
end
|
|
|
|
Obtenés esto:
|
|
|
|
Lo que pasó fue... algo malo
|
|
|
|
También, podés instalar un manejador de errores para un código de estado:
|
|
|
|
error 403 do
|
|
'Acceso prohibido'
|
|
end
|
|
|
|
get '/secreto' do
|
|
403
|
|
end
|
|
|
|
O un rango:
|
|
|
|
error 400..510 do
|
|
'Boom'
|
|
end
|
|
|
|
Sinatra instala manejadores <tt>not_found</tt> y <tt>error</ttt> especiales
|
|
cuando se ejecuta dentro del entorno de desarrollo "development".
|
|
|
|
== Tipos Mime
|
|
|
|
Cuando usás <tt>send_file</tt> o archivos estáticos tal vez tengas tipos mime
|
|
que Sinatra no entiende. Usá +mime_type+ para registrarlos a través de la
|
|
extensión de archivo:
|
|
|
|
mime_type :foo, 'text/foo'
|
|
|
|
También lo podés usar con el ayudante +content_type+:
|
|
|
|
content_type :foo
|
|
|
|
== Rack Middleware
|
|
|
|
Sinatra corre sobre Rack[http://rack.rubyforge.org/], una interfaz minimalista
|
|
que es un estándar para frameworks webs escritos en Ruby. Una de las
|
|
capacidades más interesantes de Rack para los desarrolladores de aplicaciones
|
|
es el soporte de "middleware" -- componentes que se ubican entre el servidor y
|
|
tu aplicación, supervisando y/o manipulando la petición/respuesta HTTP para
|
|
proporcionar varios tipos de funcionalidades comunes.
|
|
|
|
Sinatra hace muy sencillo construir tuberías de Rack middleware a través del
|
|
método top-level +use+:
|
|
|
|
require 'sinatra'
|
|
require 'mi_middleware_personalizado'
|
|
|
|
use Rack::Lint
|
|
use MiMiddlewarePersonalizado
|
|
|
|
get '/hola' do
|
|
'Hola Mundo'
|
|
end
|
|
|
|
Las semánticas de +use+ son idénticas a las definidas para el DSL
|
|
Rack::Builder[http://rack.rubyforge.org/doc/classes/Rack/Builder.html] (más
|
|
frecuentemente usado desde archivos rackup). Por ejemplo, el método +use+
|
|
acepta argumentos múltiples/variables así como bloques:
|
|
|
|
use Rack::Auth::Basic do |nombre_de_usuario, password|
|
|
nombre_de_usuario == 'admin' && password == 'secreto'
|
|
end
|
|
|
|
Rack es distribuido con una variedad de middleware estándar para logging,
|
|
debugging, enrutamiento URL, autenticación, y manejo de sesiones. Sinatra
|
|
usa muchos de estos componentes automáticamente de acuerdo a su configuración
|
|
para que típicamente no tengas que usarlas (con +use+) explícitamente.
|
|
|
|
== Pruebas
|
|
|
|
Las pruebas para las aplicaciones Sinatra pueden ser escritas utilizando
|
|
cualquier framework o librería de pruebas basada en Rack. Se recomienda usar
|
|
{Rack::Test}[http://gitrdoc.com/brynary/rack-test]:
|
|
|
|
require 'mi_app_sinatra'
|
|
require 'test/unit'
|
|
require 'rack/test'
|
|
|
|
class MiAppTest < Test::Unit::TestCase
|
|
include Rack::Test::Methods
|
|
|
|
def app
|
|
Sinatra::Application
|
|
end
|
|
|
|
def test_mi_defecto
|
|
get '/'
|
|
assert_equal 'Hola Mundo!', last_response.body
|
|
end
|
|
|
|
def test_con_parametros
|
|
get '/saludar', :name => 'Franco'
|
|
assert_equal 'Hola Frank!', last_response.body
|
|
end
|
|
|
|
def test_con_entorno_rack
|
|
get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
|
|
assert_equal "Estás usando Songbird!", last_response.body
|
|
end
|
|
end
|
|
|
|
NOTA: El módulo Sinatra::Test y la clase Sinatra::TestHarness están
|
|
deprecados a partir de la versión 0.9.2.
|
|
|
|
== Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares
|
|
|
|
Definir tu aplicación en el top-level funciona bien para micro-aplicaciones
|
|
pero trae inconvenientes considerables a la hora de construir componentes
|
|
reutilizables como Rack middleware, Rails metal, simple librerías con un
|
|
componente de servidor, o incluso extensiones de Sinatra. El DSL de top-level
|
|
contamina el espacio de nombres de Object y asume una configuración apropiada
|
|
para micro-aplicaciones (por ejemplo, un único archivo de aplicación, los
|
|
directorios ./public y ./views, logging, página con detalles de excepción,
|
|
etc.). Ahí es donde Sinatra::Base entra en el juego:
|
|
|
|
require 'sinatra/base'
|
|
|
|
class MiApp < Sinatra::Base
|
|
set :sessions, true
|
|
set :foo, 'bar'
|
|
|
|
get '/' do
|
|
'Hola Mundo!'
|
|
end
|
|
end
|
|
|
|
La clase MiApp es un componente Rack independiente que puede actuar como Rack
|
|
middleware, una aplicación Rack, o Rails metal. Podés usar (con +use+) o
|
|
ejecutar (con +run+) esta clase desde un archivo rackup +config.ru+; o,
|
|
controlar un componente de servidor provisto como una librería:
|
|
|
|
MiApp.run! :host => 'localhost', :port => 9090
|
|
|
|
Las subclases de Sinatra::Base tienen disponibles exactamente los mismos
|
|
métodos que los provistos por el DSL de top-level. La mayoría de las
|
|
aplicaciones top-level se pueden convertir en componentes Sinatra::Base con
|
|
dos modificaciones:
|
|
|
|
* Tu archivo debe requerir +sinatra/base+ en lugar de +sinatra+; de otra
|
|
manera, todos los métodos del DSL de sinatra son importados dentro del
|
|
espacio de nombres principal.
|
|
* Poné las rutas, manejadores de errores, filtros y opciones de tu aplicación
|
|
en una subclase de Sinatra::Base.
|
|
|
|
<tt>Sinatra::Base</tt> es una pizarra en blanco. La mayoría de las opciones están
|
|
desactivadas por defecto, incluyendo el servidor incorporado. Mirá
|
|
{Opciones y Configuraciones}[http://sinatra.github.com/configuration.html]
|
|
para detalles sobre las opciones disponibles y su comportamiento.
|
|
|
|
=== Utilizando Sinatra como Middleware
|
|
|
|
Sinatra no solo es capaz de usar otro Rack middleware, sino que a su vez,
|
|
cualquier aplicación Sinatra puede ser agregada delante de un endpoint Rack
|
|
como middleware. Este endpoint puede ser otra aplicación Sinatra, o cualquier
|
|
aplicación basada en Rack (Rails/Ramaze/Camping/...).
|
|
|
|
require 'sinatra/base'
|
|
|
|
class PantallaDeLogin< Sinatra::Base
|
|
enable :session
|
|
|
|
get('/login') { haml :login }
|
|
|
|
post('/login') do
|
|
if params[:nombre] = 'admin' and params[:password] = 'admin'
|
|
session['nombre_de_usuario'] = params[:nombre]
|
|
else
|
|
redirect '/login'
|
|
end
|
|
end
|
|
end
|
|
|
|
class MiApp < Sinatra::Base
|
|
# el middleware se ejecutará antes que los filtros
|
|
use PantallaDeLogin
|
|
|
|
before do
|
|
unless session['nombre_de_usuario']
|
|
halt "Acceso denegado, por favor <a href='/login'>iniciá sesión</a>."
|
|
end
|
|
end
|
|
|
|
get('/') { "Hola #{session['nombre_de_usuario']}." }
|
|
end
|
|
|
|
== Ámbitos y Ligaduras
|
|
|
|
El ámbito en el que te encontrás determina que métodos y variables están
|
|
disponibles.
|
|
|
|
=== Ámbito de Aplicación/Clase
|
|
|
|
Cada aplicación Sinatra es una subclase de Sinatra::Base. Si estás usando el
|
|
DSL de top-level (<tt>require 'sinatra'</tt>), entonces esta clase es
|
|
Sinatra::Application, de otra manera es la subclase que creaste explícitamente.
|
|
Al nivel de la clase tenés métodos como `get` o `before`, pero no podés acceder
|
|
a los objetos `request` o `session`, ya que hay una única clase de la
|
|
aplicación para todas las peticiones.
|
|
|
|
Las opciones creadas utilizando `set` son métodos al nivel de la clase:
|
|
|
|
class MiApp << Sinatra::Base
|
|
# Ey, estoy en el ámbito de la aplicación!
|
|
set :foo, 42
|
|
foo # => 42
|
|
|
|
get '/foo' do
|
|
# Hey, ya no estoy en el ámbito de la aplicación!
|
|
end
|
|
end
|
|
|
|
Tenés la ligadura al ámbito de la aplicación dentro de:
|
|
|
|
* El cuerpo de la clase de tu aplicación
|
|
* Métodos definidos por extensiones
|
|
* El bloque pasado a `helpers`
|
|
* Procs/bloques usados como el valor para `set`
|
|
|
|
Este ámbito puede alcanzarse de las siguientes maneras:
|
|
|
|
* A través del objeto pasado a los bloques de configuración (<tt>configure { |c| ...}</tt>)
|
|
* Llamando a `settings` desde dentro del ámbito de la petición
|
|
|
|
=== Ámbito de Petición/Instancia
|
|
|
|
Para cada petición entrante, una nueva instancia de la clase de tu aplicación
|
|
es creada y todos los bloques de rutas son ejecutados en ese ámbito. Desde este
|
|
ámbito podés acceder a los objetos `request` y `session` o llamar a los métodos
|
|
de renderización como `erb` o `haml`. Podés acceder al ámbito de la aplicación
|
|
desde el ámbito de la petición utilizando `settings`:
|
|
|
|
class MiApp << Sinatra::Base
|
|
# Ey, estoy en el ámbito de la aplicación!
|
|
get '/definir_ruta/:nombre' do
|
|
# Ámbito de petición para '/definir_ruta/:nombre'
|
|
@valor = 42
|
|
|
|
settings.get("/#{params[:nombre]}") do
|
|
# Ámbito de petición para "/#{params[:nombre]}"
|
|
@valor # => nil (no es la misma petición)
|
|
end
|
|
|
|
"Ruta definida!"
|
|
end
|
|
end
|
|
|
|
Tenés la ligadura al ámbito de la petición dentro de:
|
|
|
|
* bloques pasados a get/head/post/put/delete
|
|
* filtros before/after
|
|
* métodos ayudantes
|
|
* plantillas/vistas
|
|
|
|
=== Ámbito de Delegación
|
|
|
|
El ámbito de delegación solo reenvía métodos al ámbito de clase. De cualquier
|
|
manera, no se comporta 100% como el ámbito de clase porque no tenés la ligadura
|
|
de la clase: únicamente métodos marcados explícitamente para delegación están
|
|
disponibles y no compartís variables/estado con el ámbito de clase (léase:
|
|
tenés un `self` diferente). Podés agregar delegaciones de método llamando a
|
|
<tt>Sinatra::Delegator.delegate :nombre_del_metodo</tt>.
|
|
|
|
Tenés la ligadura al ámbito de delegación dentro de:
|
|
|
|
* La ligadura del top-level, si hiciste <tt>require "sinatra"</tt>
|
|
* Un objeto extendido con el mixin `Sinatra::Delegator`
|
|
|
|
Pegale una mirada al código: acá está el
|
|
{Sinatra::Delegator mixin}[http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128]
|
|
que es {incluido en el espacio de nombres principal}[http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28].
|
|
|
|
== Línea de comandos
|
|
|
|
Las aplicaciones Sinatra pueden ser ejecutadas directamente:
|
|
|
|
ruby miapp.rb [-h] [-x] [-e ENTORNO] [-p PUERTO] [-o HOST] [-s MANEJADOR]
|
|
|
|
Las opciones son:
|
|
|
|
-h # ayuda
|
|
-p # asigna el puerto (4567 es usado por defecto)
|
|
-o # asigna el host (0.0.0.0 es usado por defecto)
|
|
-e # asigna el entorno (development es usado por defecto)
|
|
-s # especifica el servidor/manejador rack (thin es usado por defecto)
|
|
-x # activa el mutex lock (está desactivado por defecto)
|
|
|
|
== A la vanguardia
|
|
|
|
Si querés usar el código de Sinatra más reciente, cloná el repositorio
|
|
localmente y ejecutá tu aplicación, asegurándote que el directorio
|
|
<tt>sinatra/lib</tt> esté en el <tt>LOAD_PATH</tt>:
|
|
|
|
cd miapp
|
|
git clone git://github.com/sinatra/sinatra.git
|
|
ruby -Isinatra/lib miapp.rb
|
|
|
|
Otra opción consiste en agregar el directorio <tt>sinatra/lib</tt> al
|
|
<tt>LOAD_PATH</tt> dentro de tu aplicación:
|
|
|
|
$LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
|
|
require 'rubygems'
|
|
require 'sinatra'
|
|
|
|
get '/acerca-de' do
|
|
"Estoy usando la versión " + Sinatra::VERSION
|
|
end
|
|
|
|
Para actualizar el código fuente de Sinatra en el futuro:
|
|
|
|
cd miproyecto/sinatra
|
|
git pull
|
|
|
|
== Más
|
|
|
|
* {Sito web del proyecto}[http://www.sinatrarb.com/] - Documentación
|
|
adicional, noticias, y enlaces a otros recursos.
|
|
* {Contribuyendo}[http://www.sinatrarb.com/contributing] - ¿Encontraste un
|
|
error?. ¿Necesitás ayuda?. ¿Tenés un parche?.
|
|
* {Seguimiento de problemas}[http://github.com/sinatra/sinatra/issues]
|
|
* {Twitter}[http://twitter.com/sinatra]
|
|
* {Lista de Correo}[http://groups.google.com/group/sinatrarb/topics]
|
|
* {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] en http://freenode.net
|