sinatra 1.4.0.c → 1.4.0.d

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sinatra might be problematic. Click here for more details.

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