sinatra-base 1.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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/]