mirror of
https://github.com/sinatra/sinatra
synced 2023-03-27 23:18:01 -04:00
1abac3217f
French translation by Gabriel Andretta (ohhgabriel) Spanish translation by michelc Japanese translation by Kiko Uehara (kiko) Japanese translation also includes 'Please refer to original English document for the most up to date information.' More translations and discussion: http://gist.github.com/589498
659 lines
18 KiB
Text
659 lines
18 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
|
|
|
|
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
|
|
|
|
== 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>). 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
|
|
content_type 'application/xml', :charset => 'utf-8'
|
|
builder :index
|
|
end
|
|
|
|
Renderiza <tt>./views/index.builder</tt>.
|
|
|
|
=== Plantillas Sass
|
|
|
|
La gem/librería sass es necesaria para renderizar plantillas Sass:
|
|
|
|
## Vas a necesitar requerir haml o sass en tu app
|
|
require 'sass'
|
|
|
|
get '/stylesheet.css' do
|
|
content_type 'text/css', :charset => 'utf-8'
|
|
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
|
|
content_type 'text/css', :charset => 'utf-8'
|
|
sass :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
|
|
content_type 'text/css', :charset => 'utf-8'
|
|
less :stylesheet
|
|
end
|
|
|
|
Renderiza <tt>./views/stylesheet.less</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á
|
|
`enable :inline_templates` 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 contexto de
|
|
la petición 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 contexto de
|
|
la petición 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 encabezados ...
|
|
|
|
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.
|
|
|
|
== 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 '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
|
|
reusables 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.
|
|
|
|
+Sinatra::Base+ 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.
|
|
|
|
NOTA AL MÁRGEN: el DSL de top-level de Sinatra es implementado usando un
|
|
simple sistema de delegación. La clase +Sinatra::Application+ -- una subclase
|
|
especial de Sinatra::Base -- recive todos los mensajes :get, :put, :post,
|
|
:delete, :before, :error, :not_found, :configure y :set enviados al top-level.
|
|
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?.
|
|
* {Issue tracker}[http://sinatra.lighthouseapp.com]
|
|
* {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
|