sinatra-base 1.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. data/.yardopts +4 -0
  2. data/AUTHORS +15 -0
  3. data/CHANGES +524 -1
  4. data/Gemfile +82 -0
  5. data/LICENSE +1 -1
  6. data/README.de.rdoc +2093 -0
  7. data/README.es.rdoc +2091 -0
  8. data/README.fr.rdoc +2116 -0
  9. data/README.hu.rdoc +607 -0
  10. data/README.jp.rdoc +514 -23
  11. data/README.pt-br.rdoc +647 -0
  12. data/README.pt-pt.rdoc +646 -0
  13. data/README.rdoc +1580 -205
  14. data/README.ru.rdoc +2015 -0
  15. data/README.zh.rdoc +1816 -0
  16. data/Rakefile +110 -44
  17. data/examples/chat.rb +61 -0
  18. data/examples/simple.rb +3 -0
  19. data/examples/stream.ru +26 -0
  20. data/lib/sinatra.rb +0 -3
  21. data/lib/sinatra/base.rb +923 -393
  22. data/lib/sinatra/main.rb +9 -7
  23. data/lib/sinatra/showexceptions.rb +37 -4
  24. data/lib/sinatra/version.rb +3 -0
  25. data/sinatra-base.gemspec +15 -91
  26. data/test/base_test.rb +2 -2
  27. data/test/builder_test.rb +32 -2
  28. data/test/coffee_test.rb +92 -0
  29. data/test/contest.rb +62 -28
  30. data/test/creole_test.rb +65 -0
  31. data/test/delegator_test.rb +162 -0
  32. data/test/encoding_test.rb +20 -0
  33. data/test/erb_test.rb +25 -2
  34. data/test/extensions_test.rb +1 -1
  35. data/test/filter_test.rb +226 -8
  36. data/test/haml_test.rb +8 -2
  37. data/test/helper.rb +47 -0
  38. data/test/helpers_test.rb +1287 -80
  39. data/test/integration/app.rb +62 -0
  40. data/test/integration_helper.rb +208 -0
  41. data/test/integration_test.rb +82 -0
  42. data/test/less_test.rb +36 -6
  43. data/test/liquid_test.rb +59 -0
  44. data/test/mapped_error_test.rb +84 -7
  45. data/test/markaby_test.rb +80 -0
  46. data/test/markdown_test.rb +81 -0
  47. data/test/middleware_test.rb +1 -1
  48. data/test/nokogiri_test.rb +69 -0
  49. data/test/rack_test.rb +45 -0
  50. data/test/radius_test.rb +59 -0
  51. data/test/rdoc_test.rb +66 -0
  52. data/test/readme_test.rb +136 -0
  53. data/test/request_test.rb +13 -1
  54. data/test/response_test.rb +21 -2
  55. data/test/result_test.rb +5 -5
  56. data/test/route_added_hook_test.rb +1 -1
  57. data/test/routing_test.rb +328 -13
  58. data/test/sass_test.rb +48 -18
  59. data/test/scss_test.rb +88 -0
  60. data/test/server_test.rb +4 -3
  61. data/test/settings_test.rb +191 -21
  62. data/test/sinatra_test.rb +5 -1
  63. data/test/slim_test.rb +88 -0
  64. data/test/static_test.rb +89 -5
  65. data/test/streaming_test.rb +140 -0
  66. data/test/templates_test.rb +143 -4
  67. data/test/textile_test.rb +65 -0
  68. data/test/views/a/in_a.str +1 -0
  69. data/test/views/ascii.erb +2 -0
  70. data/test/views/b/in_b.str +1 -0
  71. data/test/views/calc.html.erb +1 -0
  72. data/test/views/explicitly_nested.str +1 -0
  73. data/test/views/hello.coffee +1 -0
  74. data/test/views/hello.creole +1 -0
  75. data/test/views/hello.liquid +1 -0
  76. data/test/views/hello.mab +1 -0
  77. data/test/views/hello.md +1 -0
  78. data/test/views/hello.nokogiri +1 -0
  79. data/test/views/hello.radius +1 -0
  80. data/test/views/hello.rdoc +1 -0
  81. data/test/views/hello.sass +1 -1
  82. data/test/views/hello.scss +3 -0
  83. data/test/views/hello.slim +1 -0
  84. data/test/views/hello.str +1 -0
  85. data/test/views/hello.textile +1 -0
  86. data/test/views/hello.yajl +1 -0
  87. data/test/views/layout2.liquid +2 -0
  88. data/test/views/layout2.mab +2 -0
  89. data/test/views/layout2.nokogiri +3 -0
  90. data/test/views/layout2.radius +2 -0
  91. data/test/views/layout2.slim +3 -0
  92. data/test/views/layout2.str +2 -0
  93. data/test/views/nested.str +1 -0
  94. data/test/views/utf8.erb +2 -0
  95. data/test/yajl_test.rb +80 -0
  96. metadata +126 -91
  97. data/lib/sinatra/tilt.rb +0 -746
  98. data/test/erubis_test.rb +0 -82
  99. data/test/views/error.erubis +0 -3
  100. data/test/views/hello.erubis +0 -1
  101. data/test/views/layout2.erubis +0 -2
@@ -0,0 +1,2091 @@
1
+ = Sinatra
2
+ <i>Atención: Este documento es una traducción de la versión en inglés y puede estar desactualizado.</i>
3
+
4
+ Sinatra es un DSL para crear aplicaciones web rápidamente en Ruby con un mínimo
5
+ esfuerzo:
6
+
7
+ # miapp.rb
8
+ require 'sinatra'
9
+
10
+ get '/' do
11
+ 'Hola mundo!'
12
+ end
13
+
14
+ Instalá la gem y ejecutá la aplicación con:
15
+
16
+ gem install sinatra
17
+ ruby -rubygems miapp.rb
18
+
19
+ Podés verla en: http://localhost:4567
20
+
21
+ Es recomendable además ejecutar <tt>gem install thin</tt>, ya que Sinatra lo va
22
+ a utilizar cuando esté disponible.
23
+
24
+ == Rutas
25
+
26
+ En Sinatra, una ruta está compuesta por un método HTTP y un patrón de una URL.
27
+ Cada ruta se asocia con un bloque:
28
+
29
+ get '/' do
30
+ .. mostrar algo ..
31
+ end
32
+
33
+ post '/' do
34
+ .. crear algo ..
35
+ end
36
+
37
+ put '/' do
38
+ .. reemplazar algo ..
39
+ end
40
+
41
+ patch '/' do
42
+ .. modificar algo ..
43
+ end
44
+
45
+ delete '/' do
46
+ .. aniquilar algo ..
47
+ end
48
+
49
+ options '/' do
50
+ .. informar algo ..
51
+ end
52
+
53
+
54
+ Las rutas son comparadas en el orden en el que son definidas. La primer ruta
55
+ que coincide con la petición es invocada.
56
+
57
+ Los patrones de las rutas pueden incluir parámetros nombrados, accesibles a
58
+ través de el hash <tt>params</tt>:
59
+
60
+ get '/hola/:nombre' do
61
+ # coincide con "GET /hola/foo" y "GET /hola/bar"
62
+ # params[:nombre] es 'foo' o 'bar'
63
+ "Hola #{params[:nombre]}!"
64
+ end
65
+
66
+ También podés acceder a los parámetros nombrados usando parámetros de bloque:
67
+
68
+ get '/hola/:nombre' do |n|
69
+ "Hola #{n}!"
70
+ end
71
+
72
+ Los patrones de ruta también pueden incluir parámetros splat (o wildcard),
73
+ accesibles a través del arreglo <tt>params[:splat]</tt>:
74
+
75
+ get '/decir/*/al/*' do
76
+ # coincide con /decir/hola/al/mundo
77
+ params[:splat] # => ["hola", "mundo"]
78
+ end
79
+
80
+ get '/descargar/*.*' do
81
+ # coincide con /descargar/path/al/archivo.xml
82
+ params[:splat] # => ["path/al/archivo", "xml"]
83
+ end
84
+
85
+ O, con parámetros de bloque:
86
+
87
+ get '/descargar/*.*' do |path, ext|
88
+ [path, ext] # => ["path/al/archivo", "xml"]
89
+ end
90
+
91
+ Rutas con Expresiones Regulares:
92
+
93
+ get %r{/hola/([\w]+)} do
94
+ "Hola, #{params[:captures].first}!"
95
+ end
96
+
97
+ O con un parámetro de bloque:
98
+
99
+ get %r{/hola/([\w]+)} do |c|
100
+ "Hola, #{c}!"
101
+ end
102
+
103
+ Los patrones de ruta pueden contener parámetros opcionales:
104
+
105
+ get '/posts.?:formato?' do
106
+ # coincide con "GET /posts" y además admite cualquier extensión, por
107
+ # ejemplo, "GET /posts.json", "GET /posts.xml", etc.
108
+ end
109
+
110
+ A propósito, a menos que desactivés la protección para el ataque <em>path
111
+ traversal</em> (ver más abajo), el path de la petición puede ser modificado
112
+ antes de que se compare con los de tus rutas.
113
+
114
+ === Condiciones
115
+
116
+ Las rutas pueden incluir una variedad de condiciones de selección, como por
117
+ ejemplo el user agent:
118
+
119
+ get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
120
+ "Estás usando la versión de Songbird #{params[:agent][0]}"
121
+ end
122
+
123
+ get '/foo' do
124
+ # Coincide con browsers que no sean songbird
125
+ end
126
+
127
+ Otras condiciones disponibles son +host_name+ y +provides+:
128
+
129
+ get '/', :host_name => /^admin\./ do
130
+ "Área de Administración, Acceso denegado!"
131
+ end
132
+
133
+ get '/', :provides => 'html' do
134
+ haml :index
135
+ end
136
+
137
+ get '/', :provides => ['rss', 'atom', 'xml'] do
138
+ builder :feed
139
+ end
140
+
141
+ Podés definir tus propias condiciones fácilmente:
142
+
143
+ set(:probabilidad) { |valor| condition { rand <= valor } }
144
+
145
+ get '/gana_un_auto', :probabilidad => 0.1 do
146
+ "Ganaste!"
147
+ end
148
+
149
+ get '/gana_un_auto' do
150
+ "Lo siento, perdiste."
151
+ end
152
+
153
+ Si tu condición acepta más de un argumento, podés pasarle un arreglo. Al
154
+ definir la condición puede resultarte conveniente utilizar el operador splat en
155
+ la lista de parámetros:
156
+
157
+ set(:autorizar) do |*roles| # <- mirá el splat
158
+ condition do
159
+ unless sesion_iniciada? && roles.any? {|rol| usuario_actual.tiene_rol? rol }
160
+ redirect "/iniciar_sesion/", 303
161
+ end
162
+ end
163
+ end
164
+
165
+ get "/mi/cuenta/", :autorizar => [:usuario, :administrador] do
166
+ "Detalles de mi cuenta"
167
+ end
168
+
169
+ get "/solo/administradores/", :autorizar => :administrador do
170
+ "Únicamente para administradores!"
171
+ end
172
+
173
+ === Valores de Retorno
174
+
175
+ El valor de retorno de un bloque de ruta determina al menos el cuerpo de la
176
+ respuesta que se le pasa al cliente HTTP o al siguiente middleware en la pila
177
+ de Rack. Lo más común es que sea un string, como en los ejemplos anteriores.
178
+ Sin embargo, otros valor también son aceptados.
179
+
180
+ Podés devolver cualquier objeto que sea una respuesta Rack válida, un objeto
181
+ que represente el cuerpo de una respuesta Rack o un código de estado HTTP:
182
+
183
+ * Un arreglo con tres elementos: <tt>[estado (Fixnum), cabeceras (Hash), cuerpo de la respuesta (responde a #each)]</tt>
184
+ * Un arreglo con dos elementos: <tt>[estado (Fixnum), cuerpo de la respuesta (responde a #each)]</tt>
185
+ * Un objeto que responde a <tt>#each</tt> y que le pasa únicamente strings al bloque dado
186
+ * Un Fixnum representando el código de estado
187
+
188
+ De esa manera podemos, por ejemplo, implementar fácilmente un streaming:
189
+
190
+ class Stream
191
+ def each
192
+ 100.times { |i| yield "#{i}\n" }
193
+ end
194
+ end
195
+
196
+ get('/') { Stream.new }
197
+
198
+ === Comparadores de Rutas Personalizados
199
+
200
+ Como se mostró anteriormente, Sinatra permite utilizar Strings y expresiones
201
+ regulares para definir las rutas. Sin embargo, la cosa no termina ahí. Podés
202
+ definir tus propios comparadores muy fácilmente:
203
+
204
+ class PattronCualquieraMenos
205
+ Match = Struct.new(:captures)
206
+
207
+ def initialize(excepto)
208
+ @excepto = excepto
209
+ @capturas = Match.new([])
210
+ end
211
+
212
+ def match(str)
213
+ @capturas unless @excepto === str
214
+ end
215
+ end
216
+
217
+ def cualquiera_menos(patron)
218
+ PatronCualquieraMenos.new(patron)
219
+ end
220
+
221
+ get cualquiera_menos("/index") do
222
+ # ...
223
+ end
224
+
225
+ Tené en cuenta que el ejemplo anterior es un poco rebuscado. Un resultado
226
+ similar puede conseguirse más sencillamente:
227
+
228
+ get // do
229
+ pass if request.path_info == "/index"
230
+ # ...
231
+ end
232
+
233
+ O, usando un lookahead negativo:
234
+
235
+ get %r{^(?!/index$)} do
236
+ # ...
237
+ end
238
+
239
+ == Archivos Estáticos
240
+
241
+ Los archivos estáticos son servidos desde el directorio público
242
+ <tt>./public</tt>. Podés especificar una ubicación diferente ajustando la
243
+ opción <tt>:public_folder</tt>:
244
+
245
+ set :public_folder, File.dirname(__FILE__) + '/estaticos'
246
+
247
+ Notá que el nombre del directorio público no está incluido en la URL. Por
248
+ ejemplo, el archivo <tt>./public/css/style.css</tt> se accede a través de
249
+ <tt>http://ejemplo.com/css/style.css</tt>.
250
+
251
+ Usá la configuración <tt>:static_cache_control</tt> para agregar el encabezado
252
+ <tt>Cache-Control</tt> (ver la sección de configuración para más detalles).
253
+
254
+ == Vistas / Plantillas
255
+
256
+ Cada lenguaje de plantilla se expone a través de un método de renderizado que
257
+ lleva su nombre. Estos métodos simplemente devuelven un string:
258
+
259
+ get '/' do
260
+ erb :index
261
+ end
262
+
263
+ Renderiza <tt>views/index.erb</tt>.
264
+
265
+ En lugar del nombre de la plantilla podés proporcionar directamente el
266
+ contenido de la misma:
267
+
268
+ get '/' do
269
+ codigo = "<%= Time.now %>"
270
+ erb codigo
271
+ end
272
+
273
+ Los métodos de renderizado, aceptan además un segundo argumento, el hash de
274
+ opciones:
275
+
276
+ get '/' do
277
+ erb :index, :layout => :post
278
+ end
279
+
280
+ Renderiza <tt>views/index.erb</tt> embebido en <tt>views/post.erb</tt> (por
281
+ defecto, la plantilla :index es embebida en <tt>views/layout.erb</tt> siempre y
282
+ cuando este último archivo exista).
283
+
284
+ Cualquier opción que Sinatra no entienda le será pasada al motor de renderizado
285
+ de la plantilla:
286
+
287
+ get '/' do
288
+ haml :index, :format => :html5
289
+ end
290
+
291
+ Además podés definir las opciones para un lenguaje de plantillas de forma
292
+ general:
293
+
294
+ set :haml, :format => :html5
295
+
296
+ get '/' do
297
+ haml :index
298
+ end
299
+
300
+ Las opciones pasadas al método de renderizado tienen precedencia sobre las
301
+ definidas mediante +set+.
302
+
303
+ Opciones disponibles:
304
+
305
+ [locals]
306
+ Lista de variables locales pasadas al documento. Resultan muy útiles cuando
307
+ se combinan con parciales.
308
+ Ejemplo: <tt>erb "<%= foo %>", :locals => {:foo => "bar"}</tt>
309
+
310
+ [default_encoding]
311
+ Encoding utilizado cuando el de un string es dudoso. Por defecto toma el
312
+ valor de <tt>settings.default_encoding</tt>.
313
+
314
+ [views]
315
+ Directorio desde donde se cargan las vistas. Por defecto toma el valor de
316
+ <tt>settings.views</tt>.
317
+
318
+ [layout]
319
+ Si es +true+ o +false+ indica que se debe usar, o nó, un layout,
320
+ respectivamente. También puede ser un símbolo que especifique qué plantilla
321
+ usar. Ejemplo: <tt>erb :index, :layout => !request.xhr?</tt>
322
+
323
+ [content_type]
324
+ Content-Type que produce la plantilla. El valor por defecto depende de cada
325
+ lenguaje de plantillas.
326
+
327
+ [scope]
328
+ Ámbito en el que se renderiza la plantilla. Por defecto utiliza la instancia
329
+ de la aplicación. Tené en cuenta que si cambiás esta opción las variables de
330
+ instancia y los helpers van a dejar de estar disponibles.
331
+
332
+ [layout_engine]
333
+ Motor de renderizado de plantillas que usa para el layout. Resulta
334
+ conveniente para lenguajes que no soportan layouts. Por defecto toma el valor
335
+ del motor usado para renderizar la plantilla.
336
+ Ejemplo: <tt>set :rdoc, :layout_engine => :erb</tt>
337
+
338
+ Se asume que las plantillas están ubicadas directamente bajo el directorio
339
+ <tt>./views</tt>. Para usar un directorio de vistas diferente:
340
+
341
+ set :views, settings.root + '/plantillas'
342
+
343
+ Es importante acordarse que siempre tenés que referenciar a las plantillas con
344
+ símbolos, incluso cuando se encuentran en un subdirectorio (en este caso tenés
345
+ que usar <tt>:'subdir/plantilla'</tt>). Tenés que usar un símbolo porque los
346
+ métodos de renderización van a renderizar directamente cualquier string que se
347
+ les pase como argumento.
348
+
349
+ === Lenguajes de Plantillas Disponibles
350
+
351
+ Algunos lenguajes tienen varias implementaciones. Para especificar que
352
+ implementación usar (y para ser thread-safe), deberías requerirla antes de
353
+ usarla:
354
+
355
+ require 'rdiscount' # o require 'bluecloth'
356
+ get('/') { markdown :index }
357
+
358
+ === Plantillas Haml
359
+
360
+ Dependencias:: {haml}[http://haml-lang.com/]
361
+ Extensiones de Archivo:: <tt>.haml</tt>
362
+ Ejemplo:: <tt>haml :index, :format => :html5</tt>
363
+
364
+ === Plantillas Erb
365
+
366
+ Dependencias:: {erubis}[http://www.kuwata-lab.com/erubis/] o
367
+ erb (incluida en Ruby)
368
+ Extensiones de Archivo:: <tt>.erb</tt>, <tt>.rhtml</tt> o <tt>.erubis</tt>
369
+ (solamente con Erubis)
370
+ Ejemplo:: <tt>erb :index</tt>
371
+
372
+ === Plantillas Builder
373
+
374
+ Dependencias:: {builder}[http://builder.rubyforge.org/]
375
+ Extensiones de Archivo:: <tt>.builder</tt>
376
+ Ejemplo:: <tt>builder { |xml| xml.em "hola" }</tt>
377
+
378
+ Además, acepta un bloque con la definición de la plantilla (ver el ejemplo).
379
+
380
+ === Plantillas Nokogiri
381
+
382
+ Dependencias:: {nokogiri}[http://nokogiri.org/]
383
+ Extensiones de Archivo:: <tt>.nokogiri</tt>
384
+ Ejemplo:: <tt>nokogiri { |xml| xml.em "hola" }</tt>
385
+
386
+ Además, acepta un bloque con la definición de la plantilla (ver el ejemplo).
387
+
388
+ === Plantillas Sass
389
+
390
+ Dependencias:: {sass}[http://sass-lang.com/]
391
+ Extensiones de Archivo:: <tt>.sass</tt>
392
+ Ejemplo:: <tt>sass :stylesheet, :style => :expanded</tt>
393
+
394
+ === Plantillas SCSS
395
+
396
+ Dependencias:: {scss}[http://sass-lang.com/]
397
+ Extensiones de Archivo:: <tt>.scss</tt>
398
+ Ejemplo:: <tt>scss :stylesheet, :style => :expanded</tt>
399
+
400
+ === Plantillas Less
401
+
402
+ Dependencias:: {less}[http://www.lesscss.org/]
403
+ Extensiones de Archivo:: <tt>.less</tt>
404
+ Ejemplo:: <tt>less :stylesheet</tt>
405
+
406
+ === Plantillas Liquid
407
+
408
+ Dependencias:: {liquid}[http://www.liquidmarkup.org/]
409
+ Extensiones de Archivo:: <tt>.liquid</tt>
410
+ Ejemplo:: <tt>liquid :index, :locals => { :clave => 'valor' }</tt>
411
+
412
+ Como no vas a poder llamar a métodos de Ruby (excepto por +yield+) desde una
413
+ plantilla Liquid, casi siempre vas a querer pasarle locales.
414
+
415
+ === Plantillas Markdown
416
+
417
+ Dependencias:: {rdiscount}[https://github.com/rtomayko/rdiscount],
418
+ {redcarpet}[https://github.com/tanoku/redcarpet],
419
+ {bluecloth}[http://deveiate.org/projects/BlueCloth],
420
+ {kramdown}[http://kramdown.rubyforge.org/] *o*
421
+ {maruku}[http://maruku.rubyforge.org/]
422
+ Extensiones de Archivo:: <tt>.markdown</tt>, <tt>.mkd</tt> y <tt>.md</tt>
423
+ Ejemplo:: <tt>markdown :index, :layout_engine => :erb</tt>
424
+
425
+ No es posible llamar métodos desde markdown, ni pasarle locales. Por lo tanto,
426
+ generalmente vas a usarlo en combinación con otro motor de renderizado:
427
+
428
+ erb :resumen, :locals => { :texto => markdown(:introduccion) }
429
+
430
+ Tené en cuenta que también podés llamar al método +markdown+ desde otras
431
+ plantillas:
432
+
433
+ %h1 Hola Desde Haml!
434
+ %p= markdown(:saludos)
435
+
436
+ Como no podés utilizar Ruby desde Markdown, no podés usar layouts escritos en
437
+ Markdown. De todos modos, es posible usar un motor de renderizado para el
438
+ layout distinto al de la plantilla pasando la opción <tt>:layout_engine</tt>.
439
+
440
+ === Plantillas Textile
441
+
442
+ Dependencias:: {RedCloth}[http://redcloth.org/]
443
+ Extensiones de Archivo:: <tt>.textile</tt>
444
+ Ejemplo:: <tt>textile :index, :layout_engine => :erb</tt>
445
+
446
+ No es posible llamar métodos desde textile, ni pasarle locales. Por lo tanto,
447
+ generalmente vas a usarlo en combinación con otro motor de renderizado:
448
+
449
+ erb :resumen, :locals => { :texto => textile(:introduccion) }
450
+
451
+ Tené en cuenta que también podés llamar al método +textile+ desde otras
452
+ plantillas:
453
+
454
+ %h1 Hola Desde Haml!
455
+ %p= textile(:saludos)
456
+
457
+ Como no podés utilizar Ruby desde Textile, no podés usar layouts escritos en
458
+ Textile. De todos modos, es posible usar un motor de renderizado para el
459
+ layout distinto al de la plantilla pasando la opción <tt>:layout_engine</tt>.
460
+
461
+ === Plantillas RDoc
462
+
463
+ Dependencias:: {rdoc}[http://rdoc.rubyforge.org/]
464
+ Extensiones de Archivo:: <tt>.rdoc</tt>
465
+ Ejemplo:: <tt>rdoc :LEEME, :layout_engine => :erb</tt>
466
+
467
+ No es posible llamar métodos desde rdoc, ni pasarle locales. Por lo tanto,
468
+ generalmente vas a usarlo en combinación con otro motor de renderizado:
469
+
470
+ erb :resumen, :locals => { :texto => rdoc(:introduccion) }
471
+
472
+ Tené en cuenta que también podés llamar al método +rdoc+ desde otras
473
+ plantillas:
474
+
475
+ %h1 Hola Desde Haml!
476
+ %p= rdoc(:saludos)
477
+
478
+ Como no podés utilizar Ruby desde RDoc, no podés usar layouts escritos en RDoc.
479
+ De todos modos, es posible usar un motor de renderizado para el layout distinto
480
+ al de la plantilla pasando la opción <tt>:layout_engine</tt>.
481
+
482
+ === Plantillas Radius
483
+
484
+ Dependencias:: {radius}[http://radius.rubyforge.org/]
485
+ Extensiones de Archivo:: <tt>.radius</tt>
486
+ Ejemplo:: <tt>radius :index, :locals => { :clave => 'valor' }</tt>
487
+
488
+ Como no vas a poder llamar a métodos de Ruby (excepto por +yield+) desde una
489
+ plantilla Radius, casi siempre vas a querer pasarle locales.
490
+
491
+ === Plantillas Markaby
492
+
493
+ Dependencias:: {markaby}[http://markaby.github.com/]
494
+ Extensiones de Archivo:: <tt>.mab</tt>
495
+ Ejemplos:: <tt>markaby { h1 "Bienvenido!" }</tt>
496
+
497
+ Además, acepta un bloque con la definición de la plantilla (ver el ejemplo).
498
+
499
+ === Plantillas Slim
500
+
501
+ Dependencias:: {slim}[http://slim-lang.com/]
502
+ Extensiones de Archivo:: <tt>.slim</tt>
503
+ Ejemplo:: <tt>slim :index</tt>
504
+
505
+ === Plantillas Creole
506
+
507
+ Dependencias:: {creole}[https://github.com/minad/creole]
508
+ Extensiones de Archivo:: <tt>.creole</tt>
509
+ Ejemplo:: <tt>creole :wiki, :layout_engine => :erb</tt>
510
+
511
+ No es posible llamar métodos desde creole, ni pasarle locales. Por lo tanto,
512
+ generalmente vas a usarlo en combinación con otro motor de renderizado:
513
+
514
+ erb :resumen, :locals => { :texto => cerole(:introduccion) }
515
+
516
+ Tené en cuenta que también podés llamar al método +creole+ desde otras
517
+ plantillas:
518
+
519
+ %h1 Hola Desde Haml!
520
+ %p= creole(:saludos)
521
+
522
+ Como no podés utilizar Ruby desde Creole, no podés usar layouts escritos en
523
+ Creloe. De todos modos, es posible usar un motor de renderizado para el layout
524
+ distinto al de la plantilla pasando la opción <tt>:layout_engine</tt>.
525
+
526
+ === Plantillas CoffeeScript
527
+
528
+ Dependencias:: {coffee-script}[https://github.com/josh/ruby-coffee-script]
529
+ y un {mecanismo para ejecutar javascript}[https://github.com/sstephenson/execjs/blob/master/README.md#readme]
530
+ Extensiones de Archivo:: <tt>.coffee</tt>
531
+ Ejemplo:: <tt>coffee :index</tt>
532
+
533
+ === Plantillas Yajl
534
+
535
+ Dependencias:: {yajl-ruby}[https://github.com/brianmario/yajl-ruby]
536
+ Extensiones de Archivo:: <tt>.yajl</tt>
537
+ Ejemplo:: <tt>yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'</tt>
538
+
539
+ El contenido de La plantilla se evalúa como código Ruby, y la variable +json+ es convertida a JSON mediante <tt>#to_json</tt>.
540
+
541
+ json = { :foo => 'bar' }
542
+ json[:baz] = key
543
+
544
+ Las opciones <tt>:callback</tt> y <tt>:variable</tt> se pueden utilizar para decorar el objeto renderizado:
545
+
546
+ var resource = {"foo":"bar","baz":"qux"}; present(resource);
547
+
548
+ === Plantillas Embebidas
549
+
550
+ get '/' do
551
+ haml '%div.titulo Hola Mundo'
552
+ end
553
+
554
+ Renderiza el template embebido en el string.
555
+
556
+ === Accediendo a Variables en Plantillas
557
+
558
+ Las plantillas son evaluadas dentro del mismo contexto que los manejadores de
559
+ ruta. Las variables de instancia asignadas en los manejadores de ruta son
560
+ accesibles directamente por las plantillas:
561
+
562
+ get '/:id' do
563
+ @foo = Foo.find(params[:id])
564
+ haml '%h1= @foo.nombre'
565
+ end
566
+
567
+ O es posible especificar un Hash de variables locales explícitamente:
568
+
569
+ get '/:id' do
570
+ foo = Foo.find(params[:id])
571
+ haml '%h1= bar.nombre', :locals => { :bar => foo }
572
+ end
573
+
574
+ Esto es usado típicamente cuando se renderizan plantillas como parciales desde
575
+ adentro de otras plantillas.
576
+
577
+ === Plantillas Inline
578
+
579
+ Las plantillas pueden ser definidas al final del archivo fuente:
580
+
581
+ require 'rubygems'
582
+ require 'sinatra'
583
+
584
+ get '/' do
585
+ haml :index
586
+ end
587
+
588
+ __END__
589
+
590
+ @@ layout
591
+ %html
592
+ = yield
593
+
594
+ @@ index
595
+ %div.titulo Hola mundo!!!!!
596
+
597
+ NOTA: únicamente las plantillas inline definidas en el archivo fuente que
598
+ requiere sinatra son cargadas automáticamente. Llamá <tt>enable
599
+ :inline_templates</tt> explícitamente si tenés plantillas inline en otros
600
+ archivos fuente.
601
+
602
+ === Plantillas Nombradas
603
+
604
+ Las plantillas también pueden ser definidas usando el método top-level
605
+ <tt>template</tt>:
606
+
607
+ template :layout do
608
+ "%html\n =yield\n"
609
+ end
610
+
611
+ template :index do
612
+ '%div.titulo Hola Mundo!'
613
+ end
614
+
615
+ get '/' do
616
+ haml :index
617
+ end
618
+
619
+ Si existe una plantilla con el nombre "layout", va a ser usada cada vez que
620
+ una plantilla es renderizada. Podés desactivar los layouts individualmente
621
+ pasando <tt>:layout => false</tt> o globalmente con
622
+ <tt>set :haml, :layout => false</tt>:
623
+
624
+ get '/' do
625
+ haml :index, :layout => !request.xhr?
626
+ end
627
+
628
+ === Asociando Extensiones de Archivo
629
+
630
+ Para asociar una extensión de archivo con un motor de renderizado, usá
631
+ <tt>Tilt.register</tt>. Por ejemplo, si querés usar la extensión +tt+ para
632
+ las plantillas Textile, podés hacer lo siguiente:
633
+
634
+ Tilt.register :tt, Tilt[:textile]
635
+
636
+ === Agregando Tu Propio Motor de Renderizado
637
+
638
+ Primero, registrá tu motor con Tilt, y después, creá tu método de renderizado:
639
+
640
+ Tilt.register :mipg, MiMotorParaPlantillaGenial
641
+
642
+ helpers do
643
+ def mypg(*args) render(:mypg, *args) end
644
+ end
645
+
646
+ get '/' do
647
+ mypg :index
648
+ end
649
+
650
+ Renderiza <tt>./views/index.mypg</tt>. Mirá https://github.com/rtomayko/tilt
651
+ para aprender más de Tilt.
652
+
653
+ == Filtros
654
+
655
+ Los filtros +before+ son evaluados antes de cada petición dentro del mismo
656
+ contexto que las rutas. Pueden modificar la petición y la respuesta. Las
657
+ variables de instancia asignadas en los filtros son accesibles por las rutas y
658
+ las plantillas:
659
+
660
+ before do
661
+ @nota = 'Hey!'
662
+ request.path_info = '/foo/bar/baz'
663
+ end
664
+
665
+ get '/foo/*' do
666
+ @nota #=> 'Hey!'
667
+ params[:splat] #=> 'bar/baz'
668
+ end
669
+
670
+ Los filtros +after+ son evaluados después de cada petición dentro del mismo
671
+ contexto y también pueden modificar la petición y la respuesta. Las variables
672
+ de instancia asignadas en los filtros +before+ y en las rutas son accesibles por
673
+ los filtros +after+:
674
+
675
+ after do
676
+ puts response.status
677
+ end
678
+
679
+ Nota: A menos que usés el método +body+ en lugar de simplemente devolver un
680
+ string desde una ruta, el cuerpo de la respuesta no va a estar disponible en
681
+ un filtro after, debido a que todavía no se ha generado.
682
+
683
+ Los filtros aceptan un patrón opcional, que cuando está presente causa que los
684
+ mismos sean evaluados únicamente si el path de la petición coincide con ese
685
+ patrón:
686
+
687
+ before '/protegido/*' do
688
+ autenticar!
689
+ end
690
+
691
+ after '/crear/:slug' do |slug|
692
+ session[:ultimo_slug] = slug
693
+ end
694
+
695
+ Al igual que las rutas, los filtros también pueden aceptar condiciones:
696
+
697
+ before :agent => /Songbird/ do
698
+ # ...
699
+ end
700
+
701
+ after '/blog/*', :host_name => 'ejemplo.com' do
702
+ # ...
703
+ end
704
+
705
+ == Ayudantes
706
+
707
+ Usá el método top-level <tt>helpers</tt> para definir métodos ayudantes que
708
+ pueden ser utilizados dentro de los manejadores de rutas y las plantillas:
709
+
710
+ helpers do
711
+ def bar(nombre)
712
+ "#{nombre}bar"
713
+ end
714
+ end
715
+
716
+ get '/:nombre' do
717
+ bar(params[:nombre])
718
+ end
719
+
720
+ Por cuestiones organizativas, puede resultar conveniente organizar los métodos
721
+ ayudantes en distintos módulos:
722
+
723
+ module FooUtils
724
+ def foo(nombre) "#{nombre}foo" end
725
+ end
726
+
727
+ module BarUtils
728
+ def bar(nombre) "#{nombre}bar" end
729
+ end
730
+
731
+ helpers FooUtils, BarUtils
732
+
733
+ El efecto de utilizar <tt>helpers</tt> de esta manera es el mismo que resulta de
734
+ incluir los módulos en la clase de la aplicación.
735
+
736
+ === Usando Sesiones
737
+
738
+ Una sesión es usada para mantener el estado a través de distintas peticiones.
739
+ Cuando están activadas, tenés un hash de sesión para cada sesión de usuario:
740
+
741
+ enable :sessions
742
+
743
+ get '/' do
744
+ "valor = " << session[:valor].inspect
745
+ end
746
+
747
+ get '/:valor' do
748
+ session[:valor] = params[:valor]
749
+ end
750
+
751
+ Tené en cuenta que <tt>enable :sessions</tt> guarda todos los datos en una
752
+ cookie, lo que no es siempre deseable (guardar muchos datos va a incrementar
753
+ tu tráfico, por citar un ejemplo). Podés usar cualquier middleware Rack para
754
+ manejar sesiones, de la misma manera que usarías cualquier otro middleware,
755
+ pero con la salvedad de que *no* tenés que llamar a <tt>enable :sessions</tt>:
756
+
757
+ use Rack::Session::Pool, :expire_after => 2592000
758
+
759
+ get '/' do
760
+ "valor = " << session[:valor].inspect
761
+ end
762
+
763
+ get '/:valor' do
764
+ session[:valor] = params[:valor]
765
+ end
766
+
767
+ Para incrementar la seguridad, los datos de la sesión almacenados en
768
+ la cookie son firmados con un secreto de sesión. Este secreto, es
769
+ generado aleatoriamente por Sinatra. De cualquier manera, hay que
770
+ tener en cuenta que cada vez que inicies la aplicación se va a generar
771
+ uno nuevo. Así, si querés que todas las instancias de tu aplicación
772
+ compartan un único secreto, tenés que definirlo vos:
773
+
774
+ set :session_secret, 'super secreto'
775
+
776
+ Si necesitás una configuración más específica, +sessions+ acepta un
777
+ Hash con opciones:
778
+
779
+ set :sessions, :domain => 'foo.com'
780
+
781
+ === Interrupción
782
+
783
+ Para detener inmediatamente una petición dentro de un filtro o una ruta usá:
784
+
785
+ halt
786
+
787
+ También podés especificar el estado:
788
+
789
+ halt 410
790
+
791
+ O el cuerpo:
792
+
793
+ halt 'esto va a ser el cuerpo'
794
+
795
+ O los dos:
796
+
797
+ halt 401, 'salí de acá!'
798
+
799
+ Con cabeceras:
800
+
801
+ halt 402, { 'Content-Type' => 'text/plain' }, 'venganza'
802
+
803
+ Obviamente, es posible utilizar +halt+ con una plantilla:
804
+
805
+ halt erb(:error)
806
+
807
+ === Paso
808
+
809
+ Una ruta puede pasarle el procesamiento a la siguiente ruta que coincida con
810
+ la petición usando <tt>pass</tt>:
811
+
812
+ get '/adivina/:quien' do
813
+ pass unless params[:quien] == 'Franco'
814
+ 'Adivinaste!'
815
+ end
816
+
817
+ get '/adivina/*' do
818
+ 'Erraste!'
819
+ end
820
+
821
+ Se sale inmediatamente del bloque de la ruta y se le pasa el control a la
822
+ siguiente ruta que coincida. Si no coincide ninguna ruta, se devuelve un 404.
823
+
824
+ === Ejecutando Otra Ruta
825
+
826
+ Cuando querés obtener el resultado de la llamada a una ruta, +pass+ no te va a
827
+ servir. Para lograr esto, podés usar +call+:
828
+
829
+ get '/foo' do
830
+ status, headers, body = call env.merge("PATH_INFO" => '/bar')
831
+ [status, headers, body.map(&:upcase)]
832
+ end
833
+
834
+ get '/bar' do
835
+ "bar"
836
+ end
837
+
838
+ Notá que en el ejemplo anterior, es conveniente mover <tt>"bar"</tt> a un
839
+ helper, y llamarlo desde <tt>/foo</tt> y <tt>/bar</tt>. Así, vas a simplificar
840
+ las pruebas y a mejorar el rendimiento.
841
+
842
+ Si querés que la petición se envíe a la misma instancia de la aplicación en
843
+ lugar de a otra, usá <tt>call!</tt> en lugar de <tt>call</tt>.
844
+
845
+ En la especificación de Rack podés encontrar más información sobre
846
+ <tt>call</tt>.
847
+
848
+ === Asignando el Código de Estado, los Encabezados y el Cuerpo de una Respuesta
849
+
850
+ Es posible, y se recomienda, asignar el código de estado y el cuerpo de una
851
+ respuesta con el valor de retorno de una ruta. De cualquier manera, en varios
852
+ escenarios, puede que sea conveniente asignar el cuerpo en un punto arbitrario
853
+ del flujo de ejecución con el método +body+. A partir de ahí, podés usar ese
854
+ mismo método para acceder al cuerpo de la respuesta:
855
+
856
+ get '/foo' do
857
+ body "bar"
858
+ end
859
+
860
+ after do
861
+ puts body
862
+ end
863
+
864
+ También es posible pasarle un bloque a +body+, que será ejecutado por el Rack
865
+ handler (podés usar esto para implementar streaming, mirá "Valores de retorno").
866
+
867
+ De manera similar, también podés asignar el código de estado y encabezados:
868
+
869
+ get '/foo' do
870
+ status 418
871
+ headers \
872
+ "Allow" => "BREW, POST, GET, PROPFIND, WHEN",
873
+ "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
874
+ body "I'm a tea pot!"
875
+ end
876
+
877
+ También, al igual que +body+, tanto +status+ como +headers+ pueden utilizarse
878
+ para obtener sus valores cuando no se les pasa argumentos.
879
+
880
+ === Streaming De Respuestas
881
+
882
+ A veces vas a querer empezar a enviar la respuesta a pesar de que todavía no
883
+ terminaste de generar su cuerpo. También es posible que, en algunos casos,
884
+ quieras seguir enviando información hasta que el cliente cierre la conexión.
885
+ Cuando esto ocurra, el +stream+ helper te va a ser de gran ayuda:
886
+
887
+ get '/' do
888
+ stream do |out|
889
+ out << "Esto va a ser legen -\n"
890
+ sleep 0.5
891
+ out << " (esperalo) \n"
892
+ sleep 1
893
+ out << "- dario!\n"
894
+ end
895
+ end
896
+
897
+ Podés implementar APIs de streaming,
898
+ {Server-Sent Events}[http://dev.w3.org/html5/eventsource/] y puede ser usado
899
+ como base para {WebSockets}[http://es.wikipedia.org/wiki/WebSockets]. También
900
+ puede ser usado para incrementar el throughput si solo una parte del contenido
901
+ depende de un recurso lento.
902
+
903
+ Hay que tener en cuenta que el comportamiento del streaming, especialmente el
904
+ número de peticiones concurrentes, depende del servidor web utilizado para
905
+ servir la aplicación. Puede que algunos servidores, como es el caso de
906
+ WEBRick, no soporten streaming directamente, así el cuerpo de la respuesta será
907
+ enviado completamente de una vez cuando el bloque pasado a +stream+ finalice su
908
+ ejecución. Si estás usando Shotgun, el streaming no va a funcionar.
909
+
910
+ Cuando se pasa +keep_open+ como parámetro, no se va a enviar el mensaje
911
+ +close+ al objeto de stream. Queda en vos cerrarlo en el punto de ejecución
912
+ que quieras. Nuevamente, hay que tener en cuenta que este comportamiento es
913
+ posible solo en servidores que soporten eventos, como Thin o Rainbows. El
914
+ resto de los servidores van a cerrar el stream de todos modos:
915
+
916
+ set :server, :thin
917
+ conexiones = []
918
+
919
+ get '/' do
920
+ # mantenemos abierto el stream
921
+ stream(:keep_open) { |salida| conexiones << salida }
922
+ end
923
+
924
+ post '/' do
925
+ # escribimos a todos los streams abiertos
926
+ conexiones.each { |salida| salida << params[:mensaje] << "\n" }
927
+ "mensaje enviado"
928
+ end
929
+
930
+ === Log (Registro)
931
+
932
+ En el ámbito de la petición, el helper +logger+ (registrador) expone
933
+ una instancia de +Logger+:
934
+
935
+ get '/' do
936
+ logger.info "cargando datos"
937
+ # ...
938
+ end
939
+
940
+ Este logger tiene en cuenta la configuración de logueo de tu Rack
941
+ handler. Si el logueo está desactivado, este método va a devolver un
942
+ objeto que se comporta como un logger pero que en realidad no hace
943
+ nada. Así, no vas a tener que preocuparte por esta situación.
944
+
945
+ Tené en cuenta que el logueo está habilitado por defecto únicamente
946
+ para <tt>Sinatra::Application</tt>. Si heredaste de
947
+ <tt>Sinatra::Base</tt>, probablemente quieras habilitarlo manualmente:
948
+
949
+ class MiApp < Sinatra::Base
950
+ configure :production, :development do
951
+ enable :logging
952
+ end
953
+ end
954
+
955
+ Para evitar que se inicialice cualquier middleware de logging, configurá
956
+ +logging+ a +nil+. Tené en cuenta que, cuando hagas esto, +logger+ va a
957
+ devolver +nil+. Un caso común es cuando querés usar tu propio logger. Sinatra
958
+ va a usar lo que encuentre en <tt>env['rack.logger']</tt>.
959
+
960
+ === Tipos Mime
961
+
962
+ Cuando usás <tt>send_file</tt> o archivos estáticos tal vez tengas tipos mime
963
+ que Sinatra no entiende. Usá +mime_type+ para registrarlos a través de la
964
+ extensión de archivo:
965
+
966
+ configure do
967
+ mime_type :foo, 'text/foo'
968
+ end
969
+
970
+ También lo podés usar con el ayudante +content_type+:
971
+
972
+ get '/' do
973
+ content_type :foo
974
+ "foo foo foo"
975
+ end
976
+
977
+ === Generando URLs
978
+
979
+ Para generar URLs deberías usar el método +url+. Por ejemplo, en Haml:
980
+
981
+ %a{:href => url('/foo')} foo
982
+
983
+ Tiene en cuenta proxies inversos y encaminadores de Rack, si están presentes.
984
+
985
+ Este método también puede invocarse mediante su alias +to+ (mirá un ejemplo
986
+ a continuación).
987
+
988
+ === Redirección del Navegador
989
+
990
+ Podés redireccionar al navegador con el método +redirect+:
991
+
992
+ get '/foo' do
993
+ redirect to('/bar')
994
+ end
995
+
996
+ Cualquier parámetro adicional se utiliza de la misma manera que los argumentos
997
+ pasados a +halt+:
998
+
999
+ redirect to('/bar'), 303
1000
+ redirect 'http://google.com', 'te confundiste de lugar, compañero'
1001
+
1002
+ También podés redireccionar fácilmente de vuelta hacia la página desde donde
1003
+ vino el usuario con +redirect back+:
1004
+
1005
+ get '/foo' do
1006
+ "<a href='/bar'>hacer algo</a>"
1007
+ end
1008
+
1009
+ get '/bar' do
1010
+ hacer_algo
1011
+ redirect back
1012
+ end
1013
+
1014
+ Para pasar argumentos con una redirección, podés agregarlos a la cadena de
1015
+ búsqueda:
1016
+
1017
+ redirect to('/bar?suma=42')
1018
+
1019
+ O usar una sesión:
1020
+
1021
+ enable :sessions
1022
+
1023
+ get '/foo' do
1024
+ session[:secreto] = 'foo'
1025
+ redirect to('/bar')
1026
+ end
1027
+
1028
+ get '/bar' do
1029
+ session[:secreto]
1030
+ end
1031
+
1032
+ === Cache Control
1033
+
1034
+ Asignar tus encabezados correctamente es el cimiento para realizar un cacheo
1035
+ HTTP correcto.
1036
+
1037
+ Podés asignar el encabezado Cache-Control fácilmente:
1038
+
1039
+ get '/' do
1040
+ cache_control :public
1041
+ "cachealo!"
1042
+ end
1043
+
1044
+ Pro tip: configurar el cacheo en un filtro +before+:
1045
+
1046
+ before do
1047
+ cache_control :public, :must_revalidate, :max_age => 60
1048
+ end
1049
+
1050
+ Si estás usando el helper +expires+ para definir el encabezado correspondiente,
1051
+ <tt>Cache-Control</tt> se va a definir automáticamente:
1052
+
1053
+ before do
1054
+ expires 500, :public, :must_revalidate
1055
+ end
1056
+
1057
+ Para usar cachés adecuadamente, deberías considerar usar +etag+ o
1058
+ +last_modified+. Es recomendable que llames a estos helpers *antes* de hacer
1059
+ cualquier trabajo pesado, ya que van a enviar la respuesta inmediatamente si
1060
+ el cliente ya tiene la versión actual en su caché:
1061
+
1062
+ get '/articulo/:id' do
1063
+ @articulo = Articulo.find params[:id]
1064
+ last_modified @articulo.updated_at
1065
+ etag @articulo.sha1
1066
+ erb :articulo
1067
+ end
1068
+
1069
+ También es posible usar una
1070
+ {weak ETag}[http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation]:
1071
+
1072
+ etag @articulo.sha1, :weak
1073
+
1074
+ Estos helpers no van a cachear nada por vos, sino que van a facilitar la
1075
+ información necesaria para poder hacerlo. Si estás buscando soluciones rápidas
1076
+ de cacheo con proxys inversos, mirá
1077
+ {rack-cache}[http://rtomayko.github.com/rack-cache/]:
1078
+
1079
+ require "rack/cache"
1080
+ require "sinatra"
1081
+
1082
+ use Rack::Cache
1083
+
1084
+ get '/' do
1085
+ cache_control :public, :max_age => 36000
1086
+ sleep 5
1087
+ "hola"
1088
+ end
1089
+
1090
+ Usá la configuración <tt>:static_cache_control</tt> para agregar el encabezado
1091
+ <tt>Cache-Control</tt> a archivos estáticos (ver la sección de configuración
1092
+ para más detalles).
1093
+
1094
+ De acuerdo con la RFC 2616 tu aplicación debería comportarse diferente si a las
1095
+ cabeceras If-Match o If-None-Match se le asigna el valor <tt>*</tt> cuando el
1096
+ recurso solicitado ya existe. Sinatra asume para peticiones seguras (como get)
1097
+ e idempotentes (como put) que el recurso existe, mientras que para el resto
1098
+ (como post), que no. Podes cambiar este comportamiento con la opción
1099
+ <tt>:new_resource</tt>:
1100
+
1101
+ get '/crear' do
1102
+ etag '', :new_resource => true
1103
+ Articulo.create
1104
+ erb :nuevo_articulo
1105
+ end
1106
+
1107
+ Si querés seguir usando una weak ETag, indicalo con la opción <tt>:kind</tt>:
1108
+
1109
+ etag '', :new_resource => true, :kind => :weak
1110
+
1111
+ === Enviando Archivos
1112
+
1113
+ Para enviar archivos, podés usar el método <tt>send_file</tt>:
1114
+
1115
+ get '/' do
1116
+ send_file 'foo.png'
1117
+ end
1118
+
1119
+ Además acepta un par de opciones:
1120
+
1121
+ send_file 'foo.png', :type => :jpg
1122
+
1123
+ Estas opciones son:
1124
+
1125
+ [filename]
1126
+ nombre del archivo devuelto, por defecto es el nombre real del archivo.
1127
+
1128
+ [last_modified]
1129
+ valor para el encabezado Last-Modified, por defecto toma el mtime del archivo.
1130
+
1131
+ [type]
1132
+ el content type que se va a utilizar, si no está presente se intenta adivinar
1133
+ a partir de la extensión del archivo.
1134
+
1135
+ [disposition]
1136
+ se utiliza para el encabezado Content-Disposition, y puede tomar alguno de los
1137
+ siguientes valores: +nil+ (por defecto), <tt>:attachment</tt> e
1138
+ <tt>:inline</tt>
1139
+
1140
+ [length]
1141
+ encabezado Content-Length, por defecto toma el tamaño del archivo.
1142
+
1143
+ [status]
1144
+ código de estado devuelto. Resulta útil al enviar un archivo estático como una
1145
+ página de error.
1146
+
1147
+ Si el Rack handler lo soporta, se intentará no transmitir directamente desde el
1148
+ proceso de Ruby. Si usás este método, Sinatra se va a encargar automáticamente
1149
+ peticiones de rango.
1150
+
1151
+ === Accediendo al objeto de la petición
1152
+
1153
+ El objeto de la petición entrante puede ser accedido desde el nivel de la
1154
+ petición (filtros, rutas y manejadores de errores) a través del método
1155
+ <tt>request</tt>:
1156
+
1157
+ # app corriendo en http://ejemplo.com/ejemplo
1158
+ get '/foo' do
1159
+ t = %w[text/css text/html application/javascript]
1160
+ request.accept # ['text/html', '*/*']
1161
+ request.accept? 'text/xml' # true
1162
+ request.preferred_type(t) # 'text/html'
1163
+ request.body # cuerpo de la petición enviado por el cliente (ver más abajo)
1164
+ request.scheme # "http"
1165
+ request.script_name # "/ejemplo"
1166
+ request.path_info # "/foo"
1167
+ request.port # 80
1168
+ request.request_method # "GET"
1169
+ request.query_string # ""
1170
+ request.content_length # longitud de request.body
1171
+ request.media_type # tipo de medio de request.body
1172
+ request.host # "ejemplo.com"
1173
+ request.get? # true (hay métodos análogos para los otros verbos)
1174
+ request.form_data? # false
1175
+ request["UNA_CABECERA"] # valor de la cabecera UNA_CABECERA
1176
+ request.referrer # la referencia del cliente o '/'
1177
+ request.user_agent # user agent (usado por la condición :agent)
1178
+ request.cookies # hash de las cookies del browser
1179
+ request.xhr? # es una petición ajax?
1180
+ request.url # "http://ejemplo.com/ejemplo/foo"
1181
+ request.path # "/ejemplo/foo"
1182
+ request.ip # dirección IP del cliente
1183
+ request.secure? # false (sería true sobre ssl)
1184
+ request.forwarded? # true (si se está corriendo atrás de un proxy inverso)
1185
+ requuest.env # hash de entorno directamente entregado por Rack
1186
+ end
1187
+
1188
+ Algunas opciones, como <tt>script_name</tt> o <tt>path_info</tt> pueden
1189
+ también ser escritas:
1190
+
1191
+ before { request.path_info = "/" }
1192
+
1193
+ get "/" do
1194
+ "todas las peticiones llegan acá"
1195
+ end
1196
+
1197
+ El objeto <tt>request.body</tt> es una instancia de IO o StringIO:
1198
+
1199
+ post "/api" do
1200
+ request.body.rewind # en caso de que alguien ya lo haya leído
1201
+ datos = JSON.parse request.body.read
1202
+ "Hola #{datos['nombre']}!"
1203
+ end
1204
+
1205
+ === Archivos Adjuntos
1206
+
1207
+ Podés usar el método helper +attachment+ para indicarle al navegador que
1208
+ almacene la respuesta en el disco en lugar de mostrarla en pantalla:
1209
+
1210
+ get '/' do
1211
+ attachment
1212
+ "guardalo!"
1213
+ end
1214
+
1215
+ También podés pasarle un nombre de archivo:
1216
+
1217
+ get '/' do
1218
+ attachment "info.txt"
1219
+ "guardalo!"
1220
+ end
1221
+
1222
+ === Fecha y Hora
1223
+
1224
+ Sinatra pone a tu disposición el helper +time_for+, que genera un objeto +Time+
1225
+ a partir del valor que recibe como argumento. Este valor puede ser un
1226
+ +String+, pero también es capaz de convertir objetos +DateTime+, +Date+ y de
1227
+ otras clases similares:
1228
+
1229
+ get '/' do
1230
+ pass if Time.now > time_for('Dec 23, 2012')
1231
+ "todavía hay tiempo"
1232
+ end
1233
+
1234
+ Este método es usado internamente por métodos como +expires+ y +last_modified+,
1235
+ entre otros. Por lo tanto, es posible extender el comportamiento de estos
1236
+ métodos sobreescribiendo +time_for+ en tu aplicación:
1237
+
1238
+ helpers do
1239
+ def time_for(value)
1240
+ case value
1241
+ when :ayer then Time.now - 24*60*60
1242
+ when :mañana then Time.now + 24*60*60
1243
+ else super
1244
+ end
1245
+ end
1246
+ end
1247
+
1248
+ get '/' do
1249
+ last_modified :ayer
1250
+ expires :mañana
1251
+ "hola"
1252
+ end
1253
+
1254
+ === Buscando los Archivos de las Plantillas
1255
+
1256
+ El helper <tt>find_template</tt> se utiliza para encontrar los archivos de las
1257
+ plantillas que se van a renderizar:
1258
+
1259
+ find_template settings.views, 'foo', Tilt[:haml] do |archivo|
1260
+ puts "podría ser #{archivo}"
1261
+ end
1262
+
1263
+ Si bien esto no es muy útil, lo interesante es que podés sobreescribir este
1264
+ método, y así enganchar tu propio mecanismo de búsqueda. Por ejemplo, para
1265
+ poder utilizar más de un directorio de vistas:
1266
+
1267
+ set :views, ['vistas', 'plantillas']
1268
+
1269
+ helpers do
1270
+ def find_template(views, name, engine, &block)
1271
+ Array(views).each { |v| super(v, name, engine, &block) }
1272
+ end
1273
+ end
1274
+
1275
+ Otro ejemplo consiste en usar directorios diferentes para los distintos motores
1276
+ de renderizado:
1277
+
1278
+ set :views, :sass => 'vistas/sass', :haml => 'plantillas', :defecto => 'vistas'
1279
+
1280
+ helpers do
1281
+ def find_template(views, name, engine, &block)
1282
+ _, folder = views.detect { |k,v| engine == Tilt[k] }
1283
+ folder ||= views[:defecto]
1284
+ super(folder, name, engine, &block)
1285
+ end
1286
+ end
1287
+
1288
+ ¡Es muy fácil convertir estos ejemplos en una extensión y compartirla!.
1289
+
1290
+ Notá que <tt>find_template</tt> no verifica si un archivo existe realmente, sino
1291
+ que llama al bloque que recibe para cada path posible. Esto no representa un
1292
+ problema de rendimiento debido a que +render+ va a usar +break+ ni bien
1293
+ encuentre un archivo que exista. Además, las ubicaciones de las plantillas (y
1294
+ su contenido) se cachean cuando no estás en el modo de desarrollo. Es bueno
1295
+ tener en cuenta lo anteiror si escribís un método medio loco.
1296
+
1297
+ == Configuración
1298
+
1299
+ Ejecutar una vez, en el inicio, en cualquier entorno:
1300
+
1301
+ configure do
1302
+ # asignando una opción
1303
+ set :opcion, 'valor'
1304
+
1305
+ # asignando varias opciones
1306
+ set :a => 1, :b => 2
1307
+
1308
+ # atajo para `set :opcion, true`
1309
+ enable :opcion
1310
+
1311
+ # atajo para `set :opcion, false`
1312
+ disable :opcion
1313
+
1314
+ # también podés tener configuraciones dinámicas usando bloques
1315
+ set(:css_dir) { File.join(views, 'css') }
1316
+ end
1317
+
1318
+ Ejecutar únicamente cuando el entorno (la variable de entorno RACK_ENV) es
1319
+ <tt>:production</tt>:
1320
+
1321
+ configure :production do
1322
+ ...
1323
+ end
1324
+
1325
+ Ejecutar cuando el entorno es <tt>:production</tt> o <tt>:test</tt>:
1326
+
1327
+ configure :production, :test do
1328
+ ...
1329
+ end
1330
+
1331
+ Podés acceder a estas opciones utilizando el método <tt>settings</tt>:
1332
+
1333
+ configure do
1334
+ set :foo, 'bar'
1335
+ end
1336
+
1337
+ get '/' do
1338
+ settings.foo? # => true
1339
+ settings.foo # => 'bar'
1340
+ ...
1341
+ end
1342
+
1343
+ === Configurando la Protección de Ataques
1344
+
1345
+ Sinatra usa {Rack::Protection}[https://github.com/rkh/rack-protection#readme]
1346
+ para defender a tu aplicación de los ataques más comunes. Tenés que tener en
1347
+ cuenta que como consecuencia de esto puede venir asociada una disminución del
1348
+ rendimiento de tu aplicación. Si por este, o algún otro motivo, querés
1349
+ desactivar está funcionalidad, podés hacerlo:
1350
+
1351
+ disable :protection
1352
+
1353
+ También es posible desactivar una única capa de defensa:
1354
+
1355
+ set :protection, :except => :path_traversal
1356
+
1357
+ O varias:
1358
+
1359
+ set :protection, :except => [:path_traversal, :session_hijacking]
1360
+
1361
+ === Configuraciones Disponibles
1362
+
1363
+ [absolute_redirects] si está deshabilitada, Sinatra va a permitir
1364
+ redirecciones relativas, sin embargo, como consecuencia
1365
+ de esto, va a dejar de cumplir con el RFC 2616 (HTTP
1366
+ 1.1), que solamente permite redirecciones absolutas.
1367
+
1368
+ Activalo si tu apliación está corriendo atrás de un proxy
1369
+ inverso que no se ha configurado adecuadamente. Notá que
1370
+ el helper +url+ va a seguir produciendo URLs absolutas, a
1371
+ menos que le pasés +false+ como segundo parámetro.
1372
+
1373
+ Deshabilitada por defecto.
1374
+
1375
+ [add_charsets] tipos mime a los que el helper <tt>content_type</tt> les
1376
+ añade automáticamente el charset.
1377
+
1378
+ En general, no deberías asignar directamente esta opción,
1379
+ sino añadirle los charsets que quieras:
1380
+
1381
+ settings.add_charsets << "application/foobar"
1382
+
1383
+ [app_file] path del archivo principal de la aplicación, se utiliza
1384
+ para detectar la raíz del proyecto, el directorio de las
1385
+ vistas y el público, así como las plantillas inline.
1386
+
1387
+ [bind] dirección IP que utilizará el servidor integrado (por
1388
+ defecto: 0.0.0.0).
1389
+
1390
+ [default_encoding] encoding utilizado cuando el mismo se desconoce (por
1391
+ defecto <tt>"utf-8"</tt>).
1392
+
1393
+ [dump_errors] mostrar errores en el log.
1394
+
1395
+ [environment] entorno actual, por defecto toma el valor de
1396
+ <tt>ENV['RACK_ENV']</tt>, o <tt>"development"</tt> si no
1397
+ está disponible.
1398
+
1399
+ [logging] define si se utiliza el logger.
1400
+
1401
+ [lock] coloca un lock alrededor de cada petición, procesando
1402
+ solamente una por proceso.
1403
+
1404
+ Habilitá esta opción si tu aplicación no es thread-safe.
1405
+ Se encuentra deshabilitada por defecto.
1406
+
1407
+ [method_override] utiliza el parámetro <tt>_method</tt> para permtir
1408
+ formularios put/delete en navegadores que no los
1409
+ soportan.
1410
+
1411
+ [port] puerto en el que escuchará el servidor integrado.
1412
+
1413
+ [prefixed_redirects] define si inserta <tt>request.script_name</tt> en las
1414
+ redirecciones cuando no se proporciona un path absoluto.
1415
+ De esta manera, cuando está habilitada,
1416
+ <tt>redirect '/foo'</tt> se comporta de la misma manera
1417
+ que <tt>redirect to('/foo')</tt>. Se encuentra
1418
+ deshabilitada por defecto.
1419
+
1420
+ [protection] define si deben activarse las protecciones para los
1421
+ ataques web más comunes. Para más detalles mirá la
1422
+ sección sobre la configuración de protección de ataques
1423
+ más arriba.
1424
+
1425
+ [public_folder] path del directorio desde donde se sirven los archivos
1426
+ públicos. Solo se utiliza cuando se sirven archivos
1427
+ estáticos (ver la opción <tt>static</tt>). Si no
1428
+ está presente, se infiere del valor de la opción
1429
+ <tt>app_file</tt>.
1430
+
1431
+ [reload_templates] define si se recargan las plantillas entre peticiones.
1432
+
1433
+ Se encuentra activado en el entorno de desarrollo.
1434
+
1435
+ [root] path del directorio raíz del proyecto. Si no está
1436
+ presente, se infiere del valor de la opción
1437
+ <tt>app_file</tt>.
1438
+
1439
+ [raise_errors] elevar excepciones (detiene la aplicación). Se
1440
+ encuentra activada por defecto cuando el valor de
1441
+ <tt>environment</tt> es <tt>"test"</tt>. En caso
1442
+ contrario estará desactivada.
1443
+
1444
+ [run] cuando está habilitada, Sinatra se va a encargar de
1445
+ iniciar el servidor web, no la habilités cuando estés
1446
+ usando rackup o algún otro medio.
1447
+
1448
+ [running] indica si el servidor integrado está ejecutandose, ¡no
1449
+ cambiés esta configuración!.
1450
+
1451
+ [server] servidor, o lista de servidores, para usar como servidor
1452
+ integrado. Por defecto: ['thin', 'mongrel', 'webrick'],
1453
+ el orden establece la prioridad.
1454
+
1455
+ [sessions] habilita el soporte de sesiones basadas en cookies a
1456
+ través de <tt>Rack::Session::Cookie</tt>. Ver la
1457
+ sección 'Usando Sesiones' para más información.
1458
+
1459
+ [show_exceptions] muestra un stack trace en el navegador cuando ocurre una
1460
+ excepción. Se encuentra activada por defecto cuando el
1461
+ valor de <tt>environment</tt> es <tt>"development"</tt>.
1462
+ En caso contrario estará desactivada.
1463
+
1464
+ [static] define si Sinatra debe encargarse de servir archivos
1465
+ estáticos.
1466
+
1467
+ Deshabilitala cuando usés un servidor capaz de
1468
+ hacerlo por sí solo, porque mejorará el
1469
+ rendimiento. Se encuentra habilitada por
1470
+ defecto en el estilo clásico y desactivado en el
1471
+ el modular.
1472
+
1473
+ [static_cache_control] cuando Sinatra está sirviendo archivos estáticos, y
1474
+ está opción está habilitada, les va a agregar encabezados
1475
+ <tt>Cache-Control</tt> a las respuestas. Para esto
1476
+ utiliza el helper +cache_control+. Se encuentra
1477
+ deshabilitada por defecto. Notar que es necesario
1478
+ utilizar un array cuando se asignan múltiples valores:
1479
+ <tt>set :static_cache_control, [:public, :max_age => 300]</tt>.
1480
+
1481
+ [views] path del directorio de las vistas. Si no está presente,
1482
+ se infiere del valor de la opción <tt>app_file</tt>.
1483
+
1484
+ == Entornos
1485
+
1486
+ Existen tres entornos (+environments+) predefinidos: <tt>development</tt>,
1487
+ <tt>production</tt> y <tt>test</tt>. El entorno por defecto es
1488
+ <tt>development</tt> y tiene algunas particularidades:
1489
+
1490
+ * Se recargan las plantillas entre una petición y la siguiente, a diferencia
1491
+ de <tt>production</tt> y <tt>test</tt>, donde se cachean.
1492
+ * Se instalan manejadores de errores <tt>not_found</tt> y <tt>error</tt>
1493
+ especiales que muestran un stack trace en el navegador cuando son disparados.
1494
+
1495
+ Para utilizar alguno de los otros entornos puede asignarse el valor
1496
+ correspondiente a la variable de entorno +RACK_ENV+, o bien utilizar la opción
1497
+ <tt>-e</tt> al ejecutar la aplicación:
1498
+
1499
+ ruby mi_app.rb -e <ENTORNO>
1500
+
1501
+ Los métodos +development?+, +test?+ y +production?+ te permiten conocer el
1502
+ entorno actual.
1503
+
1504
+ == Manejo de Errores
1505
+
1506
+ Los manejadores de errores se ejecutan dentro del mismo contexto que las rutas
1507
+ y los filtros +before+, lo que significa que podés usar, por ejemplo,
1508
+ <tt>haml</tt>, <tt>erb</tt>, <tt>halt</tt>, etc.
1509
+
1510
+ === No encontrado <em>(Not Found)</em>
1511
+
1512
+ Cuando se eleva una excepción <tt>Sinatra::NotFound</tt>, o el código de
1513
+ estado de la respuesta es 404, el manejador <tt>not_found</tt> es invocado:
1514
+
1515
+ not_found do
1516
+ 'No existo'
1517
+ end
1518
+
1519
+ === Error
1520
+
1521
+ El manejador +error+ es invocado cada vez que una excepción es elevada
1522
+ desde un bloque de ruta o un filtro. El objeto de la excepción se puede
1523
+ obtener de la variable Rack <tt>sinatra.error</tt>:
1524
+
1525
+ error do
1526
+ 'Disculpá, ocurrió un error horrible - ' + env['sinatra.error'].name
1527
+ end
1528
+
1529
+ Errores personalizados:
1530
+
1531
+ error MiErrorPersonalizado do
1532
+ 'Lo que pasó fue...' + env['sinatra.error'].message
1533
+ end
1534
+
1535
+ Entonces, si pasa esto:
1536
+
1537
+ get '/' do
1538
+ raise MiErrorPersonalizado, 'algo malo'
1539
+ end
1540
+
1541
+ Obtenés esto:
1542
+
1543
+ Lo que pasó fue... algo malo
1544
+
1545
+ También, podés instalar un manejador de errores para un código de estado:
1546
+
1547
+ error 403 do
1548
+ 'Acceso prohibido'
1549
+ end
1550
+
1551
+ get '/secreto' do
1552
+ 403
1553
+ end
1554
+
1555
+ O un rango:
1556
+
1557
+ error 400..510 do
1558
+ 'Boom'
1559
+ end
1560
+
1561
+ Sinatra instala manejadores <tt>not_found</tt> y <tt>error</ttt> especiales
1562
+ cuando se ejecuta dentro del entorno de desarrollo "development".
1563
+
1564
+ == Rack Middleware
1565
+
1566
+ Sinatra corre sobre Rack[http://rack.rubyforge.org/], una interfaz minimalista
1567
+ que es un estándar para frameworks webs escritos en Ruby. Una de las
1568
+ capacidades más interesantes de Rack para los desarrolladores de aplicaciones
1569
+ es el soporte de "middleware" -- componentes que se ubican entre el servidor y
1570
+ tu aplicación, supervisando y/o manipulando la petición/respuesta HTTP para
1571
+ proporcionar varios tipos de funcionalidades comunes.
1572
+
1573
+ Sinatra hace muy sencillo construir tuberías de Rack middleware a través del
1574
+ método top-level +use+:
1575
+
1576
+ require 'sinatra'
1577
+ require 'mi_middleware_personalizado'
1578
+
1579
+ use Rack::Lint
1580
+ use MiMiddlewarePersonalizado
1581
+
1582
+ get '/hola' do
1583
+ 'Hola Mundo'
1584
+ end
1585
+
1586
+ Las semánticas de +use+ son idénticas a las definidas para el DSL
1587
+ Rack::Builder[http://rack.rubyforge.org/doc/classes/Rack/Builder.html] (más
1588
+ frecuentemente usado desde archivos rackup). Por ejemplo, el método +use+
1589
+ acepta argumentos múltiples/variables así como bloques:
1590
+
1591
+ use Rack::Auth::Basic do |nombre_de_usuario, password|
1592
+ nombre_de_usuario == 'admin' && password == 'secreto'
1593
+ end
1594
+
1595
+ Rack es distribuido con una variedad de middleware estándar para logging,
1596
+ debugging, enrutamiento URL, autenticación, y manejo de sesiones. Sinatra
1597
+ usa muchos de estos componentes automáticamente de acuerdo a su configuración
1598
+ para que típicamente no tengas que usarlas (con +use+) explícitamente.
1599
+
1600
+ Podés encontrar middleware útil en
1601
+ {rack}[https://github.com/rack/rack/tree/master/lib/rack],
1602
+ {rack-contrib}[https://github.com/rack/rack-contrib#readme],
1603
+ con {CodeRack}[http://coderack.org/] o en la
1604
+ {Rack wiki}[https://github.com/rack/rack/wiki/List-of-Middleware].
1605
+
1606
+ == Pruebas
1607
+
1608
+ Las pruebas para las aplicaciones Sinatra pueden ser escritas utilizando
1609
+ cualquier framework o librería de pruebas basada en Rack. Se recomienda usar
1610
+ {Rack::Test}[http://rdoc.info/github/brynary/rack-test/master/frames]:
1611
+
1612
+ require 'mi_app_sinatra'
1613
+ require 'test/unit'
1614
+ require 'rack/test'
1615
+
1616
+ class MiAppTest < Test::Unit::TestCase
1617
+ include Rack::Test::Methods
1618
+
1619
+ def app
1620
+ Sinatra::Application
1621
+ end
1622
+
1623
+ def test_mi_defecto
1624
+ get '/'
1625
+ assert_equal 'Hola Mundo!', last_response.body
1626
+ end
1627
+
1628
+ def test_con_parametros
1629
+ get '/saludar', :name => 'Franco'
1630
+ assert_equal 'Hola Frank!', last_response.body
1631
+ end
1632
+
1633
+ def test_con_entorno_rack
1634
+ get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
1635
+ assert_equal "Estás usando Songbird!", last_response.body
1636
+ end
1637
+ end
1638
+
1639
+ == Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares
1640
+
1641
+ Definir tu aplicación en el top-level funciona bien para micro-aplicaciones
1642
+ pero trae inconvenientes considerables a la hora de construir componentes
1643
+ reutilizables como Rack middleware, Rails metal, simple librerías con un
1644
+ componente de servidor, o incluso extensiones de Sinatra. El DSL de top-level
1645
+ asume una configuración apropiada para micro-aplicaciones (por ejemplo, un
1646
+ único archivo de aplicación, los directorios <tt>./public</tt> y
1647
+ <tt>./views</tt>, logging, página con detalles de excepción, etc.). Ahí es
1648
+ donde <tt>Sinatra::Base</tt> entra en el juego:
1649
+
1650
+ require 'sinatra/base'
1651
+
1652
+ class MiApp < Sinatra::Base
1653
+ set :sessions, true
1654
+ set :foo, 'bar'
1655
+
1656
+ get '/' do
1657
+ 'Hola Mundo!'
1658
+ end
1659
+ end
1660
+
1661
+ Las subclases de <tt>Sinatra::Base</tt> tienen disponibles exactamente los
1662
+ mismos métodos que los provistos por el DSL de top-level. La mayoría de las
1663
+ aplicaciones top-level se pueden convertir en componentes
1664
+ <tt>Sinatra::Base</tt> con dos modificaciones:
1665
+
1666
+ * Tu archivo debe requerir <tt>sinatra/base</tt> en lugar de +sinatra+; de otra
1667
+ manera, todos los métodos del DSL de sinatra son importados dentro del
1668
+ espacio de nombres principal.
1669
+ * Poné las rutas, manejadores de errores, filtros y opciones de tu aplicación
1670
+ en una subclase de <tt>Sinatra::Base</tt>.
1671
+
1672
+ <tt>Sinatra::Base</tt> es una pizarra en blanco. La mayoría de las opciones están
1673
+ desactivadas por defecto, incluyendo el servidor incorporado. Mirá
1674
+ {Opciones y Configuraciones}[http://sinatra.github.com/configuration.html]
1675
+ para detalles sobre las opciones disponibles y su comportamiento.
1676
+
1677
+ === Estilo Modular vs. Clásico
1678
+
1679
+ Contrariamente a la creencia popular, no hay nada de malo con el estilo clásico.
1680
+ Si se ajusta a tu aplicación, no es necesario que la cambies a una modular.
1681
+
1682
+ Las desventaja de usar el estilo clásico en lugar del modular consiste en que
1683
+ solamente podés tener una aplicación Sinatra por proceso Ruby. Si tenés
1684
+ planificado usar más, cambiá al estilo modular. Al mismo tiempo, tené en
1685
+ cuenta que no hay ninguna razón por la cuál no puedas mezclar los estilos
1686
+ clásico y modular.
1687
+
1688
+ A continuación se detallan las diferencias (sutiles) entre las configuraciones
1689
+ de ambos estilos:
1690
+
1691
+ Configuración Clásica Modular
1692
+
1693
+ app_file archivo que carga sinatra archivo con la subclase de Sinatra::Base
1694
+ run $0 == app_file false
1695
+ logging true false
1696
+ method_override true false
1697
+ inline_templates true false
1698
+ static true false
1699
+
1700
+ === Sirviendo una Aplicación Modular
1701
+
1702
+ Las dos opciones más comunes para iniciar una aplicación modular son, iniciarla
1703
+ activamente con <tt>run!</tt>:
1704
+
1705
+ # mi_app.rb
1706
+ require 'sinatra/base'
1707
+
1708
+ class MiApp < Sinatra::Base
1709
+ # ... código de la app ...
1710
+
1711
+ # iniciar el servidor si el archivo fue ejecutado directamente
1712
+ run! if app_file == $0
1713
+ end
1714
+
1715
+ Iniciar con:
1716
+
1717
+ ruby mi_app.rb
1718
+
1719
+ O, con un archivo <tt>config.ru</tt>, que permite usar cualquier handler Rack:
1720
+
1721
+ # config.ru
1722
+ require './mi_app'
1723
+ run MiApp
1724
+
1725
+ Después ejecutar:
1726
+
1727
+ rackup -p 4567
1728
+
1729
+ === Usando una Aplicación Clásica con un Archivo config.ru
1730
+
1731
+ Escribí el archivo de tu aplicación:
1732
+
1733
+ # app.rb
1734
+ require 'sinatra'
1735
+
1736
+ get '/' do
1737
+ 'Hola mundo!'
1738
+ end
1739
+
1740
+ Y el <tt>config.ru</tt> correspondiente:
1741
+
1742
+ require './app'
1743
+ run Sinatra::Application
1744
+
1745
+ === ¿Cuándo Usar config.ru?
1746
+
1747
+ Indicadores de que probablemente querés usar <tt>config.ru</tt>:
1748
+
1749
+ * Querés realizar el deploy con un hanlder Rack distinto (Passenger, Unicorn,
1750
+ Heroku, ...).
1751
+ * Querés usar más de una subclase de <tt>Sinatra::Base</tt>.
1752
+ * Querés usar Sinatra únicamente para middleware, pero no como un endpoint.
1753
+
1754
+ <b>No hay necesidad de utilizar un archivo <tt>config.ru</tt> exclusivamente
1755
+ porque tenés una aplicación modular, y no necesitás una aplicación modular para
1756
+ iniciarla con <tt>config.ru</tt>.</b>
1757
+
1758
+ === Utilizando Sinatra como Middleware
1759
+
1760
+ Sinatra no solo es capaz de usar otro Rack middleware, sino que a su vez,
1761
+ cualquier aplicación Sinatra puede ser agregada delante de un endpoint Rack
1762
+ como middleware. Este endpoint puede ser otra aplicación Sinatra, o cualquier
1763
+ aplicación basada en Rack (Rails/Ramaze/Camping/...):
1764
+
1765
+ require 'sinatra/base'
1766
+
1767
+ class PantallaDeLogin < Sinatra::Base
1768
+ enable :sessions
1769
+
1770
+ get('/login') { haml :login }
1771
+
1772
+ post('/login') do
1773
+ if params[:nombre] == 'admin' && params[:password] == 'admin'
1774
+ session['nombre_de_usuario'] = params[:nombre]
1775
+ else
1776
+ redirect '/login'
1777
+ end
1778
+ end
1779
+ end
1780
+
1781
+ class MiApp < Sinatra::Base
1782
+ # el middleware se ejecutará antes que los filtros
1783
+ use PantallaDeLogin
1784
+
1785
+ before do
1786
+ unless session['nombre_de_usuario']
1787
+ halt "Acceso denegado, por favor <a href='/login'>iniciá sesión</a>."
1788
+ end
1789
+ end
1790
+
1791
+ get('/') { "Hola #{session['nombre_de_usuario']}." }
1792
+ end
1793
+
1794
+ === Creación Dinámica de Aplicaciones
1795
+
1796
+ Puede que en algunas ocasiones quieras crear nuevas aplicaciones en
1797
+ tiempo de ejecución sin tener que asignarlas a una constante. Para
1798
+ esto tenés <tt>Sinatra.new</tt>:
1799
+
1800
+ require 'sinatra/base'
1801
+ mi_app = Sinatra.new { get('/') { "hola" } }
1802
+ mi_app.run!
1803
+
1804
+ Acepta como argumento opcional una aplicación desde la que se
1805
+ heredará:
1806
+
1807
+ # config.ru
1808
+ require 'sinatra/base'
1809
+
1810
+ controller = Sinatra.new do
1811
+ enable :logging
1812
+ helpers MisHelpers
1813
+ end
1814
+
1815
+ map('/a') do
1816
+ run Sinatra.new(controller) { get('/') { 'a' } }
1817
+ end
1818
+
1819
+ map('/b') do
1820
+ run Sinatra.new(controller) { get('/') { 'b' } }
1821
+ end
1822
+
1823
+ Construir aplicaciones de esta forma resulta especialmente útil para
1824
+ testear extensiones Sinatra o para usar Sinatra en tus librerías.
1825
+
1826
+ Por otro lado, hace extremadamente sencillo usar Sinatra como
1827
+ middleware:
1828
+
1829
+ require 'sinatra/base'
1830
+
1831
+ use Sinatra do
1832
+ get('/') { ... }
1833
+ end
1834
+
1835
+ run ProyectoRails::Application
1836
+
1837
+ == Ámbitos y Ligaduras
1838
+
1839
+ El ámbito en el que te encontrás determina que métodos y variables están
1840
+ disponibles.
1841
+
1842
+ === Ámbito de Aplicación/Clase
1843
+
1844
+ Cada aplicación Sinatra es una subclase de <tt>Sinatra::Base</tt>. Si estás
1845
+ usando el DSL de top-level (<tt>require 'sinatra'</tt>), entonces esta clase es
1846
+ <tt>Sinatra::Application</tt>, de otra manera es la subclase que creaste
1847
+ explícitamente. Al nivel de la clase tenés métodos como +get+ o +before+, pero
1848
+ no podés acceder a los objetos +request+ o +session+, ya que hay una única
1849
+ clase de la aplicación para todas las peticiones.
1850
+
1851
+ Las opciones creadas utilizando +set+ son métodos al nivel de la clase:
1852
+
1853
+ class MiApp < Sinatra::Base
1854
+ # Ey, estoy en el ámbito de la aplicación!
1855
+ set :foo, 42
1856
+ foo # => 42
1857
+
1858
+ get '/foo' do
1859
+ # Hey, ya no estoy en el ámbito de la aplicación!
1860
+ end
1861
+ end
1862
+
1863
+ Tenés la ligadura al ámbito de la aplicación dentro de:
1864
+
1865
+ * El cuerpo de la clase de tu aplicación
1866
+ * Métodos definidos por extensiones
1867
+ * El bloque pasado a +helpers+
1868
+ * Procs/bloques usados como el valor para +set+
1869
+
1870
+ Este ámbito puede alcanzarse de las siguientes maneras:
1871
+
1872
+ * A través del objeto pasado a los bloques de configuración (<tt>configure { |c| ...}</tt>)
1873
+ * Llamando a +settings+ desde dentro del ámbito de la petición
1874
+
1875
+ === Ámbito de Petición/Instancia
1876
+
1877
+ Para cada petición entrante, una nueva instancia de la clase de tu aplicación
1878
+ es creada y todos los bloques de rutas son ejecutados en ese ámbito. Desde este
1879
+ ámbito podés acceder a los objetos +request+ y +session+ o llamar a los métodos
1880
+ de renderización como +erb+ o +haml+. Podés acceder al ámbito de la aplicación
1881
+ desde el ámbito de la petición utilizando +settings+:
1882
+
1883
+ class MiApp < Sinatra::Base
1884
+ # Ey, estoy en el ámbito de la aplicación!
1885
+ get '/definir_ruta/:nombre' do
1886
+ # Ámbito de petición para '/definir_ruta/:nombre'
1887
+ @valor = 42
1888
+
1889
+ settings.get("/#{params[:nombre]}") do
1890
+ # Ámbito de petición para "/#{params[:nombre]}"
1891
+ @valor # => nil (no es la misma petición)
1892
+ end
1893
+
1894
+ "Ruta definida!"
1895
+ end
1896
+ end
1897
+
1898
+ Tenés la ligadura al ámbito de la petición dentro de:
1899
+
1900
+ * bloques pasados a get/head/post/put/delete/options
1901
+ * filtros before/after
1902
+ * métodos ayudantes
1903
+ * plantillas/vistas
1904
+
1905
+ === Ámbito de Delegación
1906
+
1907
+ El ámbito de delegación solo reenvía métodos al ámbito de clase. De cualquier
1908
+ manera, no se comporta 100% como el ámbito de clase porque no tenés la ligadura
1909
+ de la clase: únicamente métodos marcados explícitamente para delegación están
1910
+ disponibles y no compartís variables/estado con el ámbito de clase (léase:
1911
+ tenés un +self+ diferente). Podés agregar delegaciones de método llamando a
1912
+ <tt>Sinatra::Delegator.delegate :nombre_del_metodo</tt>.
1913
+
1914
+ Tenés la ligadura al ámbito de delegación dentro de:
1915
+
1916
+ * La ligadura del top-level, si hiciste <tt>require "sinatra"</tt>
1917
+ * Un objeto extendido con el mixin <tt>Sinatra::Delegator</tt>
1918
+
1919
+ Pegale una mirada al código: acá está el
1920
+ {Sinatra::Delegator mixin}[https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633]
1921
+ que {extiende el objeto main}[https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30].
1922
+
1923
+ == Línea de Comandos
1924
+
1925
+ Las aplicaciones Sinatra pueden ser ejecutadas directamente:
1926
+
1927
+ ruby miapp.rb [-h] [-x] [-e ENTORNO] [-p PUERTO] [-o HOST] [-s MANEJADOR]
1928
+
1929
+ Las opciones son:
1930
+
1931
+ -h # ayuda
1932
+ -p # asigna el puerto (4567 es usado por defecto)
1933
+ -o # asigna el host (0.0.0.0 es usado por defecto)
1934
+ -e # asigna el entorno (development es usado por defecto)
1935
+ -s # especifica el servidor/manejador rack (thin es usado por defecto)
1936
+ -x # activa el mutex lock (está desactivado por defecto)
1937
+
1938
+ == Versiones de Ruby Soportadas
1939
+
1940
+ Las siguientes versiones de Ruby son soportadas oficialmente:
1941
+
1942
+ [ Ruby 1.8.7 ]
1943
+ 1.8.7 es soportado completamente. Sin embargo, si no hay nada que te lo
1944
+ prohíba, te recomendamos que usés 1.9.2 o cambies a JRuby o Rubinius. No se
1945
+ dejará de dar soporte a 1.8.7 hasta Sinatra 2.0 y Ruby 2.0, aunque si se
1946
+ libera la versión 1.8.8 de Ruby las cosas podrían llegar a cambiar. Sin
1947
+ embargo, que eso ocurra es muy poco probable, e incluso el caso de que lo
1948
+ haga, puede que se siga dando soporte a 1.8.7. <b>Hemos dejado de soportar
1949
+ Ruby 1.8.6.</b> Si querés ejecutar Sinatra sobre 1.8.6, podés utilizar la
1950
+ versión 1.2, pero tené en cuenta que una vez que Sinatra 1.4.0 sea liberado,
1951
+ ya no se corregirán errores por más que se reciban reportes de los mismos.
1952
+
1953
+ [ Ruby 1.9.2 ]
1954
+ 1.9.2 es soportado y recomendado. Tené en cuenta que Radius y Markaby no son
1955
+ compatibles con 1.9 actualmente. Además, no usés 1.9.2p0, porque se producen
1956
+ fallos de segmentación cuando se ejecuta Sinatra. El soporte se mantendrá al
1957
+ menos hasta que se libere la versión 1.9.4/2.0 de Ruby. El soporte para la
1958
+ última versión de la serie 1.9 se mantendrá mientras lo haga el core team de
1959
+ Ruby.
1960
+
1961
+ [ Ruby 1.9.3 ]
1962
+ 1.9.3 es soportado completamente. De todas maneras, recomendamos esperar a
1963
+ que se liberen niveles de parche superiores (el actual es p0) antes de usarlo
1964
+ en producción. Es importante notar que el cambio desde una versión anterior a
1965
+ 1.9.3 va a invalidar todas las sesiones.
1966
+
1967
+ [ Rubinius ]
1968
+ Rubinius es soportado oficialmente (Rubinius >= 1.2.4). Todo funciona
1969
+ correctamente, incluyendo los lenguajes de plantillas. La próxima versión,
1970
+ 2.0, también es soportada.
1971
+
1972
+ [ JRuby ]
1973
+ JRuby es soportado oficialmente (JRuby >= 1.6.5). No se conocen problemas
1974
+ con librerías de plantillas de terceras partes. Sin embargo, si elegís usar
1975
+ JRuby, deberías examinar sus Rack handlers porque el servidor web Thin no es
1976
+ soportado completamente. El soporte de JRuby para extensiones C se encuentra
1977
+ en una etapa experimental, sin embargo, de momento solamente RDiscount,
1978
+ Redcarpet y RedCloth se ven afectadas.
1979
+
1980
+ Siempre le prestamos atención a las nuevas versiones de Ruby.
1981
+
1982
+ Las siguientes implementaciones de Ruby no se encuentran soportadas
1983
+ oficialmente. De cualquier manera, pueden ejecutar Sinatra:
1984
+
1985
+ * Versiones anteriores de JRuby y Rubinius
1986
+ * Ruby Enterprise Edition
1987
+ * MacRuby, Maglev e IronRuby
1988
+ * Ruby 1.9.0 y 1.9.1 (pero no te recomendamos que los usés)
1989
+
1990
+ No estar soportada oficialmente, significa que si las cosas solamente se rompen
1991
+ ahí y no en una plataforma soportada, asumimos que no es nuestro problema sino
1992
+ el suyo.
1993
+
1994
+ Nuestro servidor CI también se ejecuta sobre ruby-head (que será la próxima
1995
+ versión 2.0.0) y la rama 1.9.4. Como están en movimiento constante, no podemos
1996
+ garantizar nada. De todas formas, podés contar con que tanto 1.9.4-p0 como
1997
+ 2.0.0-p0 sea soportadas.
1998
+
1999
+ Sinatra debería funcionar en cualquier sistema operativo soportado por la
2000
+ implementación de Ruby elegida.
2001
+
2002
+ En este momento, no vas a poder ejecutar Sinatra en Cardinal, SmallRuby,
2003
+ BlueRuby o cualquier versión de Ruby anterior a 1.8.7.
2004
+
2005
+ == A la Vanguardia
2006
+
2007
+ Si querés usar el código de Sinatra más reciente, sentite libre de ejecutar
2008
+ tu aplicación sobre la rama master, en general es bastante estable.
2009
+
2010
+ También liberamos prereleases de vez en cuando, así, podés hacer
2011
+
2012
+ gem install sinatra --pre
2013
+
2014
+ Para obtener algunas de las últimas características.
2015
+
2016
+ === Con Bundler
2017
+
2018
+ Esta es la manera recomendada para ejecutar tu aplicación sobre la última
2019
+ versión de Sinatra usando {Bundler}[http://gembundler.com/].
2020
+
2021
+ Primero, instalá bundler si no lo hiciste todavía:
2022
+
2023
+ gem install bundler
2024
+
2025
+ Después, en el directorio de tu proyecto, creá un archivo +Gemfile+:
2026
+
2027
+ source :rubygems
2028
+ gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
2029
+
2030
+ # otras dependencias
2031
+ gem 'haml' # por ejemplo, si usás haml
2032
+ gem 'activerecord', '~> 3.0' # quizás también necesités ActiveRecord 3.x
2033
+
2034
+ Tené en cuenta que tenés que listar todas las dependencias directas de tu
2035
+ aplicación. No es necesario listar las dependencias de Sinatra (Rack y Tilt)
2036
+ porque Bundler las agrega directamente.
2037
+
2038
+ Ahora podés arrancar tu aplicación así:
2039
+
2040
+ bundle exec ruby miapp.rb
2041
+
2042
+ === Con Git
2043
+
2044
+ Cloná el repositorio localmente y ejecutá tu aplicación, asegurándote que el
2045
+ directorio <tt>sinatra/lib</tt> esté en el <tt>$LOAD_PATH</tt>:
2046
+
2047
+ cd miapp
2048
+ git clone git://github.com/sinatra/sinatra.git
2049
+ ruby -Isinatra/lib miapp.rb
2050
+
2051
+ Para actualizar el código fuente de Sinatra en el futuro:
2052
+
2053
+ cd miapp/sinatra
2054
+ git pull
2055
+
2056
+ === Instalación Global
2057
+
2058
+ Podés construir la gem vos mismo:
2059
+
2060
+ git clone git://github.com/sinatra/sinatra.git
2061
+ cd sinatra
2062
+ rake sinatra.gemspec
2063
+ rake install
2064
+
2065
+ Si instalás tus gems como root, el último paso debería ser
2066
+
2067
+ sudo rake install
2068
+
2069
+ == Versionado
2070
+
2071
+ Sinatra utiliza el {Versionado Semántico}[http://semver.org/],
2072
+ siguiendo las especificaciones SemVer y SemVerTag.
2073
+
2074
+ == Lecturas Recomendadas
2075
+
2076
+ * {Sito web del proyecto}[http://www.sinatrarb.com/] - Documentación
2077
+ adicional, noticias, y enlaces a otros recursos.
2078
+ * {Contribuyendo}[http://www.sinatrarb.com/contributing] - ¿Encontraste un
2079
+ error?. ¿Necesitás ayuda?. ¿Tenés un parche?.
2080
+ * {Seguimiento de problemas}[http://github.com/sinatra/sinatra/issues]
2081
+ * {Twitter}[http://twitter.com/sinatra]
2082
+ * {Lista de Correo}[http://groups.google.com/group/sinatrarb/topics]
2083
+ * {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] en http://freenode.net
2084
+ * {Sinatra Book}[http://sinatra-book.gittr.com] Tutorial (en inglés).
2085
+ * {Sinatra Recipes}[http://recipes.sinatrarb.com/] Recetas contribuidas
2086
+ por la comunidad (en inglés).
2087
+ * Documentación de la API para la
2088
+ {última versión liberada}[http://rubydoc.info/gems/sinatra] o para la
2089
+ {rama de desarrollo actual}[http://rubydoc.info/github/sinatra/sinatra]
2090
+ en http://rubydoc.info/
2091
+ * {Servidor de IC}[http://ci.rkh.im/view/Sinatra/]