= Sinatra 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 params: 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 params[:splat]. 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 ./public. Podés especificar una ubicación diferente ajustando la opción :public: set :public, File.dirname(__FILE__) + '/estaticos' Notá que el nombre del directorio público no está incluido en la URL. Por ejemplo, el archivo ./public/css/style.css se accede a través de http://ejemplo.com/css/style.css. == Vistas / Plantillas Se asume que las plantillas están ubicadas directamente bajo el directorio ./views. 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 :'subdir/plantilla'). 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 ./views/index.haml. 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 ./views/index.erb === 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 ./views/index.erubis === 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 ./views/index.builder. === 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 ./views/stylesheet.sass. 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 ./views/stylesheet.less. === 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 template: 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 :layout => false. get '/' do haml :index, :layout => !request.xhr? end == Ayudantes Usá el método top-level helpers 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 pass: 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 :production: configure :production do ... end Ejecutar cuando el entorno es :production o :test: 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, haml, erb, halt, etc. === No encontrado (Not Found) Cuando se eleva una excepción Sinatra::NotFound, o el código de estado de la respuesta es 404, el manejador not_found 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 sinatra.error: 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 not_found y error especiales cuando se ejecuta dentro del entorno de desarrollo "development". == Tipos Mime Cuando usás send_file 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 sinatra/lib esté en el LOAD_PATH: cd miapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib miapp.rb Otra opción consiste en agregar el directorio sinatra/lib al LOAD_PATH 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