Module: Sinatra::Helpers

Included in:
Base
Defined in:
lib/sinatra/base.rb

Overview

Methods available to routes, before/after filters, and views.

Defined Under Namespace

Classes: Stream

Constant Summary

ETAG_KINDS =
[:strong, :weak]

Instance Method Summary collapse

Instance Method Details

#attachment(filename = nil, disposition = 'attachment') ⇒ Object

Set the Content-Disposition to "attachment" with the specified filename, instructing the user agents to prompt to save.



340
341
342
343
344
345
346
347
348
# File 'lib/sinatra/base.rb', line 340

def attachment(filename = nil, disposition = 'attachment')
  response['Content-Disposition'] = disposition.to_s
  if filename
    params = '; filename="%s"' % File.basename(filename)
    response['Content-Disposition'] << params
    ext = File.extname(filename)
    content_type(ext) unless response['Content-Type'] or ext.empty?
  end
end

#backObject

Sugar for redirect (example: redirect back)



557
558
559
# File 'lib/sinatra/base.rb', line 557

def back
  request.referer
end

#body(value = nil, &block) ⇒ Object

Set or retrieve the response body. When a block is given, evaluation is deferred until the body is read with #each.



236
237
238
239
240
241
242
243
244
245
246
# File 'lib/sinatra/base.rb', line 236

def body(value = nil, &block)
  if block_given?
    def block.each; yield(call) end
    response.body = block
  elsif value
    headers.delete 'Content-Length' unless request.head? || value.is_a?(Rack::File) || value.is_a?(Stream)
    response.body = value
  else
    response.body
  end
end

#cache_control(*values) ⇒ Object

Specify response freshness policy for HTTP caches (Cache-Control header). Any number of non-value directives (:public, :private, :no_cache, :no_store, :must_revalidate, :proxy_revalidate) may be passed along with a Hash of value directives (:max_age, :min_stale, :s_max_age).

cache_control :public, :must_revalidate, :max_age => 60 => Cache-Control: public, must-revalidate, max-age=60

See RFC 2616 / 14.9 for more on standard cache control directives: http://tools.ietf.org/html/rfc2616#section-14.9.1



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/sinatra/base.rb', line 449

def cache_control(*values)
  if values.last.kind_of?(Hash)
    hash = values.pop
    hash.reject! { |k,v| v == false }
    hash.reject! { |k,v| values << k if v == true }
  else
    hash = {}
  end

  values.map! { |value| value.to_s.tr('_','-') }
  hash.each do |key, value|
    key = key.to_s.tr('_', '-')
    value = value.to_i if key == "max-age"
    values << "#{key}=#{value}"
  end

  response['Cache-Control'] = values.join(', ') if values.any?
end

#client_error?Boolean

whether or not the status is set to 4xx

Returns:

  • (Boolean)


577
578
579
# File 'lib/sinatra/base.rb', line 577

def client_error?
  status.between? 400, 499
end

#content_type(type = nil, params = {}) ⇒ Object

Set the Content-Type of the response body given a media type or file extension.



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/sinatra/base.rb', line 318

def content_type(type = nil, params = {})
  return response['Content-Type'] unless type
  default = params.delete :default
  mime_type = mime_type(type) || default
  fail "Unknown media type: %p" % type if mime_type.nil?
  mime_type = mime_type.dup
  unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type }
    params[:charset] = params.delete('charset') || settings.default_encoding
  end
  params.delete :charset if mime_type.include? 'charset'
  unless params.empty?
    mime_type << (mime_type.include?(';') ? ', ' : ';')
    mime_type << params.map do |key, val|
      val = val.inspect if val =~ /[";,]/
      "#{key}=#{val}"
    end.join(', ')
  end
  response['Content-Type'] = mime_type
end

#error(code, body = nil) ⇒ Object

Halt processing and return the error status provided.



284
285
286
287
288
# File 'lib/sinatra/base.rb', line 284

def error(code, body = nil)
  code, body    = 500, code.to_str if code.respond_to? :to_str
  response.body = body unless body.nil?
  halt code
end

#etag(value, options = {}) ⇒ Object

Set the response entity tag (HTTP 'ETag' header) and halt if conditional GET matches. The +value+ argument is an identifier that uniquely identifies the current version of the resource. The +kind+ argument indicates whether the etag should be used as a :strong (default) or :weak cache validator.

When the current request includes an 'If-None-Match' header with a matching etag, execution is immediately halted. If the request method is GET or HEAD, a '304 Not Modified' response is sent.



531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
# File 'lib/sinatra/base.rb', line 531

def etag(value, options = {})
  # Before touching this code, please double check RFC 2616 14.24 and 14.26.
  options      = {:kind => options} unless Hash === options
  kind         = options[:kind] || :strong
  new_resource = options.fetch(:new_resource) { request.post? }

  unless ETAG_KINDS.include?(kind)
    raise ArgumentError, ":strong or :weak expected"
  end

  value = '"%s"' % value
  value = "W/#{value}" if kind == :weak
  response['ETag'] = value

  if success? or status == 304
    if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
      halt(request.safe? ? 304 : 412)
    end

    if env['HTTP_IF_MATCH']
      halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
    end
  end
end

#expires(amount, *values) ⇒ Object

Set the Expires header and Cache-Control/max-age directive. Amount can be an integer number of seconds in the future or a Time object indicating when the response should be considered "stale". The remaining "values" arguments are passed to the #cache_control helper:

expires 500, :public, :must_revalidate => Cache-Control: public, must-revalidate, max-age=60 => Expires: Mon, 08 Jun 2009 08:50:17 GMT



477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/sinatra/base.rb', line 477

def expires(amount, *values)
  values << {} unless values.last.kind_of?(Hash)

  if amount.is_a? Integer
    time    = Time.now + amount.to_i
    max_age = amount
  else
    time    = time_for amount
    max_age = time - Time.now
  end

  values.last.merge!(:max_age => max_age)
  cache_control(*values)

  response['Expires'] = time.httpdate
end

#headers(hash = nil) ⇒ Object

Set multiple response headers with Hash.



296
297
298
299
# File 'lib/sinatra/base.rb', line 296

def headers(hash = nil)
  response.headers.merge! hash if hash
  response.headers
end

#informational?Boolean

whether or not the status is set to 1xx

Returns:

  • (Boolean)


562
563
564
# File 'lib/sinatra/base.rb', line 562

def informational?
  status.between? 100, 199
end

#last_modified(time) ⇒ Object

Set the last modified time of the resource (HTTP 'Last-Modified' header) and halt if conditional GET matches. The +time+ argument is a Time, DateTime, or other object that responds to +to_time+.

When the current request includes an 'If-Modified-Since' header that is equal or later than the time specified, execution is immediately halted with a '304 Not Modified' response.



501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
# File 'lib/sinatra/base.rb', line 501

def last_modified(time)
  return unless time
  time = time_for time
  response['Last-Modified'] = time.httpdate
  return if env['HTTP_IF_NONE_MATCH']

  if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
    # compare based on seconds since epoch
    since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
    halt 304 if since >= time.to_i
  end

  if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
    # compare based on seconds since epoch
    since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
    halt 412 if since < time.to_i
  end
rescue ArgumentError
end

#loggerObject

Access shared logger object.



307
308
309
# File 'lib/sinatra/base.rb', line 307

def logger
  request.logger
end

#mime_type(type) ⇒ Object

Look up a media type by file extension in Rack's mime registry.



312
313
314
# File 'lib/sinatra/base.rb', line 312

def mime_type(type)
  Base.mime_type(type)
end

#not_found(body = nil) ⇒ Object

Halt processing and return a 404 Not Found.



291
292
293
# File 'lib/sinatra/base.rb', line 291

def not_found(body = nil)
  error 404, body
end

#not_found?Boolean

whether or not the status is set to 404

Returns:

  • (Boolean)


587
588
589
# File 'lib/sinatra/base.rb', line 587

def not_found?
  status == 404
end

#redirect(uri, *args) ⇒ Object

Halt processing and redirect to the URI provided.



249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/sinatra/base.rb', line 249

def redirect(uri, *args)
  if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET'
    status 303
  else
    status 302
  end

  # According to RFC 2616 section 14.30, "the field value consists of a
  # single absolute URI"
  response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?)
  halt(*args)
end

#redirect?Boolean

whether or not the status is set to 3xx

Returns:

  • (Boolean)


572
573
574
# File 'lib/sinatra/base.rb', line 572

def redirect?
  status.between? 300, 399
end

#send_file(path, opts = {}) ⇒ Object

Use the contents of the file at +path+ as the response body.



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/sinatra/base.rb', line 351

def send_file(path, opts = {})
  if opts[:type] or not response['Content-Type']
    content_type opts[:type] || File.extname(path), :default => 'application/octet-stream'
  end

  disposition = opts[:disposition]
  filename    = opts[:filename]
  disposition = 'attachment' if disposition.nil? and filename
  filename    = path         if filename.nil?
  attachment(filename, disposition) if disposition

  last_modified opts[:last_modified] if opts[:last_modified]

  file      = Rack::File.new nil
  file.path = path
  result    = file.serving env
  result[1].each { |k,v| headers[k] ||= v }
  headers['Content-Length'] = result[1]['Content-Length']
  opts[:status] &&= Integer(opts[:status])
  halt opts[:status] || result[0], result[2]
rescue Errno::ENOENT
  not_found
end

#server_error?Boolean

whether or not the status is set to 5xx

Returns:

  • (Boolean)


582
583
584
# File 'lib/sinatra/base.rb', line 582

def server_error?
  status.between? 500, 599
end

#sessionObject

Access the underlying Rack session.



302
303
304
# File 'lib/sinatra/base.rb', line 302

def session
  request.session
end

#status(value = nil) ⇒ Object

Set or retrieve the response status code.



229
230
231
232
# File 'lib/sinatra/base.rb', line 229

def status(value = nil)
  response.status = value if value
  response.status
end

#stream(keep_open = false) ⇒ Object

Allows to start sending data to the client even though later parts of the response body have not yet been generated.

The close parameter specifies whether Stream#close should be called after the block has been executed. This is only relevant for evented servers like Thin or Rainbows.



433
434
435
436
437
# File 'lib/sinatra/base.rb', line 433

def stream(keep_open = false)
  scheduler = env['async.callback'] ? EventMachine : Stream
  current   = @params.dup
  body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
end

#success?Boolean

whether or not the status is set to 2xx

Returns:

  • (Boolean)


567
568
569
# File 'lib/sinatra/base.rb', line 567

def success?
  status.between? 200, 299
end

#time_for(value) ⇒ Object

Generates a Time object from the given value. Used by #expires and #last_modified.



593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
# File 'lib/sinatra/base.rb', line 593

def time_for(value)
  if value.respond_to? :to_time
    value.to_time
  elsif value.is_a? Time
    value
  elsif value.respond_to? :new_offset
    # DateTime#to_time does the same on 1.9
    d = value.new_offset 0
    t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
    t.getlocal
  elsif value.respond_to? :mday
    # Date#to_time does the same on 1.9
    Time.local(value.year, value.mon, value.mday)
  elsif value.is_a? Numeric
    Time.at value
  else
    Time.parse value.to_s
  end
rescue ArgumentError => boom
  raise boom
rescue Exception
  raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
end

#uri(addr = nil, absolute = true, add_script_name = true) ⇒ Object Also known as: url, to

Generates the absolute URI for a given path in the app. Takes Rack routers and reverse proxies into account.



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/sinatra/base.rb', line 264

def uri(addr = nil, absolute = true, add_script_name = true)
  return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/
  uri = [host = ""]
  if absolute
    host << "http#{'s' if request.secure?}://"
    if request.forwarded? or request.port != (request.secure? ? 443 : 80)
      host << request.host_with_port
    else
      host << request.host
    end
  end
  uri << request.script_name.to_s if add_script_name
  uri << (addr ? addr : request.path_info).to_s
  File.join uri
end