From 1089a9cf7282f47c53bf82e21d4a026deb1fa9b2 Mon Sep 17 00:00:00 2001 From: Kashyap Date: Tue, 12 Mar 2013 23:43:26 +0530 Subject: [PATCH 1/2] Markdown parsing is done by Kramdown now instead of Redcarpet. Redcarpet does not parse inline HTML tags properly. Syntax highlighting comes free with Kramdown in the form of Coderay gem. The previous commit's generated html files were error prone. These are due to improper parsing of Redcarpet gem and because of errors in Readmes which have been corrected now. --- Rakefile | 23 +- _includes/README.de.html | 2652 ++++++++++++++++++++------------ _includes/README.es.html | 2517 +++++++++++++++++++----------- _includes/README.fr.html | 2862 ++++++++++++++++++++++------------- _includes/README.html | 2704 +++++++++++++++++++++------------ _includes/README.hu.html | 776 ++++++---- _includes/README.jp.html | 622 +++++--- _includes/README.ko.html | 2586 ++++++++++++++++++++----------- _includes/README.pt-br.html | 1244 ++++++++++----- _includes/README.pt-pt.html | 1000 ++++++++---- _includes/README.ru.html | 2625 +++++++++++++++++++++----------- _includes/README.zh.html | 2533 ++++++++++++++++++++----------- 12 files changed, 14506 insertions(+), 7638 deletions(-) diff --git a/Rakefile b/Rakefile index 6d25dba3..5779aac4 100644 --- a/Rakefile +++ b/Rakefile @@ -3,15 +3,9 @@ require 'rake/clean' require 'rdoc/encoding' require 'rdoc/markup/to_html' require 'redcarpet' -require 'rouge' -require 'rouge/plugins/redcarpet' require 'uri' require 'nokogiri' - - -class HTML < Redcarpet::Render::HTML - include Rouge::Plugins::Redcarpet # yep, that's it. -end +require 'kramdown' def cleanup(html) @@ -72,7 +66,7 @@ end task :default => ['_sinatra', '_contrib', :build] desc "Build outdated static files and API docs" -task :build => ['build:static'] +task :build => [:pull, 'build:static'] desc "Build outdated static files" task 'build:static' => readme("_includes/%s.html") + contrib("_includes/%s.html") @@ -109,16 +103,17 @@ task 'pull:contrib' => "_contrib" do sh "cd _contrib && git pull &>/dev/null" end -readme("_sinatra/%s") { |fn| file fn => '_sinatra' } +readme("_sinatra/%s.md") { |fn| file fn => '_sinatra' } file 'AUTHORS' => '_sinatra' - readme do |fn| - file "_includes/#{fn}.html" => ["_sinatra/#{fn}", "Rakefile"] do |f| - rndr = HTML.new(:safe_links_only => true) - markdown = Redcarpet::Markdown.new(rndr, :lax_spacing => true, :fenced_code_blocks => true) + file "_includes/#{fn}.html" => ["_sinatra/#{fn}.md", "Rakefile"] do |f| markdown_string = File.read("_sinatra/#{fn}.md").encode('UTF-16le', :invalid => :replace, :replace => "").encode("UTF-8") - html = cleanup(markdown.render(markdown_string)) + markdown_string.gsub!(/```(\s?(\w+\n))?/) do |match| + match =~ /```\s?\n/ ? "~~~~~\n" : match.sub(/```\s?/, "~~~~") + end + markdown = Kramdown::Document.new(markdown_string, :fenced_code_blocks => true, :coderay_line_numbers => nil, :auto_ids => false) + html = cleanup(markdown.to_html) File.open(f.name, 'w') { |io| io.write with_toc(html) } end end diff --git a/_includes/README.de.html b/_includes/README.de.html index e2191c6f..5c5c1c75 100644 --- a/_includes/README.de.html +++ b/_includes/README.de.html @@ -6,7 +6,6 @@
  • Rückgabewerte
  • Eigene Routen-Muster
  • -
  • Statische Dateien
  • Views/Templates
    1. Direkte Templates
    2. @@ -28,12 +27,12 @@
    3. RABL Templates
    4. Slim Templates
    5. Creole Templates
    6. -
    7. CoffeeScript Templates
      1. +
      2. CoffeeScript Templates
      3. Stylus Templates
      4. Yajl Templates
      5. +
      6. WLang Templates
      -
    8. WLang Templates
    9. Auf Variablen in Templates zugreifen
    10. Templates mit yield und verschachtelte Layouts
    11. Inline-Templates
    12. @@ -71,7 +70,6 @@
      1. Einstellung des Angriffsschutzes
      -
    13. Mögliche Einstellungen
    14. Umgebungen
    15. Fehlerbehandlung
      1. @@ -116,16 +114,28 @@ DSL, die das schnelle Erstellen von Webanwendungen in Ruby mit minimalem Aufwand ermöglicht:

        -
        # myapp.rb
        -require 'sinatra'
        -get '/' do
        -  'Hallo Welt!'
        -end
        -
        + +
        +
        +
        # myapp.rb
        +require 'sinatra'
        +get '/' do
        +  'Hallo Welt!'
        +end
        +
        +
        +
        +

        Einfach via rubygems installieren und starten:

        -
        gem install sinatra
        -ruby myapp.rb
        -
        + +
        +
        +
        gem install sinatra
        +ruby myapp.rb
        +
        +
        +
        +

        Die Seite kann nun unter http://localhost:4567 betrachtet werden.

        Es wird empfohlen, den Thin-Server via gem install thin zu installieren, den @@ -136,84 +146,132 @@

        Routen

        In Sinatra wird eine Route durch eine HTTP-Methode und ein URL-Muster definiert. Jeder dieser Routen wird ein Ruby-Block zugeordnet:

        -
        get '/' do
        -  .. zeige etwas ..
        -end
         
        -post '/' do
        -  .. erstelle etwas ..
        -end
        +
        +
        +
        get '/' do
        +  .. zeige etwas ..
        +end
         
        -put '/' do
        -  .. update etwas ..
        -end
        +post '/' do
        +  .. erstelle etwas ..
        +end
         
        -delete '/' do
        -  .. entferne etwas ..
        -end
        +put '/' do
        +  .. update etwas ..
        +end
         
        -options '/' do
        -  .. zeige, was wir können ..
        -end
        +delete '/' do
        +  .. entferne etwas ..
        +end
         
        -link '/' do
        -  .. verbinde etwas ..
        -end
        +options '/' do
        +  .. zeige, was wir können ..
        +end
         
        -unlink '/' do
        -  .. trenne etwas ..
        -end
        +link '/' do
        +  .. verbinde etwas ..
        +end
        +
        +unlink '/' do
        +  .. trenne etwas ..
        +end
        +
        +
        +
        +
        -

        Die Routen werden in der Reihenfolge durchlaufen, in der sie definiert wurden. Das erste Routen-Muster, das mit dem Request übereinstimmt, wird ausgeführt.

        Die Muster der Routen können benannte Parameter beinhalten, die über den params-Hash zugänglich gemacht werden:

        -
        get '/hallo/:name' do
        -  # passt auf "GET /hallo/foo" und "GET /hallo/bar"
        -  # params[:name] ist 'foo' oder 'bar'
        -  "Hallo #{params[:name]}!"
        -end
        -
        + +
        +
        +
        get '/hallo/:name' do
        +  # passt auf "GET /hallo/foo" und "GET /hallo/bar"
        +  # params[:name] ist 'foo' oder 'bar'
        +  "Hallo #{params[:name]}!"
        +end
        +
        +
        +
        +

        Man kann auf diese auch mit Block-Parametern zugreifen:

        -
        get '/hallo/:name' do |n|
        -  "Hallo #{n}!"
        -end
        -
        + +
        +
        +
        get '/hallo/:name' do |n|
        +  "Hallo #{n}!"
        +end
        +
        +
        +
        +

        Routen-Muster können auch mit Splat- oder Wildcard-Parametern über das params[:splat]-Array angesprochen werden:

        -
        get '/sag/*/zu/*' do
        -  # passt auf /sag/hallo/zu/welt
        -  params[:splat] # => ["hallo", "welt"]
        -end
        -
        -get '/download/*.*' do
        -  # passt auf /download/pfad/zu/datei.xml
        -  params[:splat] # => ["pfad/zu/datei", "xml"]
        -end
        -
        + +
        +
        +
        get '/sag/*/zu/*' do
        +  # passt auf /sag/hallo/zu/welt
        +  params[:splat] # => ["hallo", "welt"]
        +end
        +
        +get '/download/*.*' do
        +  # passt auf /download/pfad/zu/datei.xml
        +  params[:splat] # => ["pfad/zu/datei", "xml"]
        +end
        +
        +
        +
        +

        Oder mit Block-Parametern:

        -
        get '/download/*.*' do |pfad, endung|
        -  [pfad, endung] # => ["Pfad/zu/Datei", "xml"]
        -end
        -
        + +
        +
        +
        get '/download/*.*' do |pfad, endung|
        +  [pfad, endung] # => ["Pfad/zu/Datei", "xml"]
        +end
        +
        +
        +
        +

        Routen mit regulären Ausdrücken sind auch möglich:

        -
        get %r{/hallo/([\w]+)} do
        -  "Hallo, #{params[:captures].first}!"
        -end
        -
        + +
        +
        +
        get %r{/hallo/([\w]+)} do
        +  "Hallo, #{params[:captures].first}!"
        +end
        +
        +
        +
        +

        Und auch hier können Block-Parameter genutzt werden:

        -
        get %r{/hallo/([\w]+)} do |c|
        -  "Hallo, #{c}!"
        -end
        -
        + +
        +
        +
        get %r{/hallo/([\w]+)} do |c|
        +  "Hallo, #{c}!"
        +end
        +
        +
        +
        +

        Routen-Muster können auch mit optionalen Parametern ausgestattet werden:

        -
        get '/posts.?:format?' do
        -  # passt auf "GET /posts" sowie jegliche Erweiterung
        -  # wie "GET /posts.json", "GET /posts.xml" etc.
        -end
        -
        + +
        +
        +
        get '/posts.?:format?' do
        +  # passt auf "GET /posts" sowie jegliche Erweiterung
        +  # wie "GET /posts.json", "GET /posts.xml" etc.
        +end
        +
        +
        +
        +

        Anmerkung: Solange man den sog. Path Traversal Attack-Schutz nicht deaktiviert (siehe weiter unten), kann es sein, dass der Request-Pfad noch vor dem Abgleich mit den Routen modifiziert wird.

        @@ -224,56 +282,80 @@

        Bedingungen

        An Routen können eine Vielzahl von Bedingungen angehängt werden, die erfüllt sein müssen, damit der Block ausgeführt wird. Möglich wäre etwa eine Einschränkung des User-Agents:

        -
        get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
        -  "Du verwendest Songbird Version #{params[:agent][0]}"
        -end
        -
        -get '/foo' do
        -  # passt auf andere Browser
        -end
        -
        + +
        +
        +
        get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
        +  "Du verwendest Songbird Version #{params[:agent][0]}"
        +end
        +
        +get '/foo' do
        +  # passt auf andere Browser
        +end
        +
        +
        +
        +

        Andere mitgelieferte Bedingungen sind host_name und provides:

        -
        get '/', :host_name => /^admin\./ do
        -  "Adminbereich, Zugriff verweigert!"
        -end
        -
        -get '/', :provides => 'html' do
        -  haml :index
        -end
        -
        -get '/', :provides => ['rss', 'atom', 'xml'] do
        -  builder :feed
        -end
        -
        + +
        +
        +
        get '/', :host_name => /^admin\./ do
        +  "Adminbereich, Zugriff verweigert!"
        +end
        +
        +get '/', :provides => 'html' do
        +  haml :index
        +end
        +
        +get '/', :provides => ['rss', 'atom', 'xml'] do
        +  builder :feed
        +end
        +
        +
        +
        +

        Es können auch andere Bedingungen relativ einfach hinzugefügt werden:

        -
        set(:probability) { |value| condition { rand <= value } }
         
        -get '/auto_gewinnen', :probability => 0.1 do
        -  "Du hast gewonnen!"
        -end
        +
        +
        +
        set(:probability) { |value| condition { rand <= value } }
        +
        +get '/auto_gewinnen', :probability => 0.1 do
        +  "Du hast gewonnen!"
        +end
        +
        +get '/auto_gewinnen' do
        +  "Tut mir leid, verloren."
        +end
        +
        +
        +
        -get '/auto_gewinnen' do - "Tut mir leid, verloren." -end -

        Bei Bedingungen, die mehrere Werte annehmen können, sollte ein Splat verwendet werden:

        -
        set(:auth) do |*roles|   # <- hier kommt der Splat ins Spiel
        -  condition do
        -    unless logged_in? && roles.any? {|role| current_user.in_role? role }
        -      redirect "/login/", 303
        -    end
        -  end
        -end
        -
        -get "/mein/account/", :auth => [:user, :admin] do
        -  "Mein Account"
        -end
        -
        -get "/nur/admin/", :auth => :admin do
        -  "Nur Admins dürfen hier rein!"
        -end
        -
        + +
        +
        +
        set(:auth) do |*roles|   # <- hier kommt der Splat ins Spiel
        +  condition do
        +    unless logged_in? && roles.any? {|role| current_user.in_role? role }
        +      redirect "/login/", 303
        +    end
        +  end
        +end
        +
        +get "/mein/account/", :auth => [:user, :admin] do
        +  "Mein Account"
        +end
        +
        +get "/nur/admin/", :auth => :admin do
        +  "Nur Admins dürfen hier rein!"
        +end
        +
        +
        +
        +

        Rückgabewerte

        @@ -287,14 +369,20 @@

        Rückgabewerte

        einen Rack-Rückgabewert, einen Rack-Body oder einen HTTP-Status-Code handelt:

        Damit lässt sich relativ einfach Streaming implementieren:

        -
        class Stream
        -  def each
        -    100.times { |i| yield "#{i}\n" }
        -  end
        -end
        -
        -get('/') { Stream.new }
        -
        + +
        +
        +
        class Stream
        +  def each
        +    100.times { |i| yield "#{i}\n" }
        +  end
        +end
        +
        +get('/') { Stream.new }
        +
        +
        +
        +

        Ebenso kann die stream-Helfer-Methode (s.u.) verwendet werden, die Streaming direkt in die Route integriert.

        @@ -305,45 +393,67 @@

        Eigene Routen-Muster

        String-Muster und Reguläre Ausdrücke zum Abgleichen von Routen ausgestattet. Das muss aber noch nicht alles sein, es können ohne großen Aufwand eigene Routen-Muster erstellt werden:

        -
        class AllButPattern
        -  Match = Struct.new(:captures)
        -
        -  def initialize(except)
        -    @except   = except
        -    @captures = Match.new([])
        -  end
        -
        -  def match(str)
        -    @captures unless @except === str
        -  end
        -end
        -
        -def all_but(pattern)
        -  AllButPattern.new(pattern)
        -end
        -
        -get all_but("/index") do
        -  # ...
        -end
        -
        + +
        +
        +
        class AllButPattern
        +  Match = Struct.new(:captures)
        +
        +  def initialize(except)
        +    @except   = except
        +    @captures = Match.new([])
        +  end
        +
        +  def match(str)
        +    @captures unless @except === str
        +  end
        +end
        +
        +def all_but(pattern)
        +  AllButPattern.new(pattern)
        +end
        +
        +get all_but("/index") do
        +  # ...
        +end
        +
        +
        +
        +

        Beachte, dass das obige Beispiel etwas übertrieben wirkt. Es geht auch einfacher:

        -
        get // do
        -  pass if request.path_info == "/index"
        -  # ...
        -end
        -
        + +
        +
        +
        get // do
        +  pass if request.path_info == "/index"
        +  # ...
        +end
        +
        +
        +
        +

        Oder unter Verwendung eines negativen look ahead:

        -
        get %r{^(?!/index$)} do
        -  # ...
        -end
        -
        - -

        Statische Dateien

        + +
        +
        +
        get %r{^(?!/index$)} do
        +  # ...
        +end
        +
        +
        +
        +

        ## Statische Dateien

        Statische Dateien werden aus dem ./public-Ordner ausgeliefert. Es ist möglich, einen anderen Ort zu definieren, indem man die :public_folder-Option setzt:

        -
        set :public_folder, File.dirname(__FILE__) + '/static'
        -
        + +
        +
        +
        set :public_folder, File.dirname(__FILE__) + '/static'
        +
        +
        +
        +

        Zu beachten ist, dass der Ordnername public nicht Teil der URL ist. Die Datei ./public/css/style.css ist unter http://example.com/css/style.css zu finden.

        @@ -355,38 +465,68 @@

        Views/Templates

        Alle Templatesprachen verwenden ihre eigene Renderingmethode, die jeweils einen String zurückgibt:

        -
        get '/' do
        -  erb :index
        -end
        -
        + +
        +
        +
        get '/' do
        +  erb :index
        +end
        +
        +
        +
        +

        Dieses Beispiel rendert views/index.erb.

        Anstelle eines Templatenamens kann man auch direkt die Templatesprache verwenden:

        -
        get '/' do
        -  code = "<%= Time.now %>"
        -  erb code
        -end
        -
        + +
        +
        +
        get '/' do
        +  code = "<%= Time.now %>"
        +  erb code
        +end
        +
        +
        +
        +

        Templates nehmen ein zweite Argument an, den Options-Hash:

        -
        get '/' do
        -  erb :index, :layout => :post
        -end
        -
        + +
        +
        +
        get '/' do
        +  erb :index, :layout => :post
        +end
        +
        +
        +
        +

        Dieses Beispiel rendert views/index.erb eingebettet in views/post.erb (Voreinstellung ist views/layout.erb, sofern es vorhanden ist.)

        Optionen, die Sinatra nicht versteht, werden an das Template weitergereicht:

        -
        get '/' do
        -  haml :index, :format => :html5
        -end
        -
        + +
        +
        +
        get '/' do
        +  haml :index, :format => :html5
        +end
        +
        +
        +
        +

        Für alle Templates können auch generelle Einstellungen festgelegt werden:

        -
        set :haml, :format => :html5
         
        -get '/' do
        -  haml :index
        -end
        -
        +
        +
        +
        set :haml, :format => :html5
        +
        +get '/' do
        +  haml :index
        +end
        +
        +
        +
        +

        Optionen, die an die Rendermethode weitergegeben werden, überschreiben die Einstellungen, die mit set festgelegt wurden.

        @@ -397,7 +537,7 @@

        Views/Templates

        Liste von lokalen Variablen, die and das Dokument weitergegeben werden. Praktisch für Partials: - erb "", :locals => {:foo => "bar"} + erb "<%= foo %>", :locals => {:foo => "bar"}
        default_encoding
        @@ -440,20 +580,38 @@

        Views/Templates

        Sinatra geht davon aus, dass die Templates sich im ./views Verzeichnis befinden. Es kann jedoch ein anderer Ordner festgelegt werden:

        -
        set :views, settings.root + '/templates'
        -
        + +
        +
        +
        set :views, settings.root + '/templates'
        +
        +
        +
        +

        Es ist zu beachten, dass immer mit Symbolen auf Templates verwiesen werden muss, auch dann, wenn sie sich in einem Unterordner befinden:

        -
        haml :'unterverzeichnis/template'
        -
        + +
        +
        +
        haml :'unterverzeichnis/template'
        +
        +
        +
        +

        Rendering-Methoden rendern jeden String direkt.

        Direkte Templates

        -
        get '/' do
        -  haml '%div.title Hallo Welt'
        -end
        -
        + +
        +
        +
        get '/' do
        +  haml '%div.title Hallo Welt'
        +end
        +
        +
        +
        +

        Hier wird der String direkt gerendert.

        @@ -462,9 +620,15 @@

        Verfügbare Templatesprachen

        Einige Sprachen haben mehrere Implementierungen. Um festzulegen, welche verwendet wird (und dann auch Thread-sicher ist), verwendet man am besten zu Beginn ein 'require':

        -
        require 'rdiscount' # oder require 'bluecloth'
        -get('/') { markdown :index }
        -
        + +
        +
        +
        require 'rdiscount' # oder require 'bluecloth'
        +get('/') { markdown :index }
        +
        +
        +
        +

        Haml Templates

        @@ -640,13 +804,25 @@

        Markdown Templates

        Da man aus den Markdown-Templates heraus keine Ruby-Methoden aufrufen und auch keine locals verwenden kann, wird man Markdown üblicherweise in Kombination mit anderen Renderern verwenden wollen:

        -
        erb :overview, :locals => { :text => markdown(:einfuehrung) }
        -
        + +
        +
        +
        erb :overview, :locals => { :text => markdown(:einfuehrung) }
        +
        +
        +
        +

        Beachte, dass man die markdown-Methode auch aus anderen Templates heraus aufrufen kann:

        -
        %h1 Gruß von Haml!
        -%p= markdown(:Grüße)
        -
        + +
        +
        +
        %h1 Gruß von Haml!
        +%p= markdown(:Grüße)
        +
        +
        +
        +

        Da man Ruby nicht von Markdown heraus aufrufen kann, können auch Layouts nicht in Markdown geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die @@ -672,13 +848,25 @@

        Textile Templates

        Da man aus dem Textile-Template heraus keine Ruby-Methoden aufrufen und auch keine locals verwenden kann, wird man Textile üblicherweise in Kombination mit anderen Renderern verwenden wollen:

        -
        erb :overview, :locals => { :text => textile(:einfuehrung) }
        -
        + +
        +
        +
        erb :overview, :locals => { :text => textile(:einfuehrung) }
        +
        +
        +
        +

        Beachte, dass man die textile-Methode auch aus anderen Templates heraus aufrufen kann:

        -
        %h1 Gruß von Haml!
        -%p= textile(:Grüße)
        -
        + +
        +
        +
        %h1 Gruß von Haml!
        +%p= textile(:Grüße)
        +
        +
        +
        +

        Da man Ruby nicht von Textile heraus aufrufen kann, können auch Layouts nicht in Textile geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die @@ -704,13 +892,25 @@

        RDoc Templates

        Da man aus dem RDoc-Template heraus keine Ruby-Methoden aufrufen und auch keine locals verwenden kann, wird man RDoc üblicherweise in Kombination mit anderen Renderern verwenden wollen:

        -
        erb :overview, :locals => { :text => rdoc(:einfuehrung) }
        -
        + +
        +
        +
        erb :overview, :locals => { :text => rdoc(:einfuehrung) }
        +
        +
        +
        +

        Beachte, dass man die rdoc-Methode auch aus anderen Templates heraus aufrufen kann:

        -
        %h1 Gruß von Haml!
        -%p= rdoc(:Grüße)
        -
        + +
        +
        +
        %h1 Gruß von Haml!
        +%p= rdoc(:Grüße)
        +
        +
        +
        +

        Da man Ruby nicht von RDoc heraus aufrufen kann, können auch Layouts nicht in RDoc geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die @@ -809,32 +1009,45 @@

        Creole Templates

        Da man aus dem Creole-Template heraus keine Ruby-Methoden aufrufen und auch keine locals verwenden kann, wird man Creole üblicherweise in Kombination mit anderen Renderern verwenden wollen:

        -
        erb :overview, :locals => { :text => creole(:einfuehrung) }
        -
        + +
        +
        +
        erb :overview, :locals => { :text => creole(:einfuehrung) }
        +
        +
        +
        +

        Beachte, dass man die creole-Methode auch aus anderen Templates heraus aufrufen kann:

        -
        %h1 Gruß von Haml!
        -%p= creole(:Grüße)
        -
        + +
        +
        +
        %h1 Gruß von Haml!
        +%p= creole(:Grüße)
        +
        +
        +
        +

        Da man Ruby nicht von Creole heraus aufrufen kann, können auch Layouts nicht in Creole geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die :layout_engine-Option verwendet wird.

        -

        CoffeeScript Templates

        +

        CoffeeScript Templates

        +coffee-script + und eine Möglichkeit JavaScript auszuführen. + - - - + + @@ -865,14 +1078,20 @@

        Stylus Templates

        Abhängigkeit -coffee-script und eine Möglichkeit JavaScript auszuführen.
        Dateierweiterung .coffee
        Beispiel coffee :index

        Um Stylus-Templates ausführen zu können, müssen stylus und stylus/tilt zuerst geladen werden:

        -
        require 'sinatra'
        -require 'stylus'
        -require 'stylus/tilt'
        -
        -get '/' do
        -  stylus :example
        -end
        -
        + +
        +
        +
        require 'sinatra'
        +require 'stylus'
        +require 'stylus/tilt'
        +
        +get '/' do
        +  stylus :example
        +end
        +
        +
        +
        +

        Yajl Templates

        @@ -899,15 +1118,27 @@

        Yajl Templates

        Die Template-Quelle wird als Ruby-String evaluiert. Die daraus resultierende json Variable wird mit Hilfe von #to_json umgewandelt:

        -
        json = { :foo => 'bar' }
        -json[:baz] = key
        -
        + +
        +
        +
        json = { :foo => 'bar' }
        +json[:baz] = key
        +
        +
        +
        +

        Die :callback und :variable Optionen können mit dem gerenderten Objekt verwendet werden:

        -
        var resource = {"foo":"bar","baz":"qux"}; present(resource);
        -
        + +
        +
        +
        var resource = {"foo":"bar","baz":"qux"}; present(resource);
        +
        +
        +
        + -

        WLang Templates

        +

        WLang Templates

        @@ -934,17 +1165,29 @@

        Auf Variablen in Templates zugreifen

        Templates werden in demselben Kontext ausgeführt wie Routen. Instanzvariablen in Routen sind auch direkt im Template verfügbar:

        -
        get '/:id' do
        -  @foo = Foo.find(params[:id])
        -  haml '%h1= @foo.name'
        -end
        -
        + +
        +
        +
        get '/:id' do
        +  @foo = Foo.find(params[:id])
        +  haml '%h1= @foo.name'
        +end
        +
        +
        +
        +

        Oder durch einen expliziten Hash von lokalen Variablen:

        -
        get '/:id' do
        -  foo = Foo.find(params[:id])
        -  haml '%h1= bar.name', :locals => { :bar => foo }
        -end
        -
        + +
        +
        +
        get '/:id' do
        +  foo = Foo.find(params[:id])
        +  haml '%h1= bar.name', :locals => { :bar => foo }
        +end
        +
        +
        +
        +

        Dies wird typischerweise bei Verwendung von Subtemplates (partials) in anderen Templates eingesetzt.

        @@ -954,26 +1197,44 @@

        Templates mit yield und verschachtelte Layouts

        Ein Layout ist üblicherweise ein Template, dass ein yield aufruft. Ein solches Template kann entweder wie oben beschrieben über die :template option verwendet werden oder mit einem Block gerendert werden:

        -
        erb :post, :layout => false do
        -  erb :index
        -end
        -
        + +
        +
        +
        erb :post, :layout => false do
        +  erb :index
        +end
        +
        +
        +
        +

        Dieser Code entspricht weitestgehend erb :index, :layout => :post.

        Blöcke an Render-Methoden weiterzugeben ist besonders bei verschachtelten Layouts hilfreich:

        -
        erb :main_layout, :layout => false do
        -  erb :admin_layout do
        -    erb :user
        -  end
        -end
        -
        + +
        +
        +
        erb :main_layout, :layout => false do
        +  erb :admin_layout do
        +    erb :user
        +  end
        +end
        +
        +
        +
        +

        Der gleiche Effekt kann auch mit weniger Code erreicht werden:

        -
        erb :admin_layout, :layout => :main_layout do
        -  erb :user
        -end
        -
        -

        Zur Zeit nehmen folgende Renderer Blöcke an: erb, haml, liquid, slim + +

        +
        +
        erb :admin_layout, :layout => :main_layout do
        +  erb :user
        +end
        +
        +
        +
        + +

        Zur Zeit nehmen folgende Renderer Blöcke an: erb, haml, liquid, slim und wlang.

        Das gleich gilt auch für die allgemeine render Methode.

        @@ -982,21 +1243,27 @@

        Templates mit yield und verschachtelte Layouts

        Inline-Templates

        Templates können auch am Ende der Datei definiert werden:

        -
        require 'sinatra'
         
        -get '/' do
        -  haml :index
        -end
        +
        +
        +
        require 'sinatra'
        +
        +get '/' do
        +  haml :index
        +end
         
        -__END__
        +__END__
         
         @@ layout
         %html
           = yield
         
         @@ index
        -%div.title Hallo Welt!!!!!
        -
        +%div.title Hallo Welt!!!!! +
        + + +

        Anmerkung: Inline-Templates, die in der Datei definiert sind, die require 'sinatra' aufruft, werden automatisch geladen. Um andere Inline-Templates in anderen Dateien aufzurufen, muss explizit enable :inline_templates verwendet @@ -1006,47 +1273,71 @@

        Inline-Templates

        Benannte Templates

        Templates können auch mit der Top-Level template-Methode definiert werden:

        -
        template :layout do
        -  "%html\n  =yield\n"
        -end
        -
        -template :index do
        -  '%div.title Hallo Welt!'
        -end
        -
        -get '/' do
        -  haml :index
        -end
        -
        -

        Wenn ein Template mit dem Namen "layout" existiert, wird es bei jedem Aufruf + +

        +
        +
        template :layout do
        +  "%html\n  =yield\n"
        +end
        +
        +template :index do
        +  '%div.title Hallo Welt!'
        +end
        +
        +get '/' do
        +  haml :index
        +end
        +
        +
        +
        + +

        Wenn ein Template mit dem Namen “layout” existiert, wird es bei jedem Aufruf verwendet. Durch :layout => false kann das Ausführen verhindert werden:

        -
        get '/' do
        -  haml :index, :layout => request.xhr?
        -end
        -
        + +
        +
        +
        get '/' do
        +  haml :index, :layout => request.xhr?
        +end
        +
        +
        +
        +

        Dateiendungen zuordnen

        Um eine Dateiendung einer Template-Engine zuzuordnen, kann Tilt.register genutzt werden. Wenn etwa die Dateiendung tt für Textile-Templates genutzt werden soll, lässt sich dies wie folgt bewerkstelligen:

        -
        Tilt.register :tt, Tilt[:textile]
        -
        + +
        +
        +
        Tilt.register :tt, Tilt[:textile]
        +
        +
        +
        +

        Eine eigene Template-Engine hinzufügen

        Zu allererst muss die Engine bei Tilt registriert und danach eine Rendering-Methode erstellt werden:

        -
        Tilt.register :mtt, MeineTolleTemplateEngine
         
        -helpers do
        -  def mtt(*args) render(:mtt, *args) end
        -end
        +
        +
        +
        Tilt.register :mtt, MeineTolleTemplateEngine
        +
        +helpers do
        +  def mtt(*args) render(:mtt, *args) end
        +end
        +
        +get '/' do
        +  mtt :index
        +end
        +
        +
        +
        -get '/' do - mtt :index -end -

        Dieser Code rendert ./views/application.mtt. Siehe github.com/rtomayko/tilt, um mehr über Tilt zu erfahren.

        @@ -1058,133 +1349,228 @@

        Filter

        Routen, ausgeführt. So können etwa Request und Antwort geändert werden. Gesetzte Instanzvariablen in Filtern können in Routen und Templates verwendet werden:

        -
        before do
        -  @note = 'Hi!'
        -  request.path_info = '/foo/bar/baz'
        -end
        -
        -get '/foo/*' do
        -  @note #=> 'Hi!'
        -  params[:splat] #=> 'bar/baz'
        -end
        -
        + +
        +
        +
        before do
        +  @note = 'Hi!'
        +  request.path_info = '/foo/bar/baz'
        +end
        +
        +get '/foo/*' do
        +  @note #=> 'Hi!'
        +  params[:splat] #=> 'bar/baz'
        +end
        +
        +
        +
        +

        After-Filter werden nach jedem Request in demselben Kontext ausgeführt und können ebenfalls Request und Antwort ändern. In Before-Filtern gesetzte Instanzvariablen können in After-Filtern verwendet werden:

        -
        after do
        -  puts response.status
        -end
        -
        + +
        +
        +
        after do
        +  puts response.status
        +end
        +
        +
        +
        +

        Filter können optional auch mit einem Muster ausgestattet werden, welches auf den Request-Pfad passen muss, damit der Filter ausgeführt wird:

        -
        before '/protected/*' do
        -  authenticate!
        -end
        -
        -after '/create/:slug' do |slug|
        -  session[:last_slug] = slug
        -end
        -
        + +
        +
        +
        before '/protected/*' do
        +  authenticate!
        +end
        +
        +after '/create/:slug' do |slug|
        +  session[:last_slug] = slug
        +end
        +
        +
        +
        +

        Ähnlich wie Routen können Filter auch mit weiteren Bedingungen eingeschränkt werden:

        -
        before :agent => /Songbird/ do
        -  # ...
        -end
        -
        -after '/blog/*', :host_name => 'example.com' do
        -  # ...
        -end
        -
        + +
        +
        +
        before :agent => /Songbird/ do
        +  # ...
        +end
        +
        +after '/blog/*', :host_name => 'example.com' do
        +  # ...
        +end
        +
        +
        +
        +

        Helfer

        Durch die Top-Level helpers-Methode werden sogenannte Helfer-Methoden definiert, die in Routen und Templates verwendet werden können:

        -
        helpers do
        -  def bar(name)
        -    "#{name}bar"
        -  end
        -end
        -
        -get '/:name' do
        -  bar(params[:name])
        -end
        -
        + +
        +
        +
        helpers do
        +  def bar(name)
        +    "#{name}bar"
        +  end
        +end
        +
        +get '/:name' do
        +  bar(params[:name])
        +end
        +
        +
        +
        +

        Sessions verwenden

        -

        Sessions werden verwendet, um Zustände zwischen den Requests zu speichern. Sind sie aktiviert, kann ein Session-Hash je Benutzer-Session verwendet werden:

        -
        enable :sessions
         
        -get '/' do
        -  "value = " << session[:value].inspect
        -end
        +
        +
        +
        enable :sessions
        +
        +get '/' do
        +  "value = " << session[:value].inspect
        +end
        +
        +get '/:value' do
        +  session[:value] = params[:value]
        +end
        +
        +
        +
        -get '/:value' do - session[:value] = params[:value] -end -

        Beachte, dass enable :sessions alle Daten in einem Cookie speichert. Unter Umständen kann dies negative Effekte haben, z.B. verursachen viele Daten höheren, teilweise überflüssigen Traffic. Um das zu vermeiden, kann eine Rack- Session-Middleware verwendet werden. Dabei wird auf enable :sessions verzichtet und die Middleware wie üblich im Programm eingebunden:

        -
        use Rack::Session::Pool, :expire_after => 2592000
         
        -get '/' do
        -  "value = " << session[:value].inspect
        -end
        +
        +
        +
        use Rack::Session::Pool, :expire_after => 2592000
        +
        +get '/' do
        +  "value = " << session[:value].inspect
        +end
        +
        +get '/:value' do
        +  session[:value] = params[:value]
        +end
        +
        +
        +
        -get '/:value' do - session[:value] = params[:value] -end -

        Um die Sicherheit zu erhöhen, werden Cookies, die Session-Daten führen, mit einem sogenannten Session-Secret signiert. Da sich dieses Geheimwort bei jedem Neustart der Applikation automatisch ändert, ist es sinnvoll, ein eigenes zu wählen, damit sich alle Instanzen der Applikation dasselbe Session-Secret teilen:

        -
        set :session_secret, 'super secret'
        -
        + +
        +
        +
        set :session_secret, 'super secret'
        +
        +
        +
        +

        Zur weiteren Konfiguration kann man einen Hash mit Optionen in den sessions Einstellungen ablegen.

        -
        set :sessions, :domain => 'foo.com'
        -
        + +
        +
        +
        set :sessions, :domain => 'foo.com'
        +
        +
        +
        +

        Anhalten

        Zum sofortigen Stoppen eines Request in einem Filter oder einer Route:

        -
        halt
        -
        + +
        +
        +
        halt
        +
        +
        +
        +

        Der Status kann beim Stoppen auch angegeben werden:

        -
        halt 410
        -
        -

        Oder auch den Response-Body:

        -
        halt 'Hier steht der Body'
        -
        -

        Oder beides:

        -
        halt 401, 'verschwinde!'
        -
        -

        Sogar mit Headern:

        -
        halt 402, {'Content-Type' => 'text/plain'}, 'Rache'
        -
        -

        Natürlich ist es auch möglich, ein Template mit halt zu verwenden:

        -
        halt erb(:error)
        -
        - -

        Weiterspringen

        + +
        +
        +
        halt 410
        +
        +
        +
        + +

        Oder auch den Response-Body:

        + +
        +
        +
        halt 'Hier steht der Body'
        +
        +
        +
        + +

        Oder beides:

        + +
        +
        +
        halt 401, 'verschwinde!'
        +
        +
        +
        + +

        Sogar mit Headern:

        + +
        +
        +
        halt 402, {'Content-Type' => 'text/plain'}, 'Rache'
        +
        +
        +
        + +

        Natürlich ist es auch möglich, ein Template mit halt zu verwenden:

        + +
        +
        +
        halt erb(:error)
        +
        +
        +
        + + +

        Weiterspringen

        Eine Route kann mittels pass zu der nächsten passenden Route springen:

        -
        get '/raten/:wer' do
        -  pass unless params[:wer] == 'Frank'
        -  'Du hast mich!'
        -end
        -
        -get '/raten/*' do
        -  'Du hast mich nicht!'
        -end
        -
        + +
        +
        +
        get '/raten/:wer' do
        +  pass unless params[:wer] == 'Frank'
        +  'Du hast mich!'
        +end
        +
        +get '/raten/*' do
        +  'Du hast mich nicht!'
        +end
        +
        +
        +
        +

        Der Block wird sofort verlassen und es wird nach der nächsten treffenden Route gesucht. Ein 404-Fehler wird zurückgegeben, wenn kein treffendes Routen-Muster gefunden wird.

        @@ -1194,15 +1580,21 @@

        Eine andere Route ansteuern

        Manchmal entspricht pass nicht den Anforderungen, wenn das Ergebnis einer anderen Route gefordert wird. Um das zu erreichen, lässt sich call nutzen:

        -
        get '/foo' do
        -  status, headers, body = call env.merge("PATH_INFO" => '/bar')
        -  [status, headers, body.map(&:upcase)]
        -end
        -
        -get '/bar' do
        -  "bar"
        -end
        -
        + +
        +
        +
        get '/foo' do
        +  status, headers, body = call env.merge("PATH_INFO" => '/bar')
        +  [status, headers, body.map(&:upcase)]
        +end
        +
        +get '/bar' do
        +  "bar"
        +end
        +
        +
        +
        +

        Beachte, dass in dem oben angegeben Beispiel die Performance erheblich erhöht werden kann, wenn "bar" in eine Helfer-Methode umgewandelt wird, auf die /foo und /bar zugreifen können.

        @@ -1222,27 +1614,39 @@

        Body, Status-Code und Header setzen

        gesetzt wird. Das lässt sich mit der Helfer-Methode body bewerkstelligen. Wird body verwendet, lässt sich der Body jederzeit über diese Methode aufrufen:

        -
        get '/foo' do
        -  body "bar"
        -end
        -
        -after do
        -  puts body
        -end
        -
        + +
        +
        +
        get '/foo' do
        +  body "bar"
        +end
        +
        +after do
        +  puts body
        +end
        +
        +
        +
        +

        Ebenso ist es möglich, einen Block an body weiterzureichen, der dann vom Rack-Handler ausgeführt wird (lässt sich z.B. zur Umsetzung von Streaming -einsetzen, siehe auch "Rückgabewerte").

        +einsetzen, siehe auch “Rückgabewerte”).

        Vergleichbar mit body lassen sich auch Status-Code und Header setzen:

        -
        get '/foo' do
        -  status 418
        -  headers \
        -    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
        -    "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
        -  halt "Ich bin ein Teekesselchen"
        -end
        -
        + +
        +
        +
        get '/foo' do
        +  status 418
        +  headers \
        +    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
        +    "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
        +  halt "Ich bin ein Teekesselchen"
        +end
        +
        +
        +
        +

        Genau wie bei body liest ein Aufrufen von headers oder status ohne Argumente den aktuellen Wert aus.

        @@ -1254,16 +1658,22 @@

        Response-Streams

        Verbindung auch erst dann beenden und Daten so lange an den Client zurückschicken, bis er die Verbindung abbricht. Für diese Fälle gibt es die stream-Helfer-Methode, die es einem erspart eigene Lösungen zu schreiben:

        -
        get '/' do
        -  stream do |out|
        -    out << "Das ist ja mal wieder fanta -\n"
        -    sleep 0.5
        -    out << " (bitte warten…) \n"
        -    sleep 1
        -    out << "- stisch!\n"
        -  end
        -end
        -
        + +
        +
        +
        get '/' do
        +  stream do |out|
        +    out << "Das ist ja mal wieder fanta -\n"
        +    sleep 0.5
        +    out << " (bitte warten…) \n"
        +    sleep 1
        +    out << "- stisch!\n"
        +  end
        +end
        +
        +
        +
        +

        Damit lassen sich Streaming-APIs realisieren, sog. Server Sent Events die als Basis für WebSockets dienen. Ebenso können sie @@ -1283,46 +1693,58 @@

        Response-Streams

        späteren Zeitpunkt nachholen. Die Funktion ist jedoch nur bei Event-gesteuerten Serven wie Thin oder Rainbows möglich, andere Server werden trotzdem den Stream beenden:

        -
        # Durchgehende Anfrage (long polling)
         
        -set :server, :thin
        -connections = []
        +
        +
        +
        # Durchgehende Anfrage (long polling)
        +
        +set :server, :thin
        +connections = []
        +
        +get '/subscribe' do
        +  # Client-Registrierung beim Server, damit Events mitgeteilt werden können
        +  stream(:keep_open) { |out| connections << out }
         
        -get '/subscribe' do
        -  # Client-Registrierung beim Server, damit Events mitgeteilt werden können
        -  stream(:keep_open) { |out| connections << out }
        +  # tote Verbindungen entfernen 
        +  connections.reject!(&:closed?)
         
        -  # tote Verbindungen entfernen 
        -  connections.reject!(&:closed?)
        +  # Rückmeldung
        +  "Angemeldet"
        +end
         
        -  # Rückmeldung
        -  "Angemeldet"
        -end
        +post '/message' do
        +  connections.each do |out|
        +    # Den Client über eine neue Nachricht in Kenntnis setzen
        +    # notify client that a new message has arrived
        +    out << params[:message] << "\n"
         
        -post '/message' do
        -  connections.each do |out|
        -    # Den Client über eine neue Nachricht in Kenntnis setzen
        -    # notify client that a new message has arrived
        -    out << params[:message] << "\n"
        +    # Den Client zur erneuten Verbindung auffordern
        +    out.close
        +  end
         
        -    # Den Client zur erneuten Verbindung auffordern
        -    out.close
        -  end
        +  # Rückmeldung
        +  "Mitteiling erhalten"
        +end
        +
        +
        +
        - # Rückmeldung - "Mitteiling erhalten" -end -

        Logger

        Im Geltungsbereich eines Request stellt die logger Helfer-Methode eine Logger Instanz zur Verfügung:

        -
        get '/' do
        -  logger.info "es passiert gerade etwas"
        -  # ...
        -end
        -
        + +
        +
        +
        get '/' do
        +  logger.info "es passiert gerade etwas"
        +  # ...
        +end
        +
        +
        +
        +

        Der Logger übernimmt dabei automatisch alle im Rack-Handler eingestellten Log-Vorgaben. Ist Loggen ausgeschaltet, gibt die Methode ein Leerobjekt zurück. In den Routen und Filtern muss man sich also nicht weiter darum kümmern.

        @@ -1330,12 +1752,18 @@

        Logger

        Beachte, dass das Loggen standardmäßig nur für Sinatra::Application voreingestellt ist. Wird über Sinatra::Base vererbt, muss es erst aktiviert werden:

        -
        class MyApp < Sinatra::Base
        -  configure :production, :development  do
        -    enable :logging
        -  end
        -end
        -
        + +
        +
        +
        class MyApp < Sinatra::Base
        +  configure :production, :development  do
        +    enable :logging
        +  end
        +end
        +
        +
        +
        +

        Damit auch keine Middleware das Logging aktivieren kann, muss die logging Einstellung auf nil gesetzt werden. Das heißt aber auch, dass logger in diesem Fall nil zurückgeben wird. Üblicherweise wird das eingesetzt, wenn ein @@ -1348,23 +1776,41 @@

        Mime-Types

        Wenn send_file oder statische Dateien verwendet werden, kann es vorkommen, dass Sinatra den Mime-Typ nicht kennt. Registriert wird dieser mit mime_type per Dateiendung:

        -
        configure do
        -  mime_type :foo, 'text/foo'
        -end
        -
        + +
        +
        +
        configure do
        +  mime_type :foo, 'text/foo'
        +end
        +
        +
        +
        +

        Es kann aber auch der content_type-Helfer verwendet werden:

        -
        get '/' do
        -  content_type :foo
        -  "foo foo foo"
        -end
        -
        + +
        +
        +
        get '/' do
        +  content_type :foo
        +  "foo foo foo"
        +end
        +
        +
        +
        +

        URLs generieren

        Zum Generieren von URLs sollte die url-Helfer-Methode genutzen werden, so z.B. beim Einsatz von Haml:

        -
        %a{:href => url('/foo')} foo
        -
        + +
        +
        +
        %a{:href => url('/foo')} foo
        +
        +
        +
        +

        Soweit vorhanden, wird Rücksicht auf Proxys und Rack-Router genommen.

        Diese Methode ist ebenso über das Alias to zu erreichen (siehe Beispiel unten).

        @@ -1374,41 +1820,71 @@

        Browser-Umleitung

        Eine Browser-Umleitung kann mithilfe der redirect-Helfer-Methode erreicht werden:

        -
        get '/foo' do
        -  redirect to('/bar')
        -end
        -
        + +
        +
        +
        get '/foo' do
        +  redirect to('/bar')
        +end
        +
        +
        +
        +

        Weitere Parameter werden wie Argumente der halt-Methode behandelt:

        -
        redirect to('/bar'), 303
        -redirect 'http://google.com', 'Hier bist du falsch'
        -
        + +
        +
        +
        redirect to('/bar'), 303
        +redirect 'http://google.com', 'Hier bist du falsch'
        +
        +
        +
        +

        Ebenso leicht lässt sich ein Schritt zurück mit dem Alias redirect back erreichen:

        -
        get '/foo' do
        -  "<a href='/bar'>mach was</a>"
        -end
        -
        -get '/bar' do
        -  mach_was
        -  redirect back
        -end
        -
        + +
        +
        +
        get '/foo' do
        +  "<a href='/bar'>mach was</a>"
        +end
        +
        +get '/bar' do
        +  mach_was
        +  redirect back
        +end
        +
        +
        +
        +

        Um Argumente an ein Redirect weiterzugeben, können sie entweder dem Query übergeben:

        -
        redirect to('/bar?summe=42')
        -
        + +
        +
        +
        redirect to('/bar?summe=42')
        +
        +
        +
        +

        oder eine Session verwendet werden:

        -
        enable :sessions
         
        -get '/foo' do
        -  session[:secret] = 'foo'
        -  redirect to('/bar')
        -end
        +
        +
        +
        enable :sessions
        +
        +get '/foo' do
        +  session[:secret] = 'foo'
        +  redirect to('/bar')
        +end
        +
        +get '/bar' do
        +  session[:secret]
        +end
        +
        +
        +
        -get '/bar' do - session[:secret] -end -

        Cache einsetzen

        @@ -1416,52 +1892,88 @@

        Cache einsetzen

        ordentliches HTTP-Caching.

        Der Cache-Control-Header lässt sich ganz einfach einstellen:

        -
        get '/' do
        -  cache_control :public
        -  "schon gecached!"
        -end
        -
        + +
        +
        +
        get '/' do
        +  cache_control :public
        +  "schon gecached!"
        +end
        +
        +
        +
        +

        Profitipp: Caching im before-Filter aktivieren

        -
        before do
        -  cache_control :public, :must_revalidate, :max_age => 60
        -end
        -
        + +
        +
        +
        before do
        +  cache_control :public, :must_revalidate, :max_age => 60
        +end
        +
        +
        +
        +

        Bei Verwendung der expires-Helfermethode zum Setzen des gleichnamigen Headers, wird Cache-Control automatisch eigestellt:

        -
        before do
        -  expires 500, :public, :must_revalidate
        -end
        -
        + +
        +
        +
        before do
        +  expires 500, :public, :must_revalidate
        +end
        +
        +
        +
        +

        Um alles richtig zu machen, sollten auch etag oder last_modified verwendet werden. Es wird empfohlen, dass diese Helfer aufgerufen werden bevor die eigentliche Arbeit anfängt, da sie sofort eine Antwort senden, wenn der Client eine aktuelle Version im Cache vorhält:

        -
        get '/article/:id' do
        -  @article = Article.find params[:id]
        -  last_modified @article.updated_at
        -  etag @article.sha1
        -  erb :article
        -end
        -
        + +
        +
        +
        get '/article/:id' do
        +  @article = Article.find params[:id]
        +  last_modified @article.updated_at
        +  etag @article.sha1
        +  erb :article
        +end
        +
        +
        +
        +

        ebenso ist es möglich einen schwachen ETag zu verwenden:

        -
        etag @article.sha1, :weak
        -
        + +
        +
        +
        etag @article.sha1, :weak
        +
        +
        +
        +

        Diese Helfer führen nicht das eigentliche Caching aus, sondern geben die dafür notwendigen Informationen an den Cache weiter. Für schnelle Reverse-Proxy Cache-Lösungen bietet sich z.B. rack-cache an:

        -
        require "rack/cache"
        -require "sinatra"
         
        -use Rack::Cache
        +
        +
        +
        require "rack/cache"
        +require "sinatra"
        +
        +use Rack::Cache
        +
        +get '/' do
        +  cache_control :public, :max_age => 36000
        +  sleep 5
        +  "hello"
        +end
        +
        +
        +
        -get '/' do - cache_control :public, :max_age => 36000 - sleep 5 - "hello" -end -

        Um den Cache-Control-Header mit Informationen zu versorgen, verwendet man die :static_cache_control-Einstellung (s.u.).

        @@ -1472,27 +1984,51 @@

        Cache einsetzen

        wobei anderen Ressourcen (besipielsweise bei post), als neue Ressourcen behandelt werden. Dieses Verhalten lässt sich mit der :new_resource Option ändern:

        -
        get '/create' do
        -  etag '', :new_resource => true
        -  Article.create
        -  erb :new_article
        -end
        -
        + +
        +
        +
        get '/create' do
        +  etag '', :new_resource => true
        +  Article.create
        +  erb :new_article
        +end
        +
        +
        +
        +

        Soll das schwache ETag trotzdem verwendet werden, verwendet man die :kind Option:

        -
        etag '', :new_resource => true, :kind => :weak
        -
        + +
        +
        +
        etag '', :new_resource => true, :kind => :weak
        +
        +
        +
        +

        Dateien versenden

        Zum Versenden von Dateien kann die send_file-Helfer-Methode verwendet werden:

        -
        get '/' do
        -  send_file 'foo.png'
        -end
        -
        + +
        +
        +
        get '/' do
        +  send_file 'foo.png'
        +end
        +
        +
        +
        +

        Für send_file stehen einige Hash-Optionen zur Verfügung:

        -
        send_file 'foo.png', :type => :jpg
        -
        + +
        +
        +
        send_file 'foo.png', :type => :jpg
        +
        +
        +
        +
        filename
        Dateiname als Response. Standardwert ist der eigentliche Dateiname.
        @@ -1522,131 +2058,191 @@

        Das Request-Objekt

        Auf das request-Objekt der eigehenden Anfrage kann vom Anfrage-Scope aus zugegriffen werden:

        -
        # App läuft unter http://example.com/example
        -get '/foo' do
        -  t = %w[text/css text/html application/javascript]
        -  request.accept              # ['text/html', '*/*']
        -  request.accept? 'text/xml'  # true
        -  request.preferred_type(t)   # 'text/html'
        -  request.body                # Request-Body des Client (siehe unten)
        -  request.scheme              # "http"
        -  request.script_name         # "/example"
        -  request.path_info           # "/foo"
        -  request.port                # 80
        -  request.request_method      # "GET"
        -  request.query_string        # ""
        -  request.content_length      # Länge des request.body
        -  request.media_type          # Medientypus von request.body
        -  request.host                # "example.com"
        -  request.get?                # true (ähnliche Methoden für andere Verben)
        -  request.form_data?          # false
        -  request["irgendein_param"]  # Wert von einem Parameter; [] ist die Kurzform für den params Hash
        -  request.referrer            # Der Referrer des Clients oder '/'
        -  request.user_agent          # User-Agent (verwendet in der :agent Bedingung)
        -  request.cookies             # Hash des Browser-Cookies
        -  request.xhr?                # Ist das hier ein Ajax-Request?
        -  request.url                 # "http://example.com/example/foo"
        -  request.path                # "/example/foo"
        -  request.ip                  # IP-Adresse des Clients
        -  request.secure?             # false (true wenn SSL)
        -  request.forwarded?          # true (Wenn es hinter einem Reverse-Proxy verwendet wird)
        -  request.env                 # vollständiger env-Hash von Rack übergeben
        -end
        -
        + +
        +
        +
        # App läuft unter http://example.com/example
        +get '/foo' do
        +  t = %w[text/css text/html application/javascript]
        +  request.accept              # ['text/html', '*/*']
        +  request.accept? 'text/xml'  # true
        +  request.preferred_type(t)   # 'text/html'
        +  request.body                # Request-Body des Client (siehe unten)
        +  request.scheme              # "http"
        +  request.script_name         # "/example"
        +  request.path_info           # "/foo"
        +  request.port                # 80
        +  request.request_method      # "GET"
        +  request.query_string        # ""
        +  request.content_length      # Länge des request.body
        +  request.media_type          # Medientypus von request.body
        +  request.host                # "example.com"
        +  request.get?                # true (ähnliche Methoden für andere Verben)
        +  request.form_data?          # false
        +  request["irgendein_param"]  # Wert von einem Parameter; [] ist die Kurzform für den params Hash
        +  request.referrer            # Der Referrer des Clients oder '/'
        +  request.user_agent          # User-Agent (verwendet in der :agent Bedingung)
        +  request.cookies             # Hash des Browser-Cookies
        +  request.xhr?                # Ist das hier ein Ajax-Request?
        +  request.url                 # "http://example.com/example/foo"
        +  request.path                # "/example/foo"
        +  request.ip                  # IP-Adresse des Clients
        +  request.secure?             # false (true wenn SSL)
        +  request.forwarded?          # true (Wenn es hinter einem Reverse-Proxy verwendet wird)
        +  request.env                 # vollständiger env-Hash von Rack übergeben
        +end
        +
        +
        +
        +

        Manche Optionen, wie etwa script_name oder path_info, sind auch schreibbar:

        -
        before { request.path_info = "/" }
         
        -get "/" do
        -  "Alle Anfragen kommen hier an!"
        -end
        -
        +
        +
        +
        before { request.path_info = "/" }
        +
        +get "/" do
        +  "Alle Anfragen kommen hier an!"
        +end
        +
        +
        +
        +

        Der request.body ist ein IO- oder StringIO-Objekt:

        -
        post "/api" do
        -  request.body.rewind # falls schon jemand davon gelesen hat
        -  daten = JSON.parse request.body.read
        -  "Hallo #{daten['name']}!"
        -end
        -
        + +
        +
        +
        post "/api" do
        +  request.body.rewind # falls schon jemand davon gelesen hat
        +  daten = JSON.parse request.body.read
        +  "Hallo #{daten['name']}!"
        +end
        +
        +
        +
        +

        Anhänge

        Damit der Browser erkennt, dass ein Response gespeichert und nicht im Browser angezeigt werden soll, kann der attachment-Helfer verwendet werden:

        -
        get '/' do
        -  attachment
        -  "Speichern!"
        -end
        -
        + +
        +
        +
        get '/' do
        +  attachment
        +  "Speichern!"
        +end
        +
        +
        +
        +

        Ebenso kann eine Dateiname als Parameter hinzugefügt werden:

        -
        get '/' do
        -  attachment "info.txt"
        -  "Speichern!"
        -end
        -
        + +
        +
        +
        get '/' do
        +  attachment "info.txt"
        +  "Speichern!"
        +end
        +
        +
        +
        +

        Umgang mit Datum und Zeit

        Sinatra bietet eine time_for-Helfer-Methode, die aus einem gegebenen Wert ein Time-Objekt generiert. Ebenso kann sie nach DateTime, Date und ähnliche Klassen konvertieren:

        -
        get '/' do
        -  pass if Time.now > time_for('Dec 23, 2012')
        -  "noch Zeit"
        -end
        -
        + +
        +
        +
        get '/' do
        +  pass if Time.now > time_for('Dec 23, 2012')
        +  "noch Zeit"
        +end
        +
        +
        +
        +

        Diese Methode wird intern für +expires, last_modiefied und ihresgleichen verwendet. Mit ein paar Handgriffen lässt sich diese Methode also in ihrem Verhalten erweitern, indem man time_for in der eigenen Applikation überschreibt:

        -
        helpers do
        -  def time_for(value)
        -    case value
        -    when :yesterday then Time.now - 24*60*60
        -    when :tomorrow  then Time.now + 24*60*60
        -    else super
        -    end
        -  end
        -end
        -
        -get '/' do
        -  last_modified :yesterday
        -  expires :tomorrow
        -  "Hallo"
        -end
        -
        + +
        +
        +
        helpers do
        +  def time_for(value)
        +    case value
        +    when :yesterday then Time.now - 24*60*60
        +    when :tomorrow  then Time.now + 24*60*60
        +    else super
        +    end
        +  end
        +end
        +
        +get '/' do
        +  last_modified :yesterday
        +  expires :tomorrow
        +  "Hallo"
        +end
        +
        +
        +
        +

        Nachschlagen von Template-Dateien

        Die find_template-Helfer-Methode wird genutzt, um Template-Dateien zum Rendern aufzufinden:

        -
        find_template settings.views, 'foo', Tilt[:haml] do |file|
        -  puts "könnte diese hier sein: #{file}"
        -end
        -
        + +
        +
        +
        find_template settings.views, 'foo', Tilt[:haml] do |file|
        +  puts "könnte diese hier sein: #{file}"
        +end
        +
        +
        +
        +

        Das ist zwar nicht wirklich brauchbar, aber wenn man sie überschreibt, kann sie nützlich werden, um eigene Nachschlage-Mechanismen einzubauen. Zum Beispiel dann, wenn mehr als nur ein view-Verzeichnis verwendet werden soll:

        -
        set :views, ['views', 'templates']
        -
        -helpers do
        -  def find_template(views, name, engine, &block)
        -    Array(views).each { |v| super(v, name, engine, &block) }
        -  end
        -end
        -
        + +
        +
        +
        set :views, ['views', 'templates']
        +
        +helpers do
        +  def find_template(views, name, engine, &block)
        +    Array(views).each { |v| super(v, name, engine, &block) }
        +  end
        +end
        +
        +
        +
        +

        Ein anderes Beispiel wäre, verschiedene Vereichnisse für verschiedene Engines zu verwenden:

        -
        set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
        -
        -helpers do
        -  def find_template(views, name, engine, &block)
        -    _, folder = views.detect { |k,v| engine == Tilt[k] }
        -    folder ||= views[:default]
        -    super(folder, name, engine, &block)
        -  end
        -end
        -
        + +
        +
        +
        set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
        +
        +helpers do
        +  def find_template(views, name, engine, &block)
        +    _, folder = views.detect { |k,v| engine == Tilt[k] }
        +    folder ||= views[:default]
        +    super(folder, name, engine, &block)
        +  end
        +end
        +
        +
        +
        +

        Ebensogut könnte eine Extension aber auch geschrieben und mit anderen geteilt werden!

        @@ -1662,45 +2258,69 @@

        Nachschlagen von Template-Dateien

        Konfiguration

        Wird einmal beim Starten in jedweder Umgebung ausgeführt:

        -
        configure do
        -  # setze eine Option
        -  set :option, 'wert'
         
        -  # setze mehrere Optionen
        -  set :a => 1, :b => 2
        +
        +
        +
        configure do
        +  # setze eine Option
        +  set :option, 'wert'
        +
        +  # setze mehrere Optionen
        +  set :a => 1, :b => 2
         
        -  # das gleiche wie `set :option, true`
        -  enable :option
        +  # das gleiche wie `set :option, true`
        +  enable :option
         
        -  # das gleiche wie `set :option, false`
        -  disable :option
        +  # das gleiche wie `set :option, false`
        +  disable :option
        +
        +  # dynamische Einstellungen mit Blöcken
        +  set(:css_dir) { File.join(views, 'css') }
        +end
        +
        +
        +
        - # dynamische Einstellungen mit Blöcken - set(:css_dir) { File.join(views, 'css') } -end -

        Läuft nur, wenn die Umgebung (RACK_ENV-Umgebungsvariable) auf :production gesetzt ist:

        -
        configure :production do
        -  ...
        -end
        -
        + +
        +
        +
        configure :production do
        +  ...
        +end
        +
        +
        +
        +

        Läuft nur, wenn die Umgebung auf :production oder auf :test gesetzt ist:

        -
        configure :production, :test do
        -  ...
        -end
        -
        + +
        +
        +
        configure :production, :test do
        +  ...
        +end
        +
        +
        +
        +

        Diese Einstellungen sind über settings erreichbar:

        -
        configure do
        -  set :foo, 'bar'
        -end
        -
        -get '/' do
        -  settings.foo? # => true
        -  settings.foo  # => 'bar'
        -  ...
        -end
        -
        + +
        +
        +
        configure do
        +  set :foo, 'bar'
        +end
        +
        +get '/' do
        +  settings.foo? # => true
        +  settings.foo  # => 'bar'
        +  ...
        +end
        +
        +
        +
        +

        Einstellung des Angriffsschutzes

        @@ -1710,18 +2330,34 @@

        Einstellung des Angriffsschutzes

        lässt sich selbstverständlich deaktivieren, der damit verbundene Geschwindigkeitszuwachs steht aber in keinem Verhätnis zu den möglichen Risiken.

        -
        disable :protection
        -
        + +
        +
        +
        disable :protection
        +
        +
        +
        +

        Um einen bestimmten Schutzmechanismus zu deaktivieren, fügt man protection einen Hash mit Optionen hinzu:

        -
        set :protection, :except => :path_traversal
        -
        + +
        +
        +
        set :protection, :except => :path_traversal
        +
        +
        +
        +

        Neben Strings akzeptiert :except auch Arrays, um gleich mehrere Schutzmechanismen zu deaktivieren:

        -
        set :protection, :except => [:path_traversal, :session_hijacking]
        -
        - -

        Mögliche Einstellungen

        + +
        +
        +
        set :protection, :except => [:path_traversal, :session_hijacking]
        +
        +
        +
        +

        ## Mögliche Einstellungen

        absolute_redirects
        @@ -1736,9 +2372,8 @@

        Mögliche Einstellungen

        add_charsets
        Mime-Types werden hier automatisch der Helfer-Methode content_type zugeordnet. Es empfielt sich, Werte hinzuzufügen statt - sie zu überschreiben: settings.add_charsets - -
        + sie zu überschreiben: settings.add_charsets << "application/foobar" +
        app_file
        Pfad zur Hauptdatei der Applikation. Wird verwendet, um das Wurzel-, @@ -1869,8 +2504,10 @@

        Umgebungen

        Um die Anwendung in einer anderen Umgebung auszuführen kann man die -e Option verwenden:

        -
        ruby my_app.rb -e [ENVIRONMENT]
        -
        + +
        ruby my_app.rb -e [ENVIRONMENT]
        +
        +

        In der Anwendung kann man die die Methoden development?, test? und production? verwenden, um die aktuelle Umgebung zu erfahren.

        @@ -1885,47 +2522,85 @@

        Nicht gefunden

        Wenn eine Sinatra::NotFound-Exception geworfen wird oder der Statuscode 404 ist, wird der not_found-Handler ausgeführt:

        -
        not_found do
        -  'Seite kann nirgendwo gefunden werden.'
        -end
        -
        + +
        +
        +
        not_found do
        +  'Seite kann nirgendwo gefunden werden.'
        +end
        +
        +
        +
        +

        Fehler

        Der error-Handler wird immer ausgeführt, wenn eine Exception in einem Routen-Block oder in einem Filter geworfen wurde. Die Exception kann über die sinatra.error-Rack-Variable angesprochen werden:

        -
        error do
        -  'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].name
        -end
        -
        + +
        +
        +
        error do
        +  'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].name
        +end
        +
        +
        +
        +

        Benutzerdefinierte Fehler:

        -
        error MeinFehler do
        -  'Au weia, ' + env['sinatra.error'].message
        -end
        -
        + +
        +
        +
        error MeinFehler do
        +  'Au weia, ' + env['sinatra.error'].message
        +end
        +
        +
        +
        +

        Dann, wenn das passiert:

        -
        get '/' do
        -  raise MeinFehler, 'etwas Schlimmes ist passiert'
        -end
        -
        + +
        +
        +
        get '/' do
        +  raise MeinFehler, 'etwas Schlimmes ist passiert'
        +end
        +
        +
        +
        +

        bekommt man dieses:

        -
        Au weia, etwas Schlimmes ist passiert
        -
        + +
        Au weia, etwas Schlimmes ist passiert
        +
        +

        Alternativ kann ein Error-Handler auch für einen Status-Code definiert werden:

        -
        error 403 do
        -  'Zugriff verboten'
        -end
        -
        -get '/geheim' do
        -  403
        -end
        -
        + +
        +
        +
        error 403 do
        +  'Zugriff verboten'
        +end
        +
        +get '/geheim' do
        +  403
        +end
        +
        +
        +
        +

        Oder ein Status-Code-Bereich:

        -
        error 400..510 do
        -  'Hallo?'
        -end
        -
        + +
        +
        +
        error 400..510 do
        +  'Hallo?'
        +end
        +
        +
        +
        +

        Sinatra setzt verschiedene not_found- und error-Handler in der Development-Umgebung ein, um hilfreiche Debugging Informationen und Stack Traces anzuzeigen.

        @@ -1941,24 +2616,36 @@

        Rack-Middleware

        Sinatra macht das Erstellen von Middleware-Verkettungen mit der Top-Level-Methode use zu einem Kinderspiel:

        -
        require 'sinatra'
        -require 'meine_middleware'
         
        -use Rack::Lint
        -use MeineMiddleware
        +
        +
        +
        require 'sinatra'
        +require 'meine_middleware'
        +
        +use Rack::Lint
        +use MeineMiddleware
        +
        +get '/hallo' do
        +  'Hallo Welt'
        +end
        +
        +
        +
        -get '/hallo' do - 'Hallo Welt' -end -

        Die Semantik von use entspricht der gleichnamigen Methode der Rack::Builder-DSL (meist verwendet in Rackup-Dateien). Ein Beispiel dafür ist, dass die use-Methode mehrere/verschiedene Argumente und auch Blöcke entgegennimmt:

        -
        use Rack::Auth::Basic do |username, password|
        -  username == 'admin' && password == 'geheim'
        -end
        -
        + +
        +
        +
        use Rack::Auth::Basic do |username, password|
        +  username == 'admin' && password == 'geheim'
        +end
        +
        +
        +
        +

        Rack bietet eine Vielzahl von Standard-Middlewares für Logging, Debugging, URL-Routing, Authentifizierung und Session-Verarbeitung. Sinatra verwendet viele von diesen Komponenten automatisch, abhängig von der Konfiguration. So @@ -1976,33 +2663,39 @@

        Testen

        Sinatra-Tests können mit jedem auf Rack aufbauendem Test-Framework geschrieben werden. Rack::Test wird empfohlen:

        -
        require 'my_sinatra_app'
        -require 'test/unit'
        -require 'rack/test'
        -
        -class MyAppTest < Test::Unit::TestCase
        -  include Rack::Test::Methods
        -
        -  def app
        -    Sinatra::Application
        -  end
        -
        -  def test_my_default
        -    get '/'
        -    assert_equal 'Hallo Welt!', last_response.body
        -  end
        -
        -  def test_with_params
        -    get '/meet', :name => 'Frank'
        -    assert_equal 'Hallo Frank!', last_response.body
        -  end
        -
        -  def test_with_rack_env
        -    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
        -    assert_equal "Du verwendest Songbird!", last_response.body
        -  end
        -end
        -
        + +
        +
        +
        require 'my_sinatra_app'
        +require 'test/unit'
        +require 'rack/test'
        +
        +class MyAppTest < Test::Unit::TestCase
        +  include Rack::Test::Methods
        +
        +  def app
        +    Sinatra::Application
        +  end
        +
        +  def test_my_default
        +    get '/'
        +    assert_equal 'Hallo Welt!', last_response.body
        +  end
        +
        +  def test_with_params
        +    get '/meet', :name => 'Frank'
        +    assert_equal 'Hallo Frank!', last_response.body
        +  end
        +
        +  def test_with_rack_env
        +    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
        +    assert_equal "Du verwendest Songbird!", last_response.body
        +  end
        +end
        +
        +
        +
        +

        Hinweis: Wird Sinatra modular verwendet, muss Sinatra::Application mit dem Namen der Applikations-Klasse ersetzt werden.

        @@ -2017,32 +2710,44 @@

        Testen

        sie z.B. bei einer einzelnen Anwendungsdatei, ./public und ./views Ordner, Logging, Exception-Detail-Seite, usw.). Genau hier kommt Sinatra::Base ins Spiel:

        -
        require 'sinatra/base'
         
        -class MyApp < Sinatra::Base
        -  set :sessions, true
        -  set :foo, 'bar'
        +
        +
        +
        require 'sinatra/base'
        +
        +class MyApp < Sinatra::Base
        +  set :sessions, true
        +  set :foo, 'bar'
        +
        +  get '/' do
        +    'Hallo Welt!'
        +  end
        +end
        +
        +
        +
        - get '/' do - 'Hallo Welt!' - end -end -

        Die MyApp-Klasse ist eine unabhängige Rack-Komponente, die als Middleware, Endpunkt oder via Rails Metal verwendet werden kann. Verwendet wird sie durch use oder run von einer Rackup-config.ru-Datei oder als Server-Komponente einer Bibliothek:

        -
        MyApp.run! :host => 'localhost', :port => 9090
        -
        + +
        +
        +
        MyApp.run! :host => 'localhost', :port => 9090
        +
        +
        +
        +

        Die Methoden der Sinatra::Base-Subklasse sind genau dieselben wie die der Top-Level-DSL. Die meisten Top-Level-Anwendungen können mit nur zwei Veränderungen zu Sinatra::Base konvertiert werden:

          -
        • Die Datei sollte require 'sinatra/base' anstelle von require +
        • Die Datei sollte require 'sinatra/base' anstelle von require 'sinatra/base' aufrufen, ansonsten werden alle von Sinatras DSL-Methoden in den Top-Level-Namespace importiert.
        • -
        • Alle Routen, Error-Handler, Filter und Optionen der Applikation müssen in +
        • Alle Routen, Error-Handler, Filter und Optionen der Applikation müssen in einer Subklasse von Sinatra::Base definiert werden.

        Sinatra::Base ist ein unbeschriebenes Blatt. Die meisten Optionen sind per @@ -2108,53 +2813,81 @@

        Eine modulare Applikation bereitstellen

        Es gibt zwei übliche Wege, eine modulare Anwendung zu starten. Zum einen über run!:

        -
        # mein_app.rb
        -require 'sinatra/base'
         
        -class MeinApp < Sinatra::Base
        -  # ... Anwendungscode hierhin ...
        +
        +
        +
        # mein_app.rb
        +require 'sinatra/base'
        +
        +class MeinApp < Sinatra::Base
        +  # ... Anwendungscode hierhin ...
        +
        +  # starte den Server, wenn die Ruby-Datei direkt ausgeführt wird
        +  run! if app_file == $0
        +end
        +
        +
        +
        - # starte den Server, wenn die Ruby-Datei direkt ausgeführt wird - run! if app_file == $0 -end -

        Starte mit:

        -
        ruby mein_app.rb
        -
        + +
        ruby mein_app.rb
        +
        +

        Oder über eine config.ru-Datei, die es erlaubt, einen beliebigen Rack-Handler zu verwenden:

        -
        # config.ru (mit rackup starten)
        -require './mein_app'
        -run MeineApp
        -
        + +
        +
        +
        # config.ru (mit rackup starten)
        +require './mein_app'
        +run MeineApp
        +
        +
        +
        +

        Starte:

        -
        rackup -p 4567
        -
        + +
        rackup -p 4567
        +
        +

        Eine klassische Anwendung mit einer config.ru verwenden

        Schreibe eine Anwendungsdatei:

        -
        # app.rb
        -require 'sinatra'
         
        -get '/' do
        -  'Hallo Welt!'
        -end
        -
        +
        +
        +
        # app.rb
        +require 'sinatra'
        +
        +get '/' do
        +  'Hallo Welt!'
        +end
        +
        +
        +
        +

        sowie eine dazugehörige config.ru-Datei:

        -
        require './app'
        -run Sinatra::Application
        -
        + +
        +
        +
        require './app'
        +run Sinatra::Application
        +
        +
        +
        +

        Wann sollte eine config.ru-Datei verwendet werden?

        Anzeichen dafür, dass eine config.ru-Datei gebraucht wird:

          -
        • Es soll ein anderer Rack-Handler verwendet werden (Passenger, Unicorn, -Heroku, ...).
        • -
        • Es gibt mehr als nur eine Subklasse von Sinatra::Base.
        • -
        • Sinatra soll als Middleware verwendet werden, nicht als Endpunkt.
        • +
        • Es soll ein anderer Rack-Handler verwendet werden (Passenger, Unicorn, +Heroku, …).
        • +
        • Es gibt mehr als nur eine Subklasse von Sinatra::Base.
        • +
        • Sinatra soll als Middleware verwendet werden, nicht als Endpunkt.

        Es gibt keinen Grund, eine config.ru-Datei zu verwenden, nur weil eine Anwendung im modularen Stil betrieben werden soll. Ebenso wird keine Anwendung @@ -2167,75 +2900,99 @@

        Sinatra als Middleware nutzen

        kann außerdem jede Sinatra-Anwendung selbst als Middleware vor jeden beliebigen Rack-Endpunkt gehangen werden. Bei diesem Endpunkt muss es sich nicht um eine andere Sinatra-Anwendung handeln, es kann jede andere -Rack-Anwendung sein (Rails/Ramaze/Camping/...):

        -
        require 'sinatra/base'
        -
        -class LoginScreen < Sinatra::Base
        -  enable :sessions
        -
        -  get('/login') { haml :login }
        -
        -  post('/login') do
        -    if params[:name] == 'admin' && params[:password] == 'admin'
        -      session['user_name'] = params[:name]
        -    else
        -      redirect '/login'
        -    end
        -  end
        -end
        -
        -class MyApp < Sinatra::Base
        -  # Middleware wird vor Filtern ausgeführt
        -  use LoginScreen
        -
        -  before do
        -    unless session['user_name']
        -      halt "Zugriff verweigert, bitte <a href='/login'>einloggen</a>."
        -    end
        -  end
        -
        -  get('/') { "Hallo #{session['user_name']}." }
        -end
        -
        +Rack-Anwendung sein (Rails/Ramaze/Camping/…):

        + +
        +
        +
        require 'sinatra/base'
        +
        +class LoginScreen < Sinatra::Base
        +  enable :sessions
        +
        +  get('/login') { haml :login }
        +
        +  post('/login') do
        +    if params[:name] == 'admin' && params[:password] == 'admin'
        +      session['user_name'] = params[:name]
        +    else
        +      redirect '/login'
        +    end
        +  end
        +end
        +
        +class MyApp < Sinatra::Base
        +  # Middleware wird vor Filtern ausgeführt
        +  use LoginScreen
        +
        +  before do
        +    unless session['user_name']
        +      halt "Zugriff verweigert, bitte <a href='/login'>einloggen</a>."
        +    end
        +  end
        +
        +  get('/') { "Hallo #{session['user_name']}." }
        +end
        +
        +
        +
        +

        Dynamische Applikationserstellung

        Manche Situationen erfordern die Erstellung neuer Applikationen zur Laufzeit, ohne dass sie einer Konstanten zugeordnet werden. Dies lässt sich mit Sinatra.new erreichen:

        -
        require 'sinatra/base'
        -my_app = Sinatra.new { get('/') { "hallo" } }
        -my_app.run!
        -
        + +
        +
        +
        require 'sinatra/base'
        +my_app = Sinatra.new { get('/') { "hallo" } }
        +my_app.run!
        +
        +
        +
        +

        Die Applikation kann mit Hilfe eines optionalen Parameters erstellt werden:

        -
        # config.ru
        -require 'sinatra/base'
        -
        -controller = Sinatra.new do
        -  enable :logging
        -  helpers MyHelpers
        -end
        -
        -map('/a') do
        -  run Sinatra.new(controller) { get('/') { 'a' } }
        -end
        -
        -map('/b') do
        -  run Sinatra.new(controller) { get('/') { 'b' } }
        -end
        -
        + +
        +
        +
        # config.ru
        +require 'sinatra/base'
        +
        +controller = Sinatra.new do
        +  enable :logging
        +  helpers MyHelpers
        +end
        +
        +map('/a') do
        +  run Sinatra.new(controller) { get('/') { 'a' } }
        +end
        +
        +map('/b') do
        +  run Sinatra.new(controller) { get('/') { 'b' } }
        +end
        +
        +
        +
        +

        Das ist besonders dann interessant, wenn Sinatra-Erweiterungen getestet werden oder Sinatra in einer Bibliothek Verwendung findet.

        Ebenso lassen sich damit hervorragend Sinatra-Middlewares erstellen:

        -
        require 'sinatra/base'
         
        -use Sinatra do
        -  get('/') { ... }
        -end
        +
        +
        +
        require 'sinatra/base'
        +
        +use Sinatra do
        +  get('/') { ... }
        +end
        +
        +run RailsProject::Application
        +
        +
        +
        -run RailsProject::Application -

        Geltungsbereich und Bindung

        @@ -2254,31 +3011,38 @@

        Anwendungs- oder Klassen-Scope

        wird.

        Optionen, die via set gesetzt werden, sind Methoden auf Klassenebene:

        -
        class MyApp < Sinatra::Base
        -  # Hey, ich bin im Anwendungsscope!
        -  set :foo, 42
        -  foo # => 42
        -
        -  get '/foo' do
        -    # Hey, ich bin nicht mehr im Anwendungs-Scope!
        -  end
        -end
        -
        + +
        +
        +
        class MyApp < Sinatra::Base
        +  # Hey, ich bin im Anwendungsscope!
        +  set :foo, 42
        +  foo # => 42
        +
        +  get '/foo' do
        +    # Hey, ich bin nicht mehr im Anwendungs-Scope!
        +  end
        +end
        +
        +
        +
        +

        Im Anwendungs-Scope befindet man sich:

          -
        • In der Anwendungs-Klasse.
        • -
        • In Methoden, die von Erweiterungen definiert werden.
        • -
        • Im Block, der an helpers übergeben wird.
        • -
        • In Procs und Blöcken, die an set übergeben werden.
        • -
        • Der an Sinatra.new übergebene Block
        • +
        • In der Anwendungs-Klasse.
        • +
        • In Methoden, die von Erweiterungen definiert werden.
        • +
        • Im Block, der an helpers übergeben wird.
        • +
        • In Procs und Blöcken, die an set übergeben werden.
        • +
        • Der an Sinatra.new übergebene Block

        Auf das Scope-Objekt (die Klasse) kann wie folgt zugegriffen werden:

          -
        • Über das Objekt, das an den configure-Block übergeben wird (configure { +
        • Über das Objekt, das an den configure-Block übergeben wird (configure { |c| ... }).
        • -
        • settings aus den anderen Scopes heraus.
        • +
        • +settings aus den anderen Scopes heraus.

        Anfrage- oder Instanz-Scope

        @@ -2288,28 +3052,34 @@

        Anfrage- oder Instanz-Scope

        kann auf request oder session zugegriffen und Methoden wie erb oder haml aufgerufen werden. Außerdem kann mit der settings-Method auf den Anwendungs-Scope zugegriffen werden:

        -
        class MyApp < Sinatra::Base
        -  # Hey, ich bin im Anwendungs-Scope!
        -  get '/neue_route/:name' do
        -    # Anfrage-Scope für '/neue_route/:name'
        -    @value = 42
        -
        -    settings.get "/#{params[:name]}" do
        -      # Anfrage-Scope für "/#{params[:name]}"
        -      @value # => nil (nicht dieselbe Anfrage)
        -    end
        -
        -    "Route definiert!"
        -  end
        -end
        -
        + +
        +
        +
        class MyApp < Sinatra::Base
        +  # Hey, ich bin im Anwendungs-Scope!
        +  get '/neue_route/:name' do
        +    # Anfrage-Scope für '/neue_route/:name'
        +    @value = 42
        +
        +    settings.get "/#{params[:name]}" do
        +      # Anfrage-Scope für "/#{params[:name]}"
        +      @value # => nil (nicht dieselbe Anfrage)
        +    end
        +
        +    "Route definiert!"
        +  end
        +end
        +
        +
        +
        +

        Im Anfrage-Scope befindet man sich:

          -
        • In get/head/post/put/delete-Blöcken
        • -
        • In before/after-Filtern
        • -
        • In Helfer-Methoden
        • -
        • In Templates
        • +
        • In get/head/post/put/delete-Blöcken
        • +
        • In before/after-Filtern
        • +
        • In Helfer-Methoden
        • +
        • In Templates

        Delegation-Scope

        @@ -2325,8 +3095,8 @@

        Delegation-Scope

        Im Delegation-Scop befindet man sich:

          -
        • Im Top-Level, wenn require 'sinatra' aufgerufen wurde.
        • -
        • In einem Objekt, das mit dem Sinatra::Delegator-Mixin erweitert wurde.
        • +
        • Im Top-Level, wenn require 'sinatra' aufgerufen wurde.
        • +
        • In einem Objekt, das mit dem Sinatra::Delegator-Mixin erweitert wurde.

        Schau am besten im Code nach: Hier ist Sinatra::Delegator mixin definiert und wird in den [globalen Namespace @@ -2336,16 +3106,20 @@

        Delegation-Scope

        Kommandozeile

        Sinatra-Anwendungen können direkt von der Kommandozeile aus gestartet werden:

        -
        ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-h HOST] [-s HANDLER]
        -
        + +
        ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-h HOST] [-s HANDLER]
        +
        +

        Die Optionen sind:

        -
        -h # Hilfe
        +
        +
        -h # Hilfe
         -p # Port setzen (Standard ist 4567)
         -h # Host setzen (Standard ist 0.0.0.0)
         -e # Umgebung setzen (Standard ist development)
         -s # Rack-Server/Handler setzen (Standard ist thin)
         -x # Mutex-Lock einschalten (Standard ist off)
        -
        +
        +

        Systemanforderungen

        @@ -2393,10 +3167,10 @@

        Systemanforderungen

        Sinatra unterstützt, funktionieren aber normalerweise:

          -
        • Ruby Enterprise Edition
        • -
        • Ältere Versionen von JRuby und Rubinius
        • -
        • MacRuby, Maglev, IronRuby
        • -
        • Ruby 1.9.0 und 1.9.1 (wird jedoch nicht empfohlen, s.o.)
        • +
        • Ruby Enterprise Edition
        • +
        • Ältere Versionen von JRuby und Rubinius
        • +
        • MacRuby, Maglev, IronRuby
        • +
        • Ruby 1.9.0 und 1.9.1 (wird jedoch nicht empfohlen, s.o.)

        Nicht offiziell unterstützt bedeutet, dass wenn Sachen nicht funktionieren, wir davon ausgehen, dass es nicht an Sinatra sondern an der jeweiligen @@ -2419,8 +3193,10 @@

        Der neuste Stand (The Bleeding Edge)

        Um auf dem neusten Stand zu bleiben, kann der Master-Branch verwendet werden. Er sollte recht stabil sein. Ebenso gibt es von Zeit zu Zeit prerelease Gems, die so installiert werden:

        -
        gem install sinatra --pre
        -
        + +
        gem install sinatra --pre
        +
        +

        Mit Bundler

        @@ -2429,61 +3205,84 @@

        Mit Bundler

        nachfolgenden Weg.

        Soweit Bundler noch nicht installiert ist:

        -
        gem install bundler
        -
        + +
        gem install bundler
        +
        +

        Anschließend wird eine Gemfile-Datei im Projektverzeichnis mit folgendem Inhalt erstellt:

        -
        source :rubygems
        -gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
         
        -# evtl. andere Abhängigkeiten
        -gem 'haml'                    # z.B. wenn du Haml verwendest...
        -gem 'activerecord', '~> 3.0'  # ...oder ActiveRecord 3.x
        -
        +
        +
        +
        source :rubygems
        +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
        +
        +# evtl. andere Abhängigkeiten
        +gem 'haml'                    # z.B. wenn du Haml verwendest...
        +gem 'activerecord', '~> 3.0'  # ...oder ActiveRecord 3.x
        +
        +
        +
        +

        Beachte: Hier sollten alle Abhängigkeiten eingetragen werden. Sinatras eigene, direkte Abhängigkeiten (Tilt und Rack) werden von Bundler automatisch aus dem Gemfile von Sinatra hinzugefügt.

        Jetzt kannst du deine Applikation starten:

        -
        bundle exec ruby myapp.rb
        -
        + +
        bundle exec ruby myapp.rb
        +
        +

        Eigenes Repository

        -

        Um auf dem neuesten Stand von Sinatras Code zu sein, kann eine lokale Kopie angelegt werden. Gestartet wird in der Anwendung mit dem sinatra/lib- Ordner im LOAD_PATH:

        -
        cd myapp
        +
        +
        cd myapp
         git clone git://github.com/sinatra/sinatra.git
         ruby -Isinatra/lib myapp.rb
        -
        +
        +

        Alternativ kann der sinatra/lib-Ordner zum LOAD_PATH in der Anwendung hinzugefügt werden:

        -
        $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
        -require 'rubygems'
        -require 'sinatra'
        -
        -get '/ueber' do
        -  "Ich laufe auf Version " + Sinatra::VERSION
        -end
        -
        + +
        +
        +
        $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
        +require 'rubygems'
        +require 'sinatra'
        +
        +get '/ueber' do
        +  "Ich laufe auf Version " + Sinatra::VERSION
        +end
        +
        +
        +
        +

        Um Sinatra-Code von Zeit zu Zeit zu aktualisieren:

        -
        cd myproject/sinatra
        +
        +
        cd myproject/sinatra
         git pull
        -
        +
        +

        Gem erstellen

        Aus der eigenen lokalen Kopie kann nun auch ein globales Gem gebaut werden:

        -
        git clone git://github.com/sinatra/sinatra.git
        +
        +
        git clone git://github.com/sinatra/sinatra.git
         cd sinatra
         rake sinatra.gemspec
         rake install
        -
        +
        +

        Falls Gems als Root installiert werden sollen, sollte die letzte Zeile folgendermaßen lauten:

        -
        sudo rake install
        -
        + +
        sudo rake install
        +
        +

        Versions-Verfahren

        @@ -2494,24 +3293,25 @@

        Versions-Verfahren

        Mehr

        diff --git a/_includes/README.es.html b/_includes/README.es.html index 7833d685..e44b1034 100644 --- a/_includes/README.es.html +++ b/_includes/README.es.html @@ -102,17 +102,29 @@

        Sinatra es un DSL para crear aplicaciones web rápidamente en Ruby con un mínimo esfuerzo:

        -
        # miapp.rb
        -require 'sinatra'
         
        -get '/' do
        -  'Hola mundo!'
        -end
        -
        +
        +
        +
        # miapp.rb
        +require 'sinatra'
        +
        +get '/' do
        +  'Hola mundo!'
        +end
        +
        +
        +
        +

        Instalá la gem y ejecutá la aplicación con:

        -
        gem install sinatra
        +
        +
        +
        +
        gem install sinatra
         ruby miapp.rb
        -
        +
        + + +

        Podés verla en: http://localhost:4567

        Es recomendable además ejecutar gem install thin, ya que Sinatra lo va @@ -123,79 +135,127 @@

        Rutas

        En Sinatra, una ruta está compuesta por un método HTTP y un patrón de una URL. Cada ruta se asocia con un bloque:

        -
        get '/' do
        -  .. mostrar algo ..
        -end
        -
        -post '/' do
        -  .. crear algo ..
        -end
        -
        -put '/' do
        -  .. reemplazar algo ..
        -end
        -
        -patch '/' do
        -  .. modificar algo ..
        -end
        -
        -delete '/' do
        -  .. aniquilar algo ..
        -end
        -
        -options '/' do
        -  .. informar algo ..
        -end
        -
        + +
        +
        +
        get '/' do
        +  .. mostrar algo ..
        +end
        +
        +post '/' do
        +  .. crear algo ..
        +end
        +
        +put '/' do
        +  .. reemplazar algo ..
        +end
        +
        +patch '/' do
        +  .. modificar algo ..
        +end
        +
        +delete '/' do
        +  .. aniquilar algo ..
        +end
        +
        +options '/' do
        +  .. informar algo ..
        +end
        +
        +
        +
        +

        Las rutas son comparadas en el orden en el que son definidas. La primer ruta que coincide con la petición es invocada.

        Los patrones de las rutas pueden incluir parámetros nombrados, accesibles a través de el hash params:

        -
        get '/hola/:nombre' do
        -  # coincide con "GET /hola/foo" y "GET /hola/bar"
        -  # params[:nombre] es 'foo' o 'bar'
        -  "Hola #{params[:nombre]}!"
        -end
        -
        + +
        +
        +
        get '/hola/:nombre' do
        +  # coincide con "GET /hola/foo" y "GET /hola/bar"
        +  # params[:nombre] es 'foo' o 'bar'
        +  "Hola #{params[:nombre]}!"
        +end
        +
        +
        +
        +

        También podés acceder a los parámetros nombrados usando parámetros de bloque:

        -
        get '/hola/:nombre' do |n|
        -  "Hola #{n}!"
        -end
        -
        + +
        +
        +
        get '/hola/:nombre' do |n|
        +  "Hola #{n}!"
        +end
        +
        +
        +
        +

        Los patrones de ruta también pueden incluir parámetros splat (o wildcard), accesibles a través del arreglo params[:splat]:

        -
        get '/decir/*/al/*' do
        -  # coincide con /decir/hola/al/mundo
        -  params[:splat] # => ["hola", "mundo"]
        -end
        -
        -get '/descargar/*.*' do
        -  # coincide con /descargar/path/al/archivo.xml
        -  params[:splat] # => ["path/al/archivo", "xml"]
        -end
        -
        + +
        +
        +
        get '/decir/*/al/*' do
        +  # coincide con /decir/hola/al/mundo
        +  params[:splat] # => ["hola", "mundo"]
        +end
        +
        +get '/descargar/*.*' do
        +  # coincide con /descargar/path/al/archivo.xml
        +  params[:splat] # => ["path/al/archivo", "xml"]
        +end
        +
        +
        +
        +

        O, con parámetros de bloque:

        -
        get '/descargar/*.*' do |path, ext|
        -  [path, ext] # => ["path/al/archivo", "xml"]
        -end
        -
        + +
        +
        +
        get '/descargar/*.*' do |path, ext|
        +  [path, ext] # => ["path/al/archivo", "xml"]
        +end
        +
        +
        +
        +

        Rutas con Expresiones Regulares:

        -
        get %r{/hola/([\w]+)} do
        -  "Hola, #{params[:captures].first}!"
        -end
        -
        + +
        +
        +
        get %r{/hola/([\w]+)} do
        +  "Hola, #{params[:captures].first}!"
        +end
        +
        +
        +
        +

        O con un parámetro de bloque:

        -
        get %r{/hola/([\w]+)} do |c|
        -  "Hola, #{c}!"
        -end
        -
        + +
        +
        +
        get %r{/hola/([\w]+)} do |c|
        +  "Hola, #{c}!"
        +end
        +
        +
        +
        +

        Los patrones de ruta pueden contener parámetros opcionales:

        -
        get '/posts.?:formato?' do
        -  # coincide con "GET /posts" y además admite cualquier extensión, por
        -  # ejemplo, "GET /posts.json", "GET /posts.xml", etc.
        -end
        -
        + +
        +
        +
        get '/posts.?:formato?' do
        +  # coincide con "GET /posts" y además admite cualquier extensión, por
        +  # ejemplo, "GET /posts.json", "GET /posts.xml", etc.
        +end
        +
        +
        +
        +

        A propósito, a menos que desactivés la protección para el ataque path traversal (ver más abajo), el path de la petición puede ser modificado antes de que se compare con los de tus rutas.

        @@ -205,57 +265,81 @@

        Condiciones

        Las rutas pueden incluir una variedad de condiciones de selección, como por ejemplo el user agent:

        -
        get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
        -  "Estás usando la versión de Songbird #{params[:agent][0]}"
        -end
        -
        -get '/foo' do
        -  # Coincide con browsers que no sean songbird
        -end
        -
        + +
        +
        +
        get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
        +  "Estás usando la versión de Songbird #{params[:agent][0]}"
        +end
        +
        +get '/foo' do
        +  # Coincide con browsers que no sean songbird
        +end
        +
        +
        +
        +

        Otras condiciones disponibles son host_name y provides:

        -
        get '/', :host_name => /^admin\./ do
        -  "Área de Administración, Acceso denegado!"
        -end
        -
        -get '/', :provides => 'html' do
        -  haml :index
        -end
        -
        -get '/', :provides => ['rss', 'atom', 'xml'] do
        -  builder :feed
        -end
        -
        + +
        +
        +
        get '/', :host_name => /^admin\./ do
        +  "Área de Administración, Acceso denegado!"
        +end
        +
        +get '/', :provides => 'html' do
        +  haml :index
        +end
        +
        +get '/', :provides => ['rss', 'atom', 'xml'] do
        +  builder :feed
        +end
        +
        +
        +
        +

        Podés definir tus propias condiciones fácilmente:

        -
        set(:probabilidad) { |valor| condition { rand <= valor } }
         
        -get '/gana_un_auto', :probabilidad => 0.1 do
        -  "Ganaste!"
        -end
        +
        +
        +
        set(:probabilidad) { |valor| condition { rand <= valor } }
        +
        +get '/gana_un_auto', :probabilidad => 0.1 do
        +  "Ganaste!"
        +end
        +
        +get '/gana_un_auto' do
        +  "Lo siento, perdiste."
        +end
        +
        +
        +
        -get '/gana_un_auto' do - "Lo siento, perdiste." -end -

        Si tu condición acepta más de un argumento, podés pasarle un arreglo. Al definir la condición puede resultarte conveniente utilizar el operador splat en la lista de parámetros:

        -
        set(:autorizar) do |*roles|   # <- mirá el splat
        -  condition do
        -    unless sesion_iniciada? && roles.any? {|rol| usuario_actual.tiene_rol? rol }
        -      redirect "/iniciar_sesion/", 303
        -    end
        -  end
        -end
        -
        -get "/mi/cuenta/", :autorizar => [:usuario, :administrador] do
        -  "Detalles de mi cuenta"
        -end
        -
        -get "/solo/administradores/", :autorizar => :administrador do
        -  "Únicamente para administradores!"
        -end
        -
        + +
        +
        +
        set(:autorizar) do |*roles|   # <- mirá el splat
        +  condition do
        +    unless sesion_iniciada? && roles.any? {|rol| usuario_actual.tiene_rol? rol }
        +      redirect "/iniciar_sesion/", 303
        +    end
        +  end
        +end
        +
        +get "/mi/cuenta/", :autorizar => [:usuario, :administrador] do
        +  "Detalles de mi cuenta"
        +end
        +
        +get "/solo/administradores/", :autorizar => :administrador do
        +  "Únicamente para administradores!"
        +end
        +
        +
        +
        +

        Valores de Retorno

        @@ -268,61 +352,91 @@

        Valores de Retorno

        que represente el cuerpo de una respuesta Rack o un código de estado HTTP:

        De esa manera podemos, por ejemplo, implementar fácilmente un streaming:

        -
        class Stream
        -  def each
        -    100.times { |i| yield "#{i}\n" }
        -  end
        -end
        -
        -get('/') { Stream.new }
        -
        + +
        +
        +
        class Stream
        +  def each
        +    100.times { |i| yield "#{i}\n" }
        +  end
        +end
        +
        +get('/') { Stream.new }
        +
        +
        +
        +

        Comparadores de Rutas Personalizados

        Como se mostró anteriormente, Sinatra permite utilizar Strings y expresiones regulares para definir las rutas. Sin embargo, la cosa no termina ahí. Podés definir tus propios comparadores muy fácilmente:

        -
        class PattronCualquieraMenos
        -  Match = Struct.new(:captures)
        -
        -  def initialize(excepto)
        -    @excepto  = excepto
        -    @capturas = Match.new([])
        -  end
        -
        -  def match(str)
        -    @capturas unless @excepto === str
        -  end
        -end
        -
        -def cualquiera_menos(patron)
        -  PatronCualquieraMenos.new(patron)
        -end
        -
        -get cualquiera_menos("/index") do
        -  # ...
        -end
        -
        + +
        +
        +
        class PattronCualquieraMenos
        +  Match = Struct.new(:captures)
        +
        +  def initialize(excepto)
        +    @excepto  = excepto
        +    @capturas = Match.new([])
        +  end
        +
        +  def match(str)
        +    @capturas unless @excepto === str
        +  end
        +end
        +
        +def cualquiera_menos(patron)
        +  PatronCualquieraMenos.new(patron)
        +end
        +
        +get cualquiera_menos("/index") do
        +  # ...
        +end
        +
        +
        +
        +

        Tené en cuenta que el ejemplo anterior es un poco rebuscado. Un resultado similar puede conseguirse más sencillamente:

        -
        get // do
        -  pass if request.path_info == "/index"
        -  # ...
        -end
        -
        + +
        +
        +
        get // do
        +  pass if request.path_info == "/index"
        +  # ...
        +end
        +
        +
        +
        +

        O, usando un lookahead negativo:

        -
        get %r{^(?!/index$)} do
        -  # ...
        -end
        -
        + +
        +
        +
        get %r{^(?!/index$)} do
        +  # ...
        +end
        +
        +
        +
        +

        Archivos Estáticos

        Los archivos estáticos son servidos desde el directorio público ./public. Podés especificar una ubicación diferente ajustando la opción :public_folder:

        -
        set :public_folder, File.dirname(__FILE__) + '/estaticos'
        -
        + +
        +
        +
        set :public_folder, File.dirname(__FILE__) + '/estaticos'
        +
        +
        +
        +

        Notá que el nombre del directorio público no está incluido en la URL. Por ejemplo, el archivo ./public/css/style.css se accede a través de http://ejemplo.com/css/style.css.

        @@ -335,43 +449,73 @@

        Vistas / Plantillas

        Cada lenguaje de plantilla se expone a través de un método de renderizado que lleva su nombre. Estos métodos simplemente devuelven un string:

        -
        get '/' do
        -  erb :index
        -end
        -
        + +
        +
        +
        get '/' do
        +  erb :index
        +end
        +
        +
        +
        +

        Renderiza views/index.erb.

        En lugar del nombre de la plantilla podés proporcionar directamente el contenido de la misma:

        -
        get '/' do
        -  codigo = "<%= Time.now %>"
        -  erb codigo
        -end
        -
        + +
        +
        +
        get '/' do
        +  codigo = "<%= Time.now %>"
        +  erb codigo
        +end
        +
        +
        +
        +

        Los métodos de renderizado, aceptan además un segundo argumento, el hash de opciones:

        -
        get '/' do
        -  erb :index, :layout => :post
        -end
        -
        + +
        +
        +
        get '/' do
        +  erb :index, :layout => :post
        +end
        +
        +
        +
        +

        Renderiza views/index.erb embebido en views/post.erb (por defecto, la plantilla :index es embebida en views/layout.erb siempre y cuando este último archivo exista).

        Cualquier opción que Sinatra no entienda le será pasada al motor de renderizado de la plantilla:

        -
        get '/' do
        -  haml :index, :format => :html5
        -end
        -
        + +
        +
        +
        get '/' do
        +  haml :index, :format => :html5
        +end
        +
        +
        +
        +

        Además podés definir las opciones para un lenguaje de plantillas de forma general:

        -
        set :haml, :format => :html5
         
        -get '/' do
        -  haml :index
        -end
        -
        +
        +
        +
        set :haml, :format => :html5
        +
        +get '/' do
        +  haml :index
        +end
        +
        +
        +
        +

        Las opciones pasadas al método de renderizado tienen precedencia sobre las definidas mediante set.

        @@ -382,7 +526,7 @@

        Vistas / Plantillas

        Lista de variables locales pasadas al documento. Resultan muy útiles cuando se combinan con parciales. - Ejemplo: erb "", :locals => {:foo => "bar"} + Ejemplo: erb "<%= foo %>", :locals => {:foo => "bar"}
        default_encoding
        @@ -445,9 +589,15 @@

        Lenguajes de Plantillas Disponibles

        Algunos lenguajes tienen varias implementaciones. Para especificar que implementación usar (y para ser thread-safe), deberías requerirla antes de usarla:

        -
        require 'rdiscount' # o require 'bluecloth'
        -get('/') { markdown :index }
        -
        + +
        +
        +
        require 'rdiscount' # o require 'bluecloth'
        +get('/') { markdown :index }
        +
        +
        +
        +

        Plantillas Haml

        @@ -624,13 +774,25 @@

        Plantillas Markdown

        No es posible llamar métodos desde markdown, ni pasarle locales. Por lo tanto, generalmente vas a usarlo en combinación con otro motor de renderizado:

        -
        erb :resumen, :locals => { :texto => markdown(:introduccion) }
        -
        + +
        +
        +
        erb :resumen, :locals => { :texto => markdown(:introduccion) }
        +
        +
        +
        +

        Tené en cuenta que también podés llamar al método markdown desde otras plantillas:

        -
        %h1 Hola Desde Haml!
        -%p= markdown(:saludos)
        -
        + +
        +
        +
        %h1 Hola Desde Haml!
        +%p= markdown(:saludos)
        +
        +
        +
        +

        Como no podés utilizar Ruby desde Markdown, no podés usar layouts escritos en Markdown. De todos modos, es posible usar un motor de renderizado para el layout distinto al de la plantilla pasando la opción :layout_engine.

        @@ -654,13 +816,25 @@

        Plantillas Textile

        No es posible llamar métodos desde textile, ni pasarle locales. Por lo tanto, generalmente vas a usarlo en combinación con otro motor de renderizado:

        -
        erb :resumen, :locals => { :texto => textile(:introduccion) }
        -
        + +
        +
        +
        erb :resumen, :locals => { :texto => textile(:introduccion) }
        +
        +
        +
        +

        Tené en cuenta que también podés llamar al método textile desde otras plantillas:

        -
        %h1 Hola Desde Haml!
        -%p= textile(:saludos)
        -
        + +
        +
        +
        %h1 Hola Desde Haml!
        +%p= textile(:saludos)
        +
        +
        +
        +

        Como no podés utilizar Ruby desde Textile, no podés usar layouts escritos en Textile. De todos modos, es posible usar un motor de renderizado para el layout distinto al de la plantilla pasando la opción :layout_engine.

        @@ -684,13 +858,25 @@

        Plantillas RDoc

        No es posible llamar métodos desde rdoc, ni pasarle locales. Por lo tanto, generalmente vas a usarlo en combinación con otro motor de renderizado:

        -
        erb :resumen, :locals => { :texto => rdoc(:introduccion) }
        -
        + +
        +
        +
        erb :resumen, :locals => { :texto => rdoc(:introduccion) }
        +
        +
        +
        +

        Tené en cuenta que también podés llamar al método rdoc desde otras plantillas:

        -
        %h1 Hola Desde Haml!
        -%p= rdoc(:saludos)
        -
        + +
        +
        +
        %h1 Hola Desde Haml!
        +%p= rdoc(:saludos)
        +
        +
        +
        +

        Como no podés utilizar Ruby desde RDoc, no podés usar layouts escritos en RDoc. De todos modos, es posible usar un motor de renderizado para el layout distinto al de la plantilla pasando la opción :layout_engine.

        @@ -787,13 +973,25 @@

        Plantillas Creole

        No es posible llamar métodos desde creole, ni pasarle locales. Por lo tanto, generalmente vas a usarlo en combinación con otro motor de renderizado:

        -
        erb :resumen, :locals => { :texto => cerole(:introduccion) }
        -
        + +
        +
        +
        erb :resumen, :locals => { :texto => cerole(:introduccion) }
        +
        +
        +
        +

        Tené en cuenta que también podés llamar al método creole desde otras plantillas:

        -
        %h1 Hola Desde Haml!
        -%p= creole(:saludos)
        -
        + +
        +
        +
        %h1 Hola Desde Haml!
        +%p= creole(:saludos)
        +
        +
        +
        +

        Como no podés utilizar Ruby desde Creole, no podés usar layouts escritos en Creloe. De todos modos, es posible usar un motor de renderizado para el layout distinto al de la plantilla pasando la opción :layout_engine.

        @@ -871,12 +1069,24 @@

        Plantillas Yajl

        El contenido de La plantilla se evalúa como código Ruby, y la variable json es convertida a JSON mediante #to_json.

        -
        json = { :foo => 'bar' }
        -json[:baz] = key
        -
        + +
        +
        +
        json = { :foo => 'bar' }
        +json[:baz] = key
        +
        +
        +
        +

        Las opciones :callback y :variable se pueden utilizar para decorar el objeto renderizado:

        -
        var resource = {"foo":"bar","baz":"qux"}; present(resource);
        -
        + +
        +
        +
        var resource = {"foo":"bar","baz":"qux"}; present(resource);
        +
        +
        +
        +

        Plantillas WLang

        @@ -899,10 +1109,16 @@

        Plantillas WLang

        Plantillas Embebidas

        -
        get '/' do
        -  haml '%div.titulo Hola Mundo'
        -end
        -
        + +
        +
        +
        get '/' do
        +  haml '%div.titulo Hola Mundo'
        +end
        +
        +
        +
        +

        Renderiza el template embebido en el string.

        @@ -911,17 +1127,29 @@

        Accediendo a Variables en Plantillas

        Las plantillas son evaluadas dentro del mismo contexto que los manejadores de ruta. Las variables de instancia asignadas en los manejadores de ruta son accesibles directamente por las plantillas:

        -
        get '/:id' do
        -  @foo = Foo.find(params[:id])
        -  haml '%h1= @foo.nombre'
        -end
        -
        + +
        +
        +
        get '/:id' do
        +  @foo = Foo.find(params[:id])
        +  haml '%h1= @foo.nombre'
        +end
        +
        +
        +
        +

        O es posible especificar un Hash de variables locales explícitamente:

        -
        get '/:id' do
        -  foo = Foo.find(params[:id])
        -  haml '%h1= bar.nombre', :locals => { :bar => foo }
        -end
        -
        + +
        +
        +
        get '/:id' do
        +  foo = Foo.find(params[:id])
        +  haml '%h1= bar.nombre', :locals => { :bar => foo }
        +end
        +
        +
        +
        +

        Esto es usado típicamente cuando se renderizan plantillas como parciales desde adentro de otras plantillas.

        @@ -929,22 +1157,28 @@

        Accediendo a Variables en Plantillas

        Plantillas Inline

        Las plantillas pueden ser definidas al final del archivo fuente:

        -
        require 'rubygems'
        -require 'sinatra'
         
        -get '/' do
        -  haml :index
        -end
        +
        +
        +
        require 'rubygems'
        +require 'sinatra'
        +
        +get '/' do
        +  haml :index
        +end
         
        -__END__
        +__END__
         
         @@ layout
         %html
           = yield
         
         @@ index
        -%div.titulo Hola mundo!!!!!
        -
        +%div.titulo Hola mundo!!!!! +
        + + +

        NOTA: únicamente las plantillas inline definidas en el archivo fuente que requiere sinatra son cargadas automáticamente. Llamá enable :inline_templates explícitamente si tenés plantillas inline en otros @@ -955,48 +1189,72 @@

        Plantillas Nombradas

        Las plantillas también pueden ser definidas usando el método top-level template:

        -
        template :layout do
        -  "%html\n  =yield\n"
        -end
        -
        -template :index do
        -  '%div.titulo Hola Mundo!'
        -end
        -
        -get '/' do
        -  haml :index
        -end
        -
        -

        Si existe una plantilla con el nombre "layout", va a ser usada cada vez que + +

        +
        +
        template :layout do
        +  "%html\n  =yield\n"
        +end
        +
        +template :index do
        +  '%div.titulo Hola Mundo!'
        +end
        +
        +get '/' do
        +  haml :index
        +end
        +
        +
        +
        + +

        Si existe una plantilla con el nombre “layout”, va a ser usada cada vez que una plantilla es renderizada. Podés desactivar los layouts individualmente pasando :layout => false o globalmente con set :haml, :layout => false:

        -
        get '/' do
        -  haml :index, :layout => !request.xhr?
        -end
        -
        + +
        +
        +
        get '/' do
        +  haml :index, :layout => !request.xhr?
        +end
        +
        +
        +
        +

        Asociando Extensiones de Archivo

        Para asociar una extensión de archivo con un motor de renderizado, usá Tilt.register. Por ejemplo, si querés usar la extensión tt para las plantillas Textile, podés hacer lo siguiente:

        -
        Tilt.register :tt, Tilt[:textile]
        -
        + +
        +
        +
        Tilt.register :tt, Tilt[:textile]
        +
        +
        +
        +

        Agregando Tu Propio Motor de Renderizado

        Primero, registrá tu motor con Tilt, y después, creá tu método de renderizado:

        -
        Tilt.register :mipg, MiMotorParaPlantillaGenial
         
        -helpers do
        -  def mypg(*args) render(:mypg, *args) end
        -end
        +
        +
        +
        Tilt.register :mipg, MiMotorParaPlantillaGenial
        +
        +helpers do
        +  def mypg(*args) render(:mypg, *args) end
        +end
        +
        +get '/' do
        +  mypg :index
        +end
        +
        +
        +
        -get '/' do - mypg :index -end -

        Renderiza ./views/index.mypg. Mirá https://github.com/rtomayko/tilt para aprender más de Tilt.

        @@ -1007,24 +1265,36 @@

        Filtros

        contexto que las rutas. Pueden modificar la petición y la respuesta. Las variables de instancia asignadas en los filtros son accesibles por las rutas y las plantillas:

        -
        before do
        -  @nota = 'Hey!'
        -  request.path_info = '/foo/bar/baz'
        -end
        -
        -get '/foo/*' do
        -  @nota #=> 'Hey!'
        -  params[:splat] #=> 'bar/baz'
        -end
        -
        + +
        +
        +
        before do
        +  @nota = 'Hey!'
        +  request.path_info = '/foo/bar/baz'
        +end
        +
        +get '/foo/*' do
        +  @nota #=> 'Hey!'
        +  params[:splat] #=> 'bar/baz'
        +end
        +
        +
        +
        +

        Los filtros after son evaluados después de cada petición dentro del mismo contexto y también pueden modificar la petición y la respuesta. Las variables de instancia asignadas en los filtros before y en las rutas son accesibles por los filtros after:

        -
        after do
        -  puts response.status
        -end
        -
        + +
        +
        +
        after do
        +  puts response.status
        +end
        +
        +
        +
        +

        Nota: A menos que usés el método body en lugar de simplemente devolver un string desde una ruta, el cuerpo de la respuesta no va a estar disponible en un filtro after, debido a que todavía no se ha generado.

        @@ -1032,50 +1302,74 @@

        Filtros

        Los filtros aceptan un patrón opcional, que cuando está presente causa que los mismos sean evaluados únicamente si el path de la petición coincide con ese patrón:

        -
        before '/protegido/*' do
        -  autenticar!
        -end
        -
        -after '/crear/:slug' do |slug|
        -  session[:ultimo_slug] = slug
        -end
        -
        + +
        +
        +
        before '/protegido/*' do
        +  autenticar!
        +end
        +
        +after '/crear/:slug' do |slug|
        +  session[:ultimo_slug] = slug
        +end
        +
        +
        +
        +

        Al igual que las rutas, los filtros también pueden aceptar condiciones:

        -
        before :agent => /Songbird/ do
        -  # ...
        -end
        -
        -after '/blog/*', :host_name => 'ejemplo.com' do
        -  # ...
        -end
        -
        + +
        +
        +
        before :agent => /Songbird/ do
        +  # ...
        +end
        +
        +after '/blog/*', :host_name => 'ejemplo.com' do
        +  # ...
        +end
        +
        +
        +
        +

        Ayudantes

        Usá el método top-level helpers para definir métodos ayudantes que pueden ser utilizados dentro de los manejadores de rutas y las plantillas:

        -
        helpers do
        -  def bar(nombre)
        -    "#{nombre}bar"
        -  end
        -end
        -
        -get '/:nombre' do
        -  bar(params[:nombre])
        -end
        -
        + +
        +
        +
        helpers do
        +  def bar(nombre)
        +    "#{nombre}bar"
        +  end
        +end
        +
        +get '/:nombre' do
        +  bar(params[:nombre])
        +end
        +
        +
        +
        +

        Por cuestiones organizativas, puede resultar conveniente organizar los métodos ayudantes en distintos módulos:

        -
        module FooUtils
        -  def foo(nombre) "#{nombre}foo" end
        -end
         
        -module BarUtils
        -  def bar(nombre) "#{nombre}bar" end
        -end
        +
        +
        +
        module FooUtils
        +  def foo(nombre) "#{nombre}foo" end
        +end
        +
        +module BarUtils
        +  def bar(nombre) "#{nombre}bar" end
        +end
        +
        +helpers FooUtils, BarUtils
        +
        +
        +
        -helpers FooUtils, BarUtils -

        El efecto de utilizar helpers de esta manera es el mismo que resulta de incluir los módulos en la clase de la aplicación.

        @@ -1084,78 +1378,144 @@

        Usando Sesiones

        Una sesión es usada para mantener el estado a través de distintas peticiones. Cuando están activadas, tenés un hash de sesión para cada sesión de usuario:

        -
        enable :sessions
         
        -get '/' do
        -  "valor = " << session[:valor].inspect
        -end
        +
        +
        +
        enable :sessions
        +
        +get '/' do
        +  "valor = " << session[:valor].inspect
        +end
        +
        +get '/:valor' do
        +  session[:valor] = params[:valor]
        +end
        +
        +
        +
        -get '/:valor' do - session[:valor] = params[:valor] -end -

        Tené en cuenta que enable :sessions guarda todos los datos en una cookie, lo que no es siempre deseable (guardar muchos datos va a incrementar tu tráfico, por citar un ejemplo). Podés usar cualquier middleware Rack para manejar sesiones, de la misma manera que usarías cualquier otro middleware, pero con la salvedad de que no tenés que llamar a enable :sessions:

        -
        use Rack::Session::Pool, :expire_after => 2592000
         
        -get '/' do
        -  "valor = " << session[:valor].inspect
        -end
        +
        +
        +
        use Rack::Session::Pool, :expire_after => 2592000
        +
        +get '/' do
        +  "valor = " << session[:valor].inspect
        +end
        +
        +get '/:valor' do
        +  session[:valor] = params[:valor]
        +end
        +
        +
        +
        -get '/:valor' do - session[:valor] = params[:valor] -end -

        Para incrementar la seguridad, los datos de la sesión almacenados en la cookie son firmados con un secreto de sesión. Este secreto, es generado aleatoriamente por Sinatra. De cualquier manera, hay que tener en cuenta que cada vez que inicies la aplicación se va a generar uno nuevo. Así, si querés que todas las instancias de tu aplicación compartan un único secreto, tenés que definirlo vos:

        -
        set :session_secret, 'super secreto'
        -
        + +
        +
        +
        set :session_secret, 'super secreto'
        +
        +
        +
        +

        Si necesitás una configuración más específica, sessions acepta un Hash con opciones:

        -
        set :sessions, :domain => 'foo.com'
        -
        + +
        +
        +
        set :sessions, :domain => 'foo.com'
        +
        +
        +
        +

        Interrupción

        Para detener inmediatamente una petición dentro de un filtro o una ruta usá:

        -
        halt
        -
        + +
        +
        +
        halt
        +
        +
        +
        +

        También podés especificar el estado:

        -
        halt 410
        -
        + +
        +
        +
        halt 410
        +
        +
        +
        +

        O el cuerpo:

        -
        halt 'esto va a ser el cuerpo'
        -
        + +
        +
        +
        halt 'esto va a ser el cuerpo'
        +
        +
        +
        +

        O los dos:

        -
        halt 401, 'salí de acá!'
        -
        + +
        +
        +
        halt 401, 'salí de acá!'
        +
        +
        +
        +

        Con cabeceras:

        -
        halt 402, { 'Content-Type' => 'text/plain' }, 'venganza'
        -
        + +
        +
        +
        halt 402, { 'Content-Type' => 'text/plain' }, 'venganza'
        +
        +
        +
        +

        Obviamente, es posible utilizar halt con una plantilla:

        -
        halt erb(:error)
        -
        + +
        +
        +
        halt erb(:error)
        +
        +
        +
        +

        Paso

        Una ruta puede pasarle el procesamiento a la siguiente ruta que coincida con la petición usando pass:

        -
        get '/adivina/:quien' do
        -  pass unless params[:quien] == 'Franco'
        -  'Adivinaste!'
        -end
        -
        -get '/adivina/*' do
        -  'Erraste!'
        -end
        -
        + +
        +
        +
        get '/adivina/:quien' do
        +  pass unless params[:quien] == 'Franco'
        +  'Adivinaste!'
        +end
        +
        +get '/adivina/*' do
        +  'Erraste!'
        +end
        +
        +
        +
        +

        Se sale inmediatamente del bloque de la ruta y se le pasa el control a la siguiente ruta que coincida. Si no coincide ninguna ruta, se devuelve un 404.

        @@ -1164,15 +1524,21 @@

        Ejecutando Otra Ruta

        Cuando querés obtener el resultado de la llamada a una ruta, pass no te va a servir. Para lograr esto, podés usar call:

        -
        get '/foo' do
        -  status, headers, body = call env.merge("PATH_INFO" => '/bar')
        -  [status, headers, body.map(&:upcase)]
        -end
        -
        -get '/bar' do
        -  "bar"
        -end
        -
        + +
        +
        +
        get '/foo' do
        +  status, headers, body = call env.merge("PATH_INFO" => '/bar')
        +  [status, headers, body.map(&:upcase)]
        +end
        +
        +get '/bar' do
        +  "bar"
        +end
        +
        +
        +
        +

        Notá que en el ejemplo anterior, es conveniente mover "bar" a un helper, y llamarlo desde /foo y /bar. Así, vas a simplificar las pruebas y a mejorar el rendimiento.

        @@ -1191,26 +1557,38 @@

        Asignando el Código de Estado, los Encabezados y el Cuerpo de una Respuesta escenarios, puede que sea conveniente asignar el cuerpo en un punto arbitrario del flujo de ejecución con el método body. A partir de ahí, podés usar ese mismo método para acceder al cuerpo de la respuesta:

        -
        get '/foo' do
        -  body "bar"
        -end
        -
        -after do
        -  puts body
        -end
        -
        + +
        +
        +
        get '/foo' do
        +  body "bar"
        +end
        +
        +after do
        +  puts body
        +end
        +
        +
        +
        +

        También es posible pasarle un bloque a body, que será ejecutado por el Rack -handler (podés usar esto para implementar streaming, mirá "Valores de retorno").

        +handler (podés usar esto para implementar streaming, mirá “Valores de retorno”).

        De manera similar, también podés asignar el código de estado y encabezados:

        -
        get '/foo' do
        -  status 418
        -  headers \
        -    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
        -    "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
        -  body "I'm a tea pot!"
        -end
        -
        + +
        +
        +
        get '/foo' do
        +  status 418
        +  headers \
        +    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
        +    "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
        +  body "I'm a tea pot!"
        +end
        +
        +
        +
        +

        También, al igual que body, tanto status como headers pueden utilizarse para obtener sus valores cuando no se les pasa argumentos.

        @@ -1221,16 +1599,22 @@

        Streaming De Respuestas

        terminaste de generar su cuerpo. También es posible que, en algunos casos, quieras seguir enviando información hasta que el cliente cierre la conexión. Cuando esto ocurra, el stream helper te va a ser de gran ayuda:

        -
        get '/' do
        -  stream do |out|
        -    out << "Esto va a ser legen -\n"
        -    sleep 0.5
        -    out << " (esperalo) \n"
        -    sleep 1
        -    out << "- dario!\n"
        -  end
        -end
        -
        + +
        +
        +
        get '/' do
        +  stream do |out|
        +    out << "Esto va a ser legen -\n"
        +    sleep 0.5
        +    out << " (esperalo) \n"
        +    sleep 1
        +    out << "- dario!\n"
        +  end
        +end
        +
        +
        +
        +

        Podés implementar APIs de streaming, Server-Sent Events y puede ser usado como base para WebSockets. También @@ -1249,30 +1633,42 @@

        Streaming De Respuestas

        que quieras. Nuevamente, hay que tener en cuenta que este comportamiento es posible solo en servidores que soporten eventos, como Thin o Rainbows. El resto de los servidores van a cerrar el stream de todos modos:

        -
        set :server, :thin
        -conexiones = []
        -
        -get '/' do
        -  # mantenemos abierto el stream
        -  stream(:keep_open) { |salida| conexiones << salida }
        -end
        -
        -post '/' do
        -  # escribimos a todos los streams abiertos
        -  conexiones.each { |salida| salida << params[:mensaje] << "\n" }
        -  "mensaje enviado"
        -end
        -
        + +
        +
        +
        set :server, :thin
        +conexiones = []
        +
        +get '/' do
        +  # mantenemos abierto el stream
        +  stream(:keep_open) { |salida| conexiones << salida }
        +end
        +
        +post '/' do
        +  # escribimos a todos los streams abiertos
        +  conexiones.each { |salida| salida << params[:mensaje] << "\n" }
        +  "mensaje enviado"
        +end
        +
        +
        +
        +

        Log (Registro)

        En el ámbito de la petición, el helper logger (registrador) expone una instancia de Logger:

        -
        get '/' do
        -  logger.info "cargando datos"
        -  # ...
        -end
        -
        + +
        +
        +
        get '/' do
        +  logger.info "cargando datos"
        +  # ...
        +end
        +
        +
        +
        +

        Este logger tiene en cuenta la configuración de logueo de tu Rack handler. Si el logueo está desactivado, este método va a devolver un objeto que se comporta como un logger pero que en realidad no hace @@ -1281,12 +1677,18 @@

        Log (Registro)

        Tené en cuenta que el logueo está habilitado por defecto únicamente para Sinatra::Application. Si heredaste de Sinatra::Base, probablemente quieras habilitarlo manualmente:

        -
        class MiApp < Sinatra::Base
        -  configure :production, :development do
        -    enable :logging
        -  end
        -end
        -
        + +
        +
        +
        class MiApp < Sinatra::Base
        +  configure :production, :development do
        +    enable :logging
        +  end
        +end
        +
        +
        +
        +

        Para evitar que se inicialice cualquier middleware de logging, configurá logging a nil. Tené en cuenta que, cuando hagas esto, logger va a devolver nil. Un caso común es cuando querés usar tu propio logger. Sinatra @@ -1298,22 +1700,40 @@

        Tipos Mime

        Cuando usás send_file o archivos estáticos tal vez tengas tipos mime que Sinatra no entiende. Usá mime_type para registrarlos a través de la extensión de archivo:

        -
        configure do
        -  mime_type :foo, 'text/foo'
        -end
        -
        + +
        +
        +
        configure do
        +  mime_type :foo, 'text/foo'
        +end
        +
        +
        +
        +

        También lo podés usar con el ayudante content_type:

        -
        get '/' do
        -  content_type :foo
        -  "foo foo foo"
        -end
        -
        + +
        +
        +
        get '/' do
        +  content_type :foo
        +  "foo foo foo"
        +end
        +
        +
        +
        +

        Generando URLs

        Para generar URLs deberías usar el método url. Por ejemplo, en Haml:

        -
        %a{:href => url('/foo')} foo
        -
        + +
        +
        +
        %a{:href => url('/foo')} foo
        +
        +
        +
        +

        Tiene en cuenta proxies inversos y encaminadores de Rack, si están presentes.

        Este método también puede invocarse mediante su alias to (mirá un ejemplo @@ -1323,42 +1743,72 @@

        Generando URLs

        Redirección del Navegador

        Podés redireccionar al navegador con el método redirect:

        -
        get '/foo' do
        -  redirect to('/bar')
        -end
        -
        + +
        +
        +
        get '/foo' do
        +  redirect to('/bar')
        +end
        +
        +
        +
        +

        Cualquier parámetro adicional se utiliza de la misma manera que los argumentos pasados a halt:

        -
        redirect to('/bar'), 303
        -redirect 'http://google.com', 'te confundiste de lugar, compañero'
        -
        + +
        +
        +
        redirect to('/bar'), 303
        +redirect 'http://google.com', 'te confundiste de lugar, compañero'
        +
        +
        +
        +

        También podés redireccionar fácilmente de vuelta hacia la página desde donde vino el usuario con redirect back:

        -
        get '/foo' do
        -  "<a href='/bar'>hacer algo</a>"
        -end
        -
        -get '/bar' do
        -  hacer_algo
        -  redirect back
        -end
        -
        + +
        +
        +
        get '/foo' do
        +  "<a href='/bar'>hacer algo</a>"
        +end
        +
        +get '/bar' do
        +  hacer_algo
        +  redirect back
        +end
        +
        +
        +
        +

        Para pasar argumentos con una redirección, podés agregarlos a la cadena de búsqueda:

        -
        redirect to('/bar?suma=42')
        -
        + +
        +
        +
        redirect to('/bar?suma=42')
        +
        +
        +
        +

        O usar una sesión:

        -
        enable :sessions
         
        -get '/foo' do
        -  session[:secreto] = 'foo'
        -  redirect to('/bar')
        -end
        +
        +
        +
        enable :sessions
        +
        +get '/foo' do
        +  session[:secreto] = 'foo'
        +  redirect to('/bar')
        +end
        +
        +get '/bar' do
        +  session[:secreto]
        +end
        +
        +
        +
        -get '/bar' do - session[:secreto] -end -

        Cache Control

        @@ -1366,52 +1816,88 @@

        Cache Control

        HTTP correcto.

        Podés asignar el encabezado Cache-Control fácilmente:

        -
        get '/' do
        -  cache_control :public
        -  "cachealo!"
        -end
        -
        + +
        +
        +
        get '/' do
        +  cache_control :public
        +  "cachealo!"
        +end
        +
        +
        +
        +

        Pro tip: configurar el cacheo en un filtro before:

        -
        before do
        -  cache_control :public, :must_revalidate, :max_age => 60
        -end
        -
        + +
        +
        +
        before do
        +  cache_control :public, :must_revalidate, :max_age => 60
        +end
        +
        +
        +
        +

        Si estás usando el helper expires para definir el encabezado correspondiente, Cache-Control se va a definir automáticamente:

        -
        before do
        -  expires 500, :public, :must_revalidate
        -end
        -
        + +
        +
        +
        before do
        +  expires 500, :public, :must_revalidate
        +end
        +
        +
        +
        +

        Para usar cachés adecuadamente, deberías considerar usar etag o last_modified. Es recomendable que llames a estos helpers antes de hacer cualquier trabajo pesado, ya que van a enviar la respuesta inmediatamente si el cliente ya tiene la versión actual en su caché:

        -
        get '/articulo/:id' do
        -  @articulo = Articulo.find params[:id]
        -  last_modified @articulo.updated_at
        -  etag @articulo.sha1
        -  erb :articulo
        -end
        -
        + +
        +
        +
        get '/articulo/:id' do
        +  @articulo = Articulo.find params[:id]
        +  last_modified @articulo.updated_at
        +  etag @articulo.sha1
        +  erb :articulo
        +end
        +
        +
        +
        +

        También es posible usar una weak ETag:

        -
        etag @articulo.sha1, :weak
        -
        + +
        +
        +
        etag @articulo.sha1, :weak
        +
        +
        +
        +

        Estos helpers no van a cachear nada por vos, sino que van a facilitar la información necesaria para poder hacerlo. Si estás buscando soluciones rápidas de cacheo con proxys inversos, mirá rack-cache:

        -
        require "rack/cache"
        -require "sinatra"
         
        -use Rack::Cache
        +
        +
        +
        require "rack/cache"
        +require "sinatra"
        +
        +use Rack::Cache
        +
        +get '/' do
        +  cache_control :public, :max_age => 36000
        +  sleep 5
        +  "hola"
        +end
        +
        +
        +
        -get '/' do - cache_control :public, :max_age => 36000 - sleep 5 - "hola" -end -

        Usá la configuración :static_cache_control para agregar el encabezado Cache-Control a archivos estáticos (ver la sección de configuración para más detalles).

        @@ -1422,26 +1908,50 @@

        Cache Control

        e idempotentes (como put) que el recurso existe, mientras que para el resto (como post), que no. Podes cambiar este comportamiento con la opción :new_resource:

        -
        get '/crear' do
        -  etag '', :new_resource => true
        -  Articulo.create
        -  erb :nuevo_articulo
        -end
        -
        + +
        +
        +
        get '/crear' do
        +  etag '', :new_resource => true
        +  Articulo.create
        +  erb :nuevo_articulo
        +end
        +
        +
        +
        +

        Si querés seguir usando una weak ETag, indicalo con la opción :kind:

        -
        etag '', :new_resource => true, :kind => :weak
        -
        + +
        +
        +
        etag '', :new_resource => true, :kind => :weak
        +
        +
        +
        +

        Enviando Archivos

        Para enviar archivos, podés usar el método send_file:

        -
        get '/' do
        -  send_file 'foo.png'
        -end
        -
        + +
        +
        +
        get '/' do
        +  send_file 'foo.png'
        +end
        +
        +
        +
        +

        Además acepta un par de opciones:

        -
        send_file 'foo.png', :type => :jpg
        -
        + +
        +
        +
        send_file 'foo.png', :type => :jpg
        +
        +
        +
        +

        Estas opciones son:

        [filename] @@ -1476,68 +1986,98 @@

        Accediendo al objeto de la petición

        El objeto de la petición entrante puede ser accedido desde el nivel de la petición (filtros, rutas y manejadores de errores) a través del método request:

        -
        # app corriendo en http://ejemplo.com/ejemplo
        -get '/foo' do
        -  t = %w[text/css text/html application/javascript]
        -  request.accept              # ['text/html', '*/*']
        -  request.accept? 'text/xml'  # true
        -  request.preferred_type(t)   # 'text/html'
        -  request.body                # cuerpo de la petición enviado por el cliente (ver más abajo)
        -  request.scheme              # "http"
        -  request.script_name         # "/ejemplo"
        -  request.path_info           # "/foo"
        -  request.port                # 80
        -  request.request_method      # "GET"
        -  request.query_string        # ""
        -  request.content_length      # longitud de request.body
        -  request.media_type          # tipo de medio de request.body
        -  request.host                # "ejemplo.com"
        -  request.get?                # true (hay métodos análogos para los otros verbos)
        -  request.form_data?          # false
        -  request["UNA_CABECERA"]     # valor de la cabecera UNA_CABECERA
        -  request.referrer            # la referencia del cliente o '/'
        -  request.user_agent          # user agent (usado por la condición :agent)
        -  request.cookies             # hash de las cookies del browser
        -  request.xhr?                # es una petición ajax?
        -  request.url                 # "http://ejemplo.com/ejemplo/foo"
        -  request.path                # "/ejemplo/foo"
        -  request.ip                  # dirección IP del cliente
        -  request.secure?             # false (sería true sobre ssl)
        -  request.forwarded?          # true (si se está corriendo atrás de un proxy inverso)
        -  requuest.env                # hash de entorno directamente entregado por Rack
        -end
        -
        + +
        +
        +
        # app corriendo en http://ejemplo.com/ejemplo
        +get '/foo' do
        +  t = %w[text/css text/html application/javascript]
        +  request.accept              # ['text/html', '*/*']
        +  request.accept? 'text/xml'  # true
        +  request.preferred_type(t)   # 'text/html'
        +  request.body                # cuerpo de la petición enviado por el cliente (ver más abajo)
        +  request.scheme              # "http"
        +  request.script_name         # "/ejemplo"
        +  request.path_info           # "/foo"
        +  request.port                # 80
        +  request.request_method      # "GET"
        +  request.query_string        # ""
        +  request.content_length      # longitud de request.body
        +  request.media_type          # tipo de medio de request.body
        +  request.host                # "ejemplo.com"
        +  request.get?                # true (hay métodos análogos para los otros verbos)
        +  request.form_data?          # false
        +  request["UNA_CABECERA"]     # valor de la cabecera UNA_CABECERA
        +  request.referrer            # la referencia del cliente o '/'
        +  request.user_agent          # user agent (usado por la condición :agent)
        +  request.cookies             # hash de las cookies del browser
        +  request.xhr?                # es una petición ajax?
        +  request.url                 # "http://ejemplo.com/ejemplo/foo"
        +  request.path                # "/ejemplo/foo"
        +  request.ip                  # dirección IP del cliente
        +  request.secure?             # false (sería true sobre ssl)
        +  request.forwarded?          # true (si se está corriendo atrás de un proxy inverso)
        +  requuest.env                # hash de entorno directamente entregado por Rack
        +end
        +
        +
        +
        +

        Algunas opciones, como script_name o path_info pueden también ser escritas:

        -
        before { request.path_info = "/" }
         
        -get "/" do
        -  "todas las peticiones llegan acá"
        -end
        -
        +
        +
        +
        before { request.path_info = "/" }
        +
        +get "/" do
        +  "todas las peticiones llegan acá"
        +end
        +
        +
        +
        +

        El objeto request.body es una instancia de IO o StringIO:

        -
        post "/api" do
        -  request.body.rewind  # en caso de que alguien ya lo haya leído
        -  datos = JSON.parse request.body.read
        -  "Hola #{datos['nombre']}!"
        -end
        -
        + +
        +
        +
        post "/api" do
        +  request.body.rewind  # en caso de que alguien ya lo haya leído
        +  datos = JSON.parse request.body.read
        +  "Hola #{datos['nombre']}!"
        +end
        +
        +
        +
        +

        Archivos Adjuntos

        Podés usar el método helper attachment para indicarle al navegador que almacene la respuesta en el disco en lugar de mostrarla en pantalla:

        -
        get '/' do
        -  attachment
        -  "guardalo!"
        -end
        -
        + +
        +
        +
        get '/' do
        +  attachment
        +  "guardalo!"
        +end
        +
        +
        +
        +

        También podés pasarle un nombre de archivo:

        -
        get '/' do
        -  attachment "info.txt"
        -  "guardalo!"
        -end
        -
        + +
        +
        +
        get '/' do
        +  attachment "info.txt"
        +  "guardalo!"
        +end
        +
        +
        +
        +

        Fecha y Hora

        @@ -1545,62 +2085,92 @@

        Fecha y Hora

        a partir del valor que recibe como argumento. Este valor puede ser un String, pero también es capaz de convertir objetos DateTime, Date y de otras clases similares:

        -
        get '/' do
        -  pass if Time.now > time_for('Dec 23, 2012')
        -  "todavía hay tiempo"
        -end
        -
        + +
        +
        +
        get '/' do
        +  pass if Time.now > time_for('Dec 23, 2012')
        +  "todavía hay tiempo"
        +end
        +
        +
        +
        +

        Este método es usado internamente por métodos como expires y last_modified, entre otros. Por lo tanto, es posible extender el comportamiento de estos métodos sobreescribiendo time_for en tu aplicación:

        -
        helpers do
        -  def time_for(value)
        -    case value
        -    when :ayer then Time.now - 24*60*60
        -    when :mañana then Time.now + 24*60*60
        -    else super
        -    end
        -  end
        -end
        -
        -get '/' do
        -  last_modified :ayer
        -  expires :mañana
        -  "hola"
        -end
        -
        + +
        +
        +
        helpers do
        +  def time_for(value)
        +    case value
        +    when :ayer then Time.now - 24*60*60
        +    when :mañana then Time.now + 24*60*60
        +    else super
        +    end
        +  end
        +end
        +
        +get '/' do
        +  last_modified :ayer
        +  expires :mañana
        +  "hola"
        +end
        +
        +
        +
        +

        Buscando los Archivos de las Plantillas

        El helper find_template se utiliza para encontrar los archivos de las plantillas que se van a renderizar:

        -
        find_template settings.views, 'foo', Tilt[:haml] do |archivo|
        -  puts "podría ser #{archivo}"
        -end
        -
        + +
        +
        +
        find_template settings.views, 'foo', Tilt[:haml] do |archivo|
        +  puts "podría ser #{archivo}"
        +end
        +
        +
        +
        +

        Si bien esto no es muy útil, lo interesante es que podés sobreescribir este método, y así enganchar tu propio mecanismo de búsqueda. Por ejemplo, para poder utilizar más de un directorio de vistas:

        -
        set :views, ['vistas', 'plantillas']
        -
        -helpers do
        -  def find_template(views, name, engine, &block)
        -    Array(views).each { |v| super(v, name, engine, &block) }
        -  end
        -end
        -
        + +
        +
        +
        set :views, ['vistas', 'plantillas']
        +
        +helpers do
        +  def find_template(views, name, engine, &block)
        +    Array(views).each { |v| super(v, name, engine, &block) }
        +  end
        +end
        +
        +
        +
        +

        Otro ejemplo consiste en usar directorios diferentes para los distintos motores de renderizado:

        -
        set :views, :sass => 'vistas/sass', :haml => 'plantillas', :defecto => 'vistas'
        -
        -helpers do
        -  def find_template(views, name, engine, &block)
        -    _, folder = views.detect { |k,v| engine == Tilt[k] }
        -    folder ||= views[:defecto]
        -    super(folder, name, engine, &block)
        -  end
        -end
        -
        + +
        +
        +
        set :views, :sass => 'vistas/sass', :haml => 'plantillas', :defecto => 'vistas'
        +
        +helpers do
        +  def find_template(views, name, engine, &block)
        +    _, folder = views.detect { |k,v| engine == Tilt[k] }
        +    folder ||= views[:defecto]
        +    super(folder, name, engine, &block)
        +  end
        +end
        +
        +
        +
        +

        ¡Es muy fácil convertir estos ejemplos en una extensión y compartirla!.

        Notá que find_template no verifica si un archivo existe realmente, sino @@ -1614,45 +2184,69 @@

        Buscando los Archivos de las Plantillas

        Configuración

        Ejecutar una vez, en el inicio, en cualquier entorno:

        -
        configure do
        -  # asignando una opción
        -  set :opcion, 'valor'
         
        -  # asignando varias opciones
        -  set :a => 1, :b => 2
        +
        +
        +
        configure do
        +  # asignando una opción
        +  set :opcion, 'valor'
         
        -  # atajo para `set :opcion, true`
        -  enable :opcion
        +  # asignando varias opciones
        +  set :a => 1, :b => 2
         
        -  # atajo para `set :opcion, false`
        -  disable :opcion
        +  # atajo para `set :opcion, true`
        +  enable :opcion
        +
        +  # atajo para `set :opcion, false`
        +  disable :opcion
        +
        +  # también podés tener configuraciones dinámicas usando bloques
        +  set(:css_dir) { File.join(views, 'css') }
        +end
        +
        +
        +
        - # también podés tener configuraciones dinámicas usando bloques - set(:css_dir) { File.join(views, 'css') } -end -

        Ejecutar únicamente cuando el entorno (la variable de entorno RACK_ENV) es :production:

        -
        configure :production do
        -  ...
        -end
        -
        + +
        +
        +
        configure :production do
        +  ...
        +end
        +
        +
        +
        +

        Ejecutar cuando el entorno es :production o :test:

        -
        configure :production, :test do
        -  ...
        -end
        -
        + +
        +
        +
        configure :production, :test do
        +  ...
        +end
        +
        +
        +
        +

        Podés acceder a estas opciones utilizando el método settings:

        -
        configure do
        -  set :foo, 'bar'
        -end
        -
        -get '/' do
        -  settings.foo? # => true
        -  settings.foo  # => 'bar'
        -  ...
        -end
        -
        + +
        +
        +
        configure do
        +  set :foo, 'bar'
        +end
        +
        +get '/' do
        +  settings.foo? # => true
        +  settings.foo  # => 'bar'
        +  ...
        +end
        +
        +
        +
        +

        Configurando la Protección de Ataques

        @@ -1661,14 +2255,32 @@

        Configurando la Protección de Ataques

        querés desactivar esta funcionalidad, podés hacerlo como se indica a continuación (tené en cuenta que tu aplicación va a quedar expuesta a un montón de vulnerabilidades bien conocidas):

        -
        disable :protection
        -
        + +
        +
        +
        disable :protection
        +
        +
        +
        +

        También es posible desactivar una única capa de defensa:

        -
        set :protection, :except => :path_traversal
        -
        + +
        +
        +
        set :protection, :except => :path_traversal
        +
        +
        +
        +

        O varias:

        -
        set :protection, :except => [:path_traversal, :session_hijacking]
        -
        + +
        +
        +
        set :protection, :except => [:path_traversal, :session_hijacking]
        +
        +
        +
        +

        Configuraciones Disponibles

        @@ -1884,14 +2496,20 @@

        Entornos

        • Se recargan las plantillas entre una petición y la siguiente, a diferencia de production y test, donde se cachean.
        • -
        • Se instalan manejadores de errores not_found y error +
        • Se instalan manejadores de errores not_found y error especiales que muestran un stack trace en el navegador cuando son disparados.

        Para utilizar alguno de los otros entornos puede asignarse el valor correspondiente a la variable de entorno RACK_ENV, o bien utilizar la opción -e al ejecutar la aplicación:

        -
        ruby mi_app.rb -e <ENTORNO>
        -
        + +
        +
        +
        ruby mi_app.rb -e <ENTORNO>
        +
        +
        +
        +

        Los métodos development?, test? y production? te permiten conocer el entorno actual.

        @@ -1907,50 +2525,86 @@

        No encontrado (Not Found)

        Cuando se eleva una excepción Sinatra::NotFound, o el código de estado de la respuesta es 404, el manejador not_found es invocado:

        -
        not_found do
        -  'No existo'
        -end
        -
        + +
        +
        +
        not_found do
        +  'No existo'
        +end
        +
        +
        +
        +

        Error

        El manejador error es invocado cada vez que una excepción es elevada desde un bloque de ruta o un filtro. El objeto de la excepción se puede obtener de la variable Rack sinatra.error:

        -
        error do
        -  'Disculpá, ocurrió un error horrible - ' + env['sinatra.error'].name
        -end
        -
        + +
        +
        +
        error do
        +  'Disculpá, ocurrió un error horrible - ' + env['sinatra.error'].name
        +end
        +
        +
        +
        +

        Errores personalizados:

        -
        error MiErrorPersonalizado do
        -  'Lo que pasó fue...' + env['sinatra.error'].message
        -end
        -
        + +
        +
        +
        error MiErrorPersonalizado do
        +  'Lo que pasó fue...' + env['sinatra.error'].message
        +end
        +
        +
        +
        +

        Entonces, si pasa esto:

        -
        get '/' do
        -  raise MiErrorPersonalizado, 'algo malo'
        -end
        -
        + +
        +
        +
        get '/' do
        +  raise MiErrorPersonalizado, 'algo malo'
        +end
        +
        +
        +
        +

        Obtenés esto:

        -

        Lo que pasó fue... algo malo

        +

        Lo que pasó fue… algo malo

        También, podés instalar un manejador de errores para un código de estado:

        -
        error 403 do
        -  'Acceso prohibido'
        -end
        -
        -get '/secreto' do
        -  403
        -end
        -
        + +
        +
        +
        error 403 do
        +  'Acceso prohibido'
        +end
        +
        +get '/secreto' do
        +  403
        +end
        +
        +
        +
        +

        O un rango:

        -
        error 400..510 do
        -  'Boom'
        -end
        -
        + +
        +
        +
        error 400..510 do
        +  'Boom'
        +end
        +
        +
        +
        +

        Sinatra instala manejadores not_found y error especiales -cuando se ejecuta dentro del entorno de desarrollo "development".

        +cuando se ejecuta dentro del entorno de desarrollo “development”.

        Rack Middleware

        @@ -1958,30 +2612,42 @@

        Rack Middleware

        Sinatra corre sobre Rack[http://rack.rubyforge.org/], una interfaz minimalista que es un estándar para frameworks webs escritos en Ruby. Una de las capacidades más interesantes de Rack para los desarrolladores de aplicaciones -es el soporte de "middleware" -- componentes que se ubican entre el servidor y +es el soporte de “middleware” – componentes que se ubican entre el servidor y tu aplicación, supervisando y/o manipulando la petición/respuesta HTTP para proporcionar varios tipos de funcionalidades comunes.

        Sinatra hace muy sencillo construir tuberías de Rack middleware a través del método top-level use:

        -
        require 'sinatra'
        -require 'mi_middleware_personalizado'
         
        -use Rack::Lint
        -use MiMiddlewarePersonalizado
        +
        +
        +
        require 'sinatra'
        +require 'mi_middleware_personalizado'
        +
        +use Rack::Lint
        +use MiMiddlewarePersonalizado
        +
        +get '/hola' do
        +  'Hola Mundo'
        +end
        +
        +
        +
        -get '/hola' do - 'Hola Mundo' -end -

        Las semánticas de use son idénticas a las definidas para el DSL Rack::Builder[http://rack.rubyforge.org/doc/classes/Rack/Builder.html] (más frecuentemente usado desde archivos rackup). Por ejemplo, el método use acepta argumentos múltiples/variables así como bloques:

        -
        use Rack::Auth::Basic do |nombre_de_usuario, password|
        -  nombre_de_usuario == 'admin' && password == 'secreto'
        -end
        -
        + +
        +
        +
        use Rack::Auth::Basic do |nombre_de_usuario, password|
        +  nombre_de_usuario == 'admin' && password == 'secreto'
        +end
        +
        +
        +
        +

        Rack es distribuido con una variedad de middleware estándar para logging, debugging, enrutamiento URL, autenticación, y manejo de sesiones. Sinatra usa muchos de estos componentes automáticamente de acuerdo a su configuración @@ -1999,33 +2665,39 @@

        Pruebas

        Las pruebas para las aplicaciones Sinatra pueden ser escritas utilizando cualquier framework o librería de pruebas basada en Rack. Se recomienda usar Rack::Test:

        -
        require 'mi_app_sinatra'
        -require 'test/unit'
        -require 'rack/test'
        -
        -class MiAppTest < Test::Unit::TestCase
        -  include Rack::Test::Methods
        -
        -  def app
        -    Sinatra::Application
        -  end
        -
        -  def test_mi_defecto
        -    get '/'
        -    assert_equal 'Hola Mundo!', last_response.body
        -  end
        -
        -  def test_con_parametros
        -    get '/saludar', :name => 'Franco'
        -    assert_equal 'Hola Frank!', last_response.body
        -  end
        -
        -  def test_con_entorno_rack
        -    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
        -    assert_equal "Estás usando Songbird!", last_response.body
        -  end
        -end
        -
        + +
        +
        +
        require 'mi_app_sinatra'
        +require 'test/unit'
        +require 'rack/test'
        +
        +class MiAppTest < Test::Unit::TestCase
        +  include Rack::Test::Methods
        +
        +  def app
        +    Sinatra::Application
        +  end
        +
        +  def test_mi_defecto
        +    get '/'
        +    assert_equal 'Hola Mundo!', last_response.body
        +  end
        +
        +  def test_con_parametros
        +    get '/saludar', :name => 'Franco'
        +    assert_equal 'Hola Frank!', last_response.body
        +  end
        +
        +  def test_con_entorno_rack
        +    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
        +    assert_equal "Estás usando Songbird!", last_response.body
        +  end
        +end
        +
        +
        +
        +

        Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares

        @@ -2037,17 +2709,23 @@

        Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares

        único archivo de aplicación, los directorios ./public y ./views, logging, página con detalles de excepción, etc.). Ahí es donde Sinatra::Base entra en el juego:

        -
        require 'sinatra/base'
         
        -class MiApp < Sinatra::Base
        -  set :sessions, true
        -  set :foo, 'bar'
        +
        +
        +
        require 'sinatra/base'
        +
        +class MiApp < Sinatra::Base
        +  set :sessions, true
        +  set :foo, 'bar'
        +
        +  get '/' do
        +    'Hola Mundo!'
        +  end
        +end
        +
        +
        +
        - get '/' do - 'Hola Mundo!' - end -end -

        Las subclases de Sinatra::Base tienen disponibles exactamente los mismos métodos que los provistos por el DSL de top-level. La mayoría de las aplicaciones top-level se pueden convertir en componentes @@ -2057,7 +2735,7 @@

        Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares

      2. Tu archivo debe requerir sinatra/base en lugar de sinatra; de otra manera, todos los métodos del DSL de sinatra son importados dentro del espacio de nombres principal.
      3. -
      4. Poné las rutas, manejadores de errores, filtros y opciones de tu aplicación +
      5. Poné las rutas, manejadores de errores, filtros y opciones de tu aplicación en una subclase de Sinatra::Base.
      6. Sinatra::Base es una pizarra en blanco. La mayoría de las opciones están @@ -2122,42 +2800,78 @@

        Sirviendo una Aplicación Modular

        Las dos opciones más comunes para iniciar una aplicación modular son, iniciarla activamente con run!:

        -
        # mi_app.rb
        -require 'sinatra/base'
         
        -class MiApp < Sinatra::Base
        -  # ... código de la app  ...
        +
        +
        +
        # mi_app.rb
        +require 'sinatra/base'
        +
        +class MiApp < Sinatra::Base
        +  # ... código de la app  ...
        +
        +  # iniciar el servidor si el archivo fue ejecutado directamente
        +  run! if app_file == $0
        +end
        +
        +
        +
        - # iniciar el servidor si el archivo fue ejecutado directamente - run! if app_file == $0 -end -

        Iniciar con:

        -
        ruby mi_app.rb
        -
        + +
        +
        +
        ruby mi_app.rb
        +
        +
        +
        +

        O, con un archivo config.ru, que permite usar cualquier handler Rack:

        -
        # config.ru
        -require './mi_app'
        -run MiApp
        -
        + +
        +
        +
        # config.ru
        +require './mi_app'
        +run MiApp
        +
        +
        +
        +

        Después ejecutar:

        -
        rackup -p 4567
        -
        + +
        +
        +
        rackup -p 4567
        +
        +
        +
        +

        Usando una Aplicación Clásica con un Archivo config.ru

        Escribí el archivo de tu aplicación:

        -
        # app.rb
        -require 'sinatra'
         
        -get '/' do
        -  'Hola mundo!'
        -end
        -
        +
        +
        +
        # app.rb
        +require 'sinatra'
        +
        +get '/' do
        +  'Hola mundo!'
        +end
        +
        +
        +
        +

        Y el config.ru correspondiente:

        -
        require './app'
        -run Sinatra::Application
        -
        + +
        +
        +
        require './app'
        +run Sinatra::Application
        +
        +
        +
        +

        ¿Cuándo Usar config.ru?

        @@ -2165,9 +2879,9 @@

        ¿Cuándo Usar config.ru?

        • Querés realizar el deploy con un hanlder Rack distinto (Passenger, Unicorn, -Heroku, ...).
        • -
        • Querés usar más de una subclase de Sinatra::Base.
        • -
        • Querés usar Sinatra únicamente para middleware, pero no como un endpoint.
        • +Heroku, …). +
        • Querés usar más de una subclase de Sinatra::Base.
        • +
        • Querés usar Sinatra únicamente para middleware, pero no como un endpoint.

        No hay necesidad de utilizar un archivo config.ru exclusivamente porque tenés una aplicación modular, y no necesitás una aplicación modular para @@ -2179,77 +2893,101 @@

        Utilizando Sinatra como Middleware

        Sinatra no solo es capaz de usar otro Rack middleware, sino que a su vez, cualquier aplicación Sinatra puede ser agregada delante de un endpoint Rack como middleware. Este endpoint puede ser otra aplicación Sinatra, o cualquier -aplicación basada en Rack (Rails/Ramaze/Camping/...):

        -
        require 'sinatra/base'
        -
        -class PantallaDeLogin < Sinatra::Base
        -  enable :sessions
        -
        -  get('/login') { haml :login }
        -
        -  post('/login') do
        -    if params[:nombre] == 'admin' && params[:password] == 'admin'
        -      session['nombre_de_usuario'] = params[:nombre]
        -    else
        -      redirect '/login'
        -    end
        -  end
        -end
        -
        -class MiApp < Sinatra::Base
        -  # el middleware se ejecutará antes que los filtros
        -  use PantallaDeLogin
        -
        -  before do
        -    unless session['nombre_de_usuario']
        -      halt "Acceso denegado, por favor <a href='/login'>iniciá sesión</a>."
        -    end
        -  end
        -
        -  get('/') { "Hola #{session['nombre_de_usuario']}." }
        -end
        -
        +aplicación basada en Rack (Rails/Ramaze/Camping/…):

        + +
        +
        +
        require 'sinatra/base'
        +
        +class PantallaDeLogin < Sinatra::Base
        +  enable :sessions
        +
        +  get('/login') { haml :login }
        +
        +  post('/login') do
        +    if params[:nombre] == 'admin' && params[:password] == 'admin'
        +      session['nombre_de_usuario'] = params[:nombre]
        +    else
        +      redirect '/login'
        +    end
        +  end
        +end
        +
        +class MiApp < Sinatra::Base
        +  # el middleware se ejecutará antes que los filtros
        +  use PantallaDeLogin
        +
        +  before do
        +    unless session['nombre_de_usuario']
        +      halt "Acceso denegado, por favor <a href='/login'>iniciá sesión</a>."
        +    end
        +  end
        +
        +  get('/') { "Hola #{session['nombre_de_usuario']}." }
        +end
        +
        +
        +
        +

        Creación Dinámica de Aplicaciones

        Puede que en algunas ocasiones quieras crear nuevas aplicaciones en tiempo de ejecución sin tener que asignarlas a una constante. Para esto tenés Sinatra.new:

        -
        require 'sinatra/base'
        -mi_app = Sinatra.new { get('/') { "hola" } }
        -mi_app.run!
        -
        + +
        +
        +
        require 'sinatra/base'
        +mi_app = Sinatra.new { get('/') { "hola" } }
        +mi_app.run!
        +
        +
        +
        +

        Acepta como argumento opcional una aplicación desde la que se heredará:

        -
        # config.ru
        -require 'sinatra/base'
        -
        -controller = Sinatra.new do
        -  enable :logging
        -  helpers MisHelpers
        -end
        -
        -map('/a') do
        -  run Sinatra.new(controller) { get('/') { 'a' } }
        -end
        -
        -map('/b') do
        -  run Sinatra.new(controller) { get('/') { 'b' } }
        -end
        -
        + +
        +
        +
        # config.ru
        +require 'sinatra/base'
        +
        +controller = Sinatra.new do
        +  enable :logging
        +  helpers MisHelpers
        +end
        +
        +map('/a') do
        +  run Sinatra.new(controller) { get('/') { 'a' } }
        +end
        +
        +map('/b') do
        +  run Sinatra.new(controller) { get('/') { 'b' } }
        +end
        +
        +
        +
        +

        Construir aplicaciones de esta forma resulta especialmente útil para testear extensiones Sinatra o para usar Sinatra en tus librerías.

        Por otro lado, hace extremadamente sencillo usar Sinatra como middleware:

        -
        require 'sinatra/base'
         
        -use Sinatra do
        -  get('/') { ... }
        -end
        +
        +
        +
        require 'sinatra/base'
        +
        +use Sinatra do
        +  get('/') { ... }
        +end
        +
        +run ProyectoRails::Application
        +
        +
        +
        -run ProyectoRails::Application -

        Ámbitos y Ligaduras

        @@ -2267,31 +3005,37 @@

        Ámbito de Aplicación/Clase

        clase de la aplicación para todas las peticiones.

        Las opciones creadas utilizando set son métodos al nivel de la clase:

        -
        class MiApp < Sinatra::Base
        -  # Ey, estoy en el ámbito de la aplicación!
        -  set :foo, 42
        -  foo # => 42
        -
        -  get '/foo' do
        -    # Hey, ya no estoy en el ámbito de la aplicación!
        -  end
        -end
        -
        + +
        +
        +
        class MiApp < Sinatra::Base
        +  # Ey, estoy en el ámbito de la aplicación!
        +  set :foo, 42
        +  foo # => 42
        +
        +  get '/foo' do
        +    # Hey, ya no estoy en el ámbito de la aplicación!
        +  end
        +end
        +
        +
        +
        +

        Tenés la ligadura al ámbito de la aplicación dentro de:

        • El cuerpo de la clase de tu aplicación
        • -
        • Métodos definidos por extensiones
        • -
        • El bloque pasado a helpers +
        • Métodos definidos por extensiones
        • +
        • El bloque pasado a helpers
        • -
        • Procs/bloques usados como el valor para set +
        • Procs/bloques usados como el valor para set

        Este ámbito puede alcanzarse de las siguientes maneras:

        • A través del objeto pasado a los bloques de configuración (configure { |c| ...})
        • -
        • Llamando a settings desde dentro del ámbito de la petición
        • +
        • Llamando a settings desde dentro del ámbito de la petición

        Ámbito de Petición/Instancia

        @@ -2301,28 +3045,34 @@

        Ámbito de Petición/Instancia

        ámbito podés acceder a los objetos request y session o llamar a los métodos de renderización como erb o haml. Podés acceder al ámbito de la aplicación desde el ámbito de la petición utilizando settings:

        -
        class MiApp < Sinatra::Base
        -  # Ey, estoy en el ámbito de la aplicación!
        -  get '/definir_ruta/:nombre' do
        -    # Ámbito de petición para '/definir_ruta/:nombre'
        -    @valor = 42
        -
        -    settings.get("/#{params[:nombre]}") do
        -      # Ámbito de petición para "/#{params[:nombre]}"
        -      @valor # => nil (no es la misma petición)
        -    end
        -
        -    "Ruta definida!"
        -  end
        -end
        -
        + +
        +
        +
        class MiApp < Sinatra::Base
        +  # Ey, estoy en el ámbito de la aplicación!
        +  get '/definir_ruta/:nombre' do
        +    # Ámbito de petición para '/definir_ruta/:nombre'
        +    @valor = 42
        +
        +    settings.get("/#{params[:nombre]}") do
        +      # Ámbito de petición para "/#{params[:nombre]}"
        +      @valor # => nil (no es la misma petición)
        +    end
        +
        +    "Ruta definida!"
        +  end
        +end
        +
        +
        +
        +

        Tenés la ligadura al ámbito de la petición dentro de:

        • bloques pasados a get/head/post/put/delete/options
        • -
        • filtros before/after
        • -
        • métodos ayudantes
        • -
        • plantillas/vistas
        • +
        • filtros before/after
        • +
        • métodos ayudantes
        • +
        • plantillas/vistas

        Ámbito de Delegación

        @@ -2339,7 +3089,7 @@

        Ámbito de Delegación

        • La ligadura del top-level, si hiciste require "sinatra"
        • -
        • Un objeto extendido con el mixin Sinatra::Delegator +
        • Un objeto extendido con el mixin Sinatra::Delegator

        Pegale una mirada al código: acá está el @@ -2350,16 +3100,24 @@

        Ámbito de Delegación

        Línea de Comandos

        Las aplicaciones Sinatra pueden ser ejecutadas directamente:

        -
        ruby miapp.rb [-h] [-x] [-e ENTORNO] [-p PUERTO] [-o HOST] [-s MANEJADOR]
        -
        + +
        +
        +
        ruby miapp.rb [-h] [-x] [-e ENTORNO] [-p PUERTO] [-o HOST] [-s MANEJADOR]
        +
        +
        +
        +

        Las opciones son:

        -
        -h # ayuda
        +
        +
        -h # ayuda
         -p # asigna el puerto (4567 es usado por defecto)
         -o # asigna el host (0.0.0.0 es usado por defecto)
         -e # asigna el entorno (development es usado por defecto)
         -s # especifica el servidor/manejador rack (thin es usado por defecto)
         -x # activa el mutex lock (está desactivado por defecto)
        -
        +
        +

        Versiones de Ruby Soportadas

        @@ -2417,9 +3175,9 @@

        Versiones de Ruby Soportadas

        • Versiones anteriores de JRuby y Rubinius
        • -
        • Ruby Enterprise Edition
        • -
        • MacRuby, Maglev e IronRuby
        • -
        • Ruby 1.9.0 y 1.9.1 (pero no te recomendamos que los usés)
        • +
        • Ruby Enterprise Edition
        • +
        • MacRuby, Maglev e IronRuby
        • +
        • Ruby 1.9.0 y 1.9.1 (pero no te recomendamos que los usés)

        No estar soportada oficialmente, significa que si las cosas solamente se rompen ahí y no en una plataforma soportada, asumimos que no es nuestro problema sino @@ -2443,8 +3201,14 @@

        A la Vanguardia

        tu aplicación sobre la rama master, en general es bastante estable.

        También liberamos prereleases de vez en cuando, así, podés hacer

        -
        gem install sinatra --pre
        -
        + +
        +
        +
        gem install sinatra --pre
        +
        +
        +
        +

        Para obtener algunas de las últimas características.

        @@ -2454,48 +3218,90 @@

        Con Bundler

        versión de Sinatra usando Bundler.

        Primero, instalá bundler si no lo hiciste todavía:

        -
        gem install bundler
        -
        + +
        +
        +
        gem install bundler
        +
        +
        +
        +

        Después, en el directorio de tu proyecto, creá un archivo Gemfile:

        -
        source :rubygems
        -gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
         
        -# otras dependencias
        -gem 'haml'                    # por ejemplo, si usás haml
        -gem 'activerecord', '~> 3.0'  # quizás también necesités ActiveRecord 3.x
        -
        +
        +
        +
        source :rubygems
        +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
        +
        +# otras dependencias
        +gem 'haml'                    # por ejemplo, si usás haml
        +gem 'activerecord', '~> 3.0'  # quizás también necesités ActiveRecord 3.x
        +
        +
        +
        +

        Tené en cuenta que tenés que listar todas las dependencias directas de tu aplicación. No es necesario listar las dependencias de Sinatra (Rack y Tilt) porque Bundler las agrega directamente.

        Ahora podés arrancar tu aplicación así:

        -
        bundle exec ruby miapp.rb
        -
        + +
        +
        +
        bundle exec ruby miapp.rb
        +
        +
        +
        +

        Con Git

        Cloná el repositorio localmente y ejecutá tu aplicación, asegurándote que el directorio sinatra/lib esté en el $LOAD_PATH:

        -
        cd miapp
        +
        +
        +
        +
        cd miapp
         git clone git://github.com/sinatra/sinatra.git
         ruby -Isinatra/lib miapp.rb
        -
        +
        + + +

        Para actualizar el código fuente de Sinatra en el futuro:

        -
        cd miapp/sinatra
        +
        +
        +
        +
        cd miapp/sinatra
         git pull
        -
        +
        + + +

        Instalación Global

        Podés construir la gem vos mismo:

        -
        git clone git://github.com/sinatra/sinatra.git
        -cd sinatra
        +
        +
        +
        +
        git clone git://github.com/sinatra/sinatra.git
        +cd sinatra
         rake sinatra.gemspec
         rake install
        -
        +
        + + +

        Si instalás tus gems como root, el último paso debería ser

        -
        sudo rake install
        -
        + +
        +
        +
        sudo rake install
        +
        +
        +
        +

        Versionado

        @@ -2509,22 +3315,23 @@

        Lecturas Recomendadas

      7. Sito web del proyecto - Documentación adicional, noticias, y enlaces a otros recursos.
      8. -
      9. +
      10. Contribuyendo - ¿Encontraste un error?. ¿Necesitás ayuda?. ¿Tenés un parche?.
      11. -
      12. Seguimiento de problemas
      13. -
      14. Twitter
      15. -
      16. Lista de Correo
      17. -
      18. [IRC: #sinatra](irc://chat.freenode.net/#sinatra) en http://freenode.net
      19. -
      20. +
      21. Seguimiento de problemas
      22. +
      23. Twitter
      24. +
      25. Lista de Correo
      26. +
      27. +IRC: #sinatra en http://freenode.net
      28. +
      29. Sinatra Book Tutorial (en inglés).
      30. -
      31. +
      32. Sinatra Recipes Recetas contribuidas por la comunidad (en inglés).
      33. -
      34. Documentación de la API para la +
      35. Documentación de la API para la última versión liberada o para la rama de desarrollo actual en http://rubydoc.info/
      36. -
      37. Servidor de CI
      38. +
      39. Servidor de CI
      40. diff --git a/_includes/README.fr.html b/_includes/README.fr.html index b3ab03da..d7517d28 100644 --- a/_includes/README.fr.html +++ b/_includes/README.fr.html @@ -51,7 +51,7 @@
      41. Redirection du navigateur
      42. Contrôle du cache
      43. Envoyer des fichiers
      44. -
      45. Accéder à l'objet requête
      46. +
      47. Accéder à l’objet requête
      48. Fichiers joints
      49. Gérer Date et Time
      50. Chercher les fichiers de templates
      51. @@ -76,11 +76,11 @@
      52. Utiliser une application de style classique avec un fichier config.ru
      53. Quand utiliser un fichier config.ru ?
      54. Utiliser Sinatra comme Middleware
      55. -
      56. Création dynamique d'applications
      57. +
      58. Création dynamique d’applications
    16. Contextes et Binding
      1. -
      2. Contexte de l'application/classe
      3. +
      4. Contexte de l’application/classe
      5. Contexte de la requête/instance
      6. Le contexte de délégation
      @@ -98,26 +98,37 @@ -

      Attention : Ce document correspond à la traduction de la version anglaise et -il n'est peut être plus à jour.

      +il n’est peut être plus à jour.

      Sinatra est un DSL pour créer rapidement et facilement des applications web en Ruby :

      -
      # mon_application.rb
      -require 'sinatra'
       
      -get '/' do
      -  'Bonjour le monde !'
      -end
      -
      +
      +
      +
      # mon_application.rb
      +require 'sinatra'
      +
      +get '/' do
      +  'Bonjour le monde !'
      +end
      +
      +
      +
      +

      Installez la gem et lancez avec :

      -
      $ gem install sinatra
      -$ ruby mon_application.rb
      -
      + +
      +
      +
      $ gem install sinatra
      +$ ruby mon_application.rb
      +
      +
      +
      +

      Le résultat est visible sur : http://localhost:4567

      -

      Il est recommandé d'exécuter également gem install thin, pour que +

      Il est recommandé d’exécuter également gem install thin, pour que Sinatra utilise le server Thin quand il est disponible.

      @@ -125,159 +136,237 @@

      Routes

      Dans Sinatra, une route est une méthode HTTP couplée à un masque (pattern) URL. Chaque route est associée à un bloc :

      -
      get '/' do
      -  .. montrer quelque chose ..
      -end
      -
      -post '/' do
      -  .. créer quelque chose ..
      -end
      -
      -put '/' do
      -  .. remplacer quelque chose ..
      -end
      -
      -patch '/' do
      -  .. changer quelque chose ..
      -end
      -
      -delete '/' do
      -  .. effacer quelque chose ..
      -end
      -
      -options '/' do
      -  .. apaiser quelquechose ..
      -end
      -
      -

      Les routes sont évaluées dans l'ordre où elles ont été définies. La première + +

      +
      +
      get '/' do
      +  .. montrer quelque chose ..
      +end
      +
      +post '/' do
      +  .. créer quelque chose ..
      +end
      +
      +put '/' do
      +  .. remplacer quelque chose ..
      +end
      +
      +patch '/' do
      +  .. changer quelque chose ..
      +end
      +
      +delete '/' do
      +  .. effacer quelque chose ..
      +end
      +
      +options '/' do
      +  .. apaiser quelquechose ..
      +end
      +
      +
      +
      + +

      Les routes sont évaluées dans l’ordre où elles ont été définies. La première route qui correspond à la requête est appelée.

      Les masques peuvent inclure des paramètres nommés, accessibles par -l'intermédiaire du hash params :

      -
      get '/bonjour/:nom' do
      -  # répond aux requêtes "GET /bonjour/foo" et "GET /bonjour/bar"
      -  # params[:nom] est 'foo' ou 'bar'
      -  "Bonjour #{params[:nom]} !"
      -end
      -
      +l’intermédiaire du hash params :

      + +
      +
      +
      get '/bonjour/:nom' do
      +  # répond aux requêtes "GET /bonjour/foo" et "GET /bonjour/bar"
      +  # params[:nom] est 'foo' ou 'bar'
      +  "Bonjour #{params[:nom]} !"
      +end
      +
      +
      +
      +

      Vous pouvez aussi accéder aux paramètres nommés directement grâce aux paramètres du bloc comme ceci :

      -
      get '/bonjour/:nom' do |n|
      -  "Bonjour #{n} !"
      -end
      -
      + +
      +
      +
      get '/bonjour/:nom' do |n|
      +  "Bonjour #{n} !"
      +end
      +
      +
      +
      +

      Une route peut contenir un splat (caractère joker), accessible par -l'intermédiaire du tableau params[:splat] :

      -
      get '/dire/*/a/*' do
      -  # répond à /dire/bonjour/a/monde
      -  params[:splat] # => ["bonjour", "monde"]
      -end
      -
      -get '/telecharger/*.*' do
      -  # répond à /telecharger/chemin/vers/fichier.xml
      -  params[:splat] # => ["chemin/vers/fichier", "xml"]
      -end
      -
      -

      Ou par l'intermédiaire des paramètres du bloc :

      -
      get '/telecharger/*.*' do |chemin, ext|
      -  [chemin, ext] # => ["path/to/file", "xml"]
      -end
      -
      +l’intermédiaire du tableau params[:splat] :

      + +
      +
      +
      get '/dire/*/a/*' do
      +  # répond à /dire/bonjour/a/monde
      +  params[:splat] # => ["bonjour", "monde"]
      +end
      +
      +get '/telecharger/*.*' do
      +  # répond à /telecharger/chemin/vers/fichier.xml
      +  params[:splat] # => ["chemin/vers/fichier", "xml"]
      +end
      +
      +
      +
      + +

      Ou par l’intermédiaire des paramètres du bloc :

      + +
      +
      +
      get '/telecharger/*.*' do |chemin, ext|
      +  [chemin, ext] # => ["path/to/file", "xml"]
      +end
      +
      +
      +
      +

      Une route peut aussi être définie par une expression régulière :

      -
      get %r{/bonjour/([\w]+)} do
      -  "Bonjour, #{params[:captures].first} !"
      -end
      -
      + +
      +
      +
      get %r{/bonjour/([\w]+)} do
      +  "Bonjour, #{params[:captures].first} !"
      +end
      +
      +
      +
      +

      Là encore on peut utiliser les paramètres de bloc :

      -
      get %r{/bonjour/([\w]+)} do |c|
      -  "Bonjour, #{c} !"
      -end
      -
      + +
      +
      +
      get %r{/bonjour/([\w]+)} do |c|
      +  "Bonjour, #{c} !"
      +end
      +
      +
      +
      +

      Les routes peuvent aussi comporter des paramètres optionnels :

      -
      get '/posts.?:format?' do
      -  # répond à "GET /posts" et aussi à "GET /posts.json", "GET /posts.xml" etc...
      -end
      -
      -

      A ce propos, à moins d'avoir désactivé la protection contre les attaques par -"path transversal" (voir plus loin), l'URL demandée peut avoir été modifiée -avant d'être comparée à vos routes.

      + +
      +
      +
      get '/posts.?:format?' do
      +  # répond à "GET /posts" et aussi à "GET /posts.json", "GET /posts.xml" etc...
      +end
      +
      +
      +
      + +

      A ce propos, à moins d’avoir désactivé la protection contre les attaques par +“path transversal” (voir plus loin), l’URL demandée peut avoir été modifiée +avant d’être comparée à vos routes.

      Conditions

      Les routes peuvent définir toutes sortes de conditions, comme par exemple le -"user agent" :

      -
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      -  "Vous utilisez Songbird version #{params[:agent][0]}"
      -end
      -
      -get '/foo' do
      -  # Correspond à tous les autres navigateurs
      -end
      -
      +“user agent” :

      + +
      +
      +
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      +  "Vous utilisez Songbird version #{params[:agent][0]}"
      +end
      +
      +get '/foo' do
      +  # Correspond à tous les autres navigateurs
      +end
      +
      +
      +
      +

      Les autres conditions disponibles sont host_name et provides :

      -
      get '/', :host_name => /^admin\./ do
      -  "Zone Administrateur, Accès refusé !"
      -end
      -
      -get '/', :provides => 'html' do
      -  haml :index
      -end
      -
      -get '/', :provides => ['rss', 'atom', 'xml'] do
      -  builder :feed
      -end
      -
      + +
      +
      +
      get '/', :host_name => /^admin\./ do
      +  "Zone Administrateur, Accès refusé !"
      +end
      +
      +get '/', :provides => 'html' do
      +  haml :index
      +end
      +
      +get '/', :provides => ['rss', 'atom', 'xml'] do
      +  builder :feed
      +end
      +
      +
      +
      +

      Vous pouvez facilement définir vos propres conditions :

      -
      set(:probability) { |value| condition { rand <= value } }
       
      -get '/gagner_une_voiture', :probability => 0.1 do
      -  "Vous avez gagné !"
      -end
      +
      +
      +
      set(:probability) { |value| condition { rand <= value } }
      +
      +get '/gagner_une_voiture', :probability => 0.1 do
      +  "Vous avez gagné !"
      +end
      +
      +get '/gagner_une_voiture' do
      +  "Désolé, vous avez perdu."
      +end
      +
      +
      +
      -get '/gagner_une_voiture' do - "Désolé, vous avez perdu." -end -
      -

      Utilisez un splat (caractère joker) dans le cas d'une condition qui prend +

      Utilisez un splat (caractère joker) dans le cas d’une condition qui prend plusieurs valeurs :

      -
      set(:auth) do |*roles|   # <- ici on utilise un splat
      -  condition do
      -    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      -      redirect "/login/", 303
      -    end
      -  end
      -end
      -
      -get "/mon/compte/", :auth => [:user, :admin] do
      -  "Informations sur votre compte"
      -end
      -
      -get "/reserve/aux/admins/", :auth => :admin do
      -  "Seuls les administrateurs sont acceptés ici !"
      -end
      -
      + +
      +
      +
      set(:auth) do |*roles|   # <- ici on utilise un splat
      +  condition do
      +    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      +      redirect "/login/", 303
      +    end
      +  end
      +end
      +
      +get "/mon/compte/", :auth => [:user, :admin] do
      +  "Informations sur votre compte"
      +end
      +
      +get "/reserve/aux/admins/", :auth => :admin do
      +  "Seuls les administrateurs sont acceptés ici !"
      +end
      +
      +
      +
      +

      Valeurs de retour

      La valeur renvoyée par le bloc correspondant à une route constitue le corps de la réponse qui sera transmise au client HTTP ou du moins au prochain middleware -dans la pile Rack. Le plus souvent, il s'agit d'une chaîne de caractères, -comme dans les exemples précédents. Cependant, d'autres valeurs sont +dans la pile Rack. Le plus souvent, il s’agit d’une chaîne de caractères, +comme dans les exemples précédents. Cependant, d’autres valeurs sont acceptées.

      -

      Vous pouvez renvoyer n'importe quel objet qu'il s'agisse d'une réponse Rack -valide, d'un corps de réponse Rack ou d'un code statut HTTP :

      +

      Vous pouvez renvoyer n’importe quel objet qu’il s’agisse d’une réponse Rack +valide, d’un corps de réponse Rack ou d’un code statut HTTP :

      Avec cela, on peut facilement implémenter un streaming par exemple :

      -
      class Stream
      -  def each
      -    100.times { |i| yield "#{i}\n" }
      -  end
      -end
      -
      -get('/') { Stream.new }
      -
      + +
      +
      +
      class Stream
      +  def each
      +    100.times { |i| yield "#{i}\n" }
      +  end
      +end
      +
      +get('/') { Stream.new }
      +
      +
      +
      +

      Vous pouvez aussi utiliser le helper stream (présenté un peu plus loin) pour éviter la surcharge et intégrer le traitement relatif au streaming dans le bloc de code de la route.

      @@ -285,98 +374,152 @@

      Valeurs de retour

      Masques de route spécifiques

      -

      Comme cela a été vu auparavant, Sinatra offre la possibilité d'utiliser des +

      Comme cela a été vu auparavant, Sinatra offre la possibilité d’utiliser des masques sous forme de chaines de caractères ou des expressions régulières pour définir les routes. Mais il est possible de faire bien plus. Vous pouvez facilement définir vos propres masques :

      -
      class MasqueToutSauf
      -  Masque = Struct.new(:captures)
      -
      -  def initialize(except)
      -    @except   = except
      -    @captures = Masque.new([])
      -  end
      -
      -  def match(str)
      -    @caputres unless @except === str
      -  end
      -end
      -
      -def tout_sauf(masque)
      -  MasqueToutSauf.new(masque)
      -end
      -
      -get tout_sauf("/index") do
      -  # ...
      -end
      -
      -

      Notez que l'exemple ci-dessus est bien trop compliqué et que le même résultat + +

      +
      +
      class MasqueToutSauf
      +  Masque = Struct.new(:captures)
      +
      +  def initialize(except)
      +    @except   = except
      +    @captures = Masque.new([])
      +  end
      +
      +  def match(str)
      +    @caputres unless @except === str
      +  end
      +end
      +
      +def tout_sauf(masque)
      +  MasqueToutSauf.new(masque)
      +end
      +
      +get tout_sauf("/index") do
      +  # ...
      +end
      +
      +
      +
      + +

      Notez que l’exemple ci-dessus est bien trop compliqué et que le même résultat peut être obtenu avec :

      -
      get // do
      -  pass if request.path_info == "/index"
      -  # ...
      -end
      -
      + +
      +
      +
      get // do
      +  pass if request.path_info == "/index"
      +  # ...
      +end
      +
      +
      +
      +

      Ou bien en utilisant la forme négative :

      -
      get %r{^(?!/index$)} do
      -  # ...
      -end
      -
      + +
      +
      +
      get %r{^(?!/index$)} do
      +  # ...
      +end
      +
      +
      +
      +

      Fichiers statiques

      Les fichiers du dossier ./public sont servis de façon statique. Vous -avez la possibilité d'utiliser un autre répertoire en définissant le paramètre +avez la possibilité d’utiliser un autre répertoire en définissant le paramètre :public_folder :

      -
      set :public_folder, File.dirname(__FILE__) + '/statique'
      -
      -

      Notez que le nom du dossier public n'apparait pas dans l'URL. Le fichier -./public/css/style.css sera appelé via l'URL : + +

      +
      +
      set :public_folder, File.dirname(__FILE__) + '/statique'
      +
      +
      +
      + +

      Notez que le nom du dossier public n’apparait pas dans l’URL. Le fichier +./public/css/style.css sera appelé via l’URL : http://exemple.com/css/style.css.

      -

      Utilisez le paramètre :static_cache_control pour ajouter l'information -d'en-tête Cache-Control (voir plus loin).

      +

      Utilisez le paramètre :static_cache_control pour ajouter l’information +d’en-tête Cache-Control (voir plus loin).

      Vues / Templates

      Chaqie langage de template est disponible via sa propre méthode de rendu, lesquelles renvoient tout simplement une chaîne de caractères.

      -
      get '/' do
      -  erb :index
      -end
      -
      + +
      +
      +
      get '/' do
      +  erb :index
      +end
      +
      +
      +
      +

      Ceci effectue le rendu de la vue views/index.erb.

      -

      Plutôt que d'utiliser le nom d'un template, vous pouvez directement passer +

      Plutôt que d’utiliser le nom d’un template, vous pouvez directement passer le contenu du template :

      -
      get '/' do
      -  code = "<%= Time.now %>"
      -  erb code
      -end
      -
      -

      Les méthodes de templates acceptent un second paramètre, un hash d'options :

      -
      get '/' do
      -  erb :index, :layout => :post
      -end
      -
      -

      Ceci effectuera le rendu de la vue views/index.erb en l'intégrant + +

      +
      +
      get '/' do
      +  code = "<%= Time.now %>"
      +  erb code
      +end
      +
      +
      +
      + +

      Les méthodes de templates acceptent un second paramètre, un hash d’options :

      + +
      +
      +
      get '/' do
      +  erb :index, :layout => :post
      +end
      +
      +
      +
      + +

      Ceci effectuera le rendu de la vue views/index.erb en l’intégrant au layout views/post.erb (les vues Erb sont intégrées par défaut au layout views/layout.erb quand ce fichier existe).

      Toute option que Sinatra ne comprend pas sera passée au moteur de rendu :

      -
      get '/' do
      -  haml :index, :format => :html5
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml :index, :format => :html5
      +end
      +
      +
      +
      +

      Vous pouvez également définir des options par langage de template de façon générale :

      -
      set :haml, :format => html5
       
      -get '/' do
      -  haml :index
      -end
      -
      +
      +
      +
      set :haml, :format => html5
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      +

      Les options passées à la méthode de rendu prennent le pas sur les options définies au moyen de set.

      @@ -388,7 +531,7 @@

      Vues / Templates

      Exemple : erb "<%= foo %>", :locals => {:foo => "bar"}.

      default_encoding - Encodage de caractères à utiliser en cas d'incertitude. Par défaut, c'est + Encodage de caractères à utiliser en cas d’incertitude. Par défaut, c’est settings.default_encoding.

      views @@ -396,8 +539,8 @@

      Vues / Templates

      settings.views.

      layout - S'il faut ou non utiliser un +layout+ (+true+ or +false+). Indique le - template à utiliser lorsque c'est un symbole. Exemple : erb :index, + S’il faut ou non utiliser un +layout+ (+true+ or +false+). Indique le + template à utiliser lorsque c’est un symbole. Exemple : erb :index, :layout => !request.xhr?.

      content_type @@ -405,21 +548,27 @@

      Vues / Templates

      template.

      scope - Contexte sous lequel effectuer le rendu du template. Par défaut il s'agit - de l'instance de l'application. Si vous changez cela, les variables - d'instance et les méthodes utilitaires ne seront pas disponibles.

      + Contexte sous lequel effectuer le rendu du template. Par défaut il s’agit + de l’instance de l’application. Si vous changez cela, les variables + d’instance et les méthodes utilitaires ne seront pas disponibles.

      layout_engine Moteur de rendu à utiliser pour le +layout+. Utile pour les langages ne - supportant pas les +layouts+. Il s'agit par défaut du moteur utilisé pour + supportant pas les +layouts+. Il s’agit par défaut du moteur utilisé pour le rendu du template. Exemple : set :rdoc, :layout_engine => :erb

      Les templates sont supposés se trouver directement dans le dossier ./views. Pour utiliser un dossier de vues différent :

      -
      set :views, settings.root + '/templates'
      -
      + +
      +
      +
      set :views, settings.root + '/templates'
      +
      +
      +
      +

      Il est important de se souvenir que les templates sont toujours référencés -sous forme de symboles, même lorsqu'ils sont dans un sous-répertoire (dans +sous forme de symboles, même lorsqu’ils sont dans un sous-répertoire (dans ce cas, utilisez :'sous_repertoire/template'). Il faut utiliser un symbole car les méthodes de rendu évaluent le contenu des chaînes de caractères au lieu de les considérer comme un chemin vers un fichier.

      @@ -427,12 +576,18 @@

      Vues / Templates

      Langages de template disponibles

      -

      Certains langages ont plusieurs implémentations. Pour préciser l'implémentation -à utiliser (et garantir l'aspect thread-safe), vous devez simplement l'avoir +

      Certains langages ont plusieurs implémentations. Pour préciser l’implémentation +à utiliser (et garantir l’aspect thread-safe), vous devez simplement l’avoir chargée au préalable :

      -
      require 'rdiscount' # ou require 'bluecloth'
      -get('/') { markdown :index }
      -
      + +
      +
      +
      require 'rdiscount' # ou require 'bluecloth'
      +get('/') { markdown :index }
      +
      +
      +
      +

      Templates Haml

      @@ -503,8 +658,9 @@

      Templates Nokogiri

      Exemple - nokogiri { |xml| xml.em "salut" } - + +nokogiri { |xml| xml.em "salut" } +

      Ce moteur accepte également un bloc pour des templates en ligne (voir @@ -610,13 +766,25 @@

      Templates Markdown

      Il n’est pas possible d’appeler des méthodes depuis markdown, ni de lui passer des variables locales. Par conséquent, il sera souvent utilisé en combinaison avec un autre moteur de rendu :

      -
      erb :overview, :locals => { :text => markdown(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => markdown(:introduction) }
      +
      +
      +
      +

      Notez que vous pouvez également appeler la méthode markdown au sein d’autres templates :

      -
      %h1 Hello From Haml !
      -%p= markdown(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml !
      +%p= markdown(:greetings)
      +
      +
      +
      +

      Comme vous ne pouvez pas appeler de Ruby au sein de Markdown, vous ne pouvez pas utiliser de layouts écrits en Markdown. Toutefois, il est possible d’utiliser un moteur de rendu différent pour le template et @@ -642,13 +810,25 @@

      Templates Textile

      Il n’est pas possible d’appeler des méthodes depuis textile, ni de lui passer des variables locales. Par conséquent, il sera souvent utilisé en combinaison avec un autre moteur de rendu :

      -
      erb :overview, :locals => { :text => textile(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => textile(:introduction) }
      +
      +
      +
      +

      Notez que vous pouvez également appeler la méthode textile au sein d’autres templates :

      -
      %h1 Hello From Haml !
      -%p= textile(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml !
      +%p= textile(:greetings)
      +
      +
      +
      +

      Comme vous ne pouvez pas appeler de Ruby au sein de Textile, vous ne pouvez pas utiliser de layouts écrits en Textile. Toutefois, il est possible d’utiliser un moteur de rendu différent pour le template et @@ -674,13 +854,25 @@

      Templates RDoc

      Il n’est pas possible d’appeler des méthodes depuis rdoc, ni de lui passer des variables locales. Par conséquent, il sera souvent utilisé en combinaison avec un autre moteur de rendu :

      -
      erb :overview, :locals => { :text => rdoc(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => rdoc(:introduction) }
      +
      +
      +
      +

      Notez que vous pouvez également appeler la méthode rdoc au sein d’autres templates :

      -
      %h1 Hello From Haml !
      -%p= rdoc(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml !
      +%p= rdoc(:greetings)
      +
      +
      +
      +

      Comme vous ne pouvez pas appeler de Ruby au sein de RDoc, vous ne pouvez pas utiliser de layouts écrits en RDoc. Toutefois, il est possible d’utiliser un moteur de rendu différent pour le template et @@ -688,7 +880,6 @@

      Templates RDoc

      Templates Radius

      - @@ -777,20 +968,32 @@

      Templates Creole

      Dépendances creole :wiki, :layout_engine => :erb
      -

      Il n'est pas possible d'appeler des méthodes depuis markdown, ni de lui +

      Il n’est pas possible d’appeler des méthodes depuis markdown, ni de lui passer des variables locales. Par conséquent, il sera souvent utilisé en combinaison avec un autre moteur de rendu :

      -
      erb :overview, :locals => { :text => markdown(:introduction) }
      -
      -

      Notez que vous pouvez également appeler la méthode +markdown+ au sein d'autres + +

      +
      +
      erb :overview, :locals => { :text => markdown(:introduction) }
      +
      +
      +
      + +

      Notez que vous pouvez également appeler la méthode +markdown+ au sein d’autres templates :

      -
      %h1 Hello From Haml !
      -%p= markdown(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml !
      +%p= markdown(:greetings)
      +
      +
      +
      +

      Comme vous ne pouvez pas appeler de Ruby au sein de Markdown, vous ne pouvez pas utiliser de +layouts+ écrits en Markdown. Toutefois, il est possible -d'utiliser un moteur de rendu différent pour le template et pour le +layout+ -en utilisant l'option :layout_engine.

      +d’utiliser un moteur de rendu différent pour le template et pour le +layout+ +en utilisant l’option :layout_engine.

      Templates CoffeeScript

      @@ -831,12 +1034,20 @@

      Templates Yajl

      Le source du template est évalué en tant que chaine Ruby, puis la variable json obtenue est convertie avec #to_json.

      -
      json = { :foo => 'bar' }
      -json[:baz] = key
      -
      + +
      +
      +
      json = { :foo => 'bar' }
      +json[:baz] = key
      +
      +
      +
      +

      Les options :callback et :variable peuvent être utilisées pour décorer l’objet retourné.

      -
      var resource = {"foo":"bar","baz":"qux"}; present(resource);</pre>
      -
      + +
      var resource = {"foo":"bar","baz":"qux"}; present(resource);</pre>
      +
      +

      Templates WLang

      @@ -858,54 +1069,78 @@

      Templates WLang

      Templates embarqués

      -
      get '/' do
      -  haml '%div.title Bonjour le monde'
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml '%div.title Bonjour le monde'
      +end
      +
      +
      +
      +

      Générera le code du template spécifié dans la chaîne de caractères.

      Accéder aux variables dans un Template

      -

      Un template est évalué dans le même contexte que l'endroit d'où il a été -appelé (gestionnaire de route). Les variables d'instance déclarées dans le +

      Un template est évalué dans le même contexte que l’endroit d’où il a été +appelé (gestionnaire de route). Les variables d’instance déclarées dans le gestionnaire de route sont directement accessibles dans le template :

      -
      get '/:id' do
      -  @foo = Foo.find(params[:id])
      -  haml '%h1= @foo.nom'
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  @foo = Foo.find(params[:id])
      +  haml '%h1= @foo.nom'
      +end
      +
      +
      +
      +

      Alternativement, on peut passer un hash contenant des variables locales :

      -
      get '/:id' do
      -  foo = Foo.find(params[:id])
      -  haml '%h1= foo.nom', :locals => { :foo => foo }
      -end
      -
      -

      Ceci est généralement utilisé lorsque l'on veut utiliser un template comme -partiel (depuis un autre template) et qu'il est donc nécessaire d'adapter les + +

      +
      +
      get '/:id' do
      +  foo = Foo.find(params[:id])
      +  haml '%h1= foo.nom', :locals => { :foo => foo }
      +end
      +
      +
      +
      + +

      Ceci est généralement utilisé lorsque l’on veut utiliser un template comme +partiel (depuis un autre template) et qu’il est donc nécessaire d’adapter les noms de variables.

      Templates dans le fichier source

      Des templates peuvent être définis dans le fichier source comme ceci :

      -
      require 'sinatra'
       
      -get '/' do
      -  haml :index
      -end
      +
      +
      +
      require 'sinatra'
      +
      +get '/' do
      +  haml :index
      +end
       
      -__END__
      +__END__
       
       @@ layout
       %html
         = yield
       
       @@ index
      -%div.title Bonjour le monde !
      -
      +%div.title Bonjour le monde ! +
      + + +

      NOTE : Les templates du fichier source qui contient require 'sinatra' -sont automatiquement chargés. Si vous avez des templates dans d'autres +sont automatiquement chargés. Si vous avez des templates dans d’autres fichiers source, il faut explicitement les déclarer avec enable :inline_templates.

      @@ -913,49 +1148,73 @@

      Templates dans le fichier source

      Templates nommés

      Les templates peuvent aussi être définis grâce à la méthode de haut niveau template :

      -
      template :layout do
      -  "%html\n  =yield\n"
      -end
      -
      -template :index do
      -  '%div.title Bonjour le monde !'
      -end
      -
      -get '/' do
      -  haml :index
      -end
      -
      -

      Si un template nommé "layout" existe, il sera utilisé à chaque fois qu'un + +

      +
      +
      template :layout do
      +  "%html\n  =yield\n"
      +end
      +
      +template :index do
      +  '%div.title Bonjour le monde !'
      +end
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      + +

      Si un template nommé “layout” existe, il sera utilisé à chaque fois qu’un template sera affiché. Vous pouvez désactivez les layouts au cas par cas en passant :layout => false ou bien les désactiver par défaut au moyen de set :haml, :layout => false :

      -
      get '/' do
      -  haml :index, :layout => !request.xhr?
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml :index, :layout => !request.xhr?
      +end
      +
      +
      +
      +

      Associer des extensions de fichier

      Pour associer une extension de fichier avec un moteur de rendu, utilisez -Tilt.register. Par exemple, si vous désirez utiliser l'extension +Tilt.register. Par exemple, si vous désirez utiliser l’extension de fichier tt pour les templates Textile, vous pouvez faire comme suit :

      -
      Tilt.register :tt, Tilt[:textile]
      -
      + +
      +
      +
      Tilt.register :tt, Tilt[:textile]
      +
      +
      +
      +

      Ajouter son propre moteur de rendu

      En premier lieu, déclarez votre moteur de rendu avec Tilt, ensuite créez votre méthode de rendu :

      -
      Tilt.register :monmoteur, MonMerveilleurMoteurDeRendu
       
      -helpers do
      -  def monmoteur(*args) render(:monmoteur, *args) end
      -end
      +
      +
      +
      Tilt.register :monmoteur, MonMerveilleurMoteurDeRendu
      +
      +helpers do
      +  def monmoteur(*args) render(:monmoteur, *args) end
      +end
      +
      +get '/' do
      +  monmoteur :index
      +end
      +
      +
      +
      -get '/' do - monmoteur :index -end -

      Utilisera ./views/index.monmoteur. Voir le dépôt Github pour en savoir plus sur Tilt.

      @@ -963,156 +1222,258 @@

      Filtres

      Les filtres before sont exécutés avant chaque requête, dans le même contexte que les routes, et permettent de modifier la requête et sa réponse. Les -variables d'instance déclarées dans les filtres sont accessibles au niveau +variables d’instance déclarées dans les filtres sont accessibles au niveau des routes et des templates :

      -
      before do
      -  @note = 'Coucou !'
      -  request.path_info = '/foo/bar/baz'
      -end
      -
      -get '/foo/*' do
      -  @note #=> 'Coucou !'
      -  params[:splat] #=> 'bar/baz'
      -end
      -
      -

      Les filtres after sont exécutés après chaque requête à l'intérieur du même + +

      +
      +
      before do
      +  @note = 'Coucou !'
      +  request.path_info = '/foo/bar/baz'
      +end
      +
      +get '/foo/*' do
      +  @note #=> 'Coucou !'
      +  params[:splat] #=> 'bar/baz'
      +end
      +
      +
      +
      + +

      Les filtres after sont exécutés après chaque requête à l’intérieur du même contexte et permettent de modifier la requête et sa réponse. Les variables -d'instance déclarées dans les filtres before ou les routes sont accessibles +d’instance déclarées dans les filtres before ou les routes sont accessibles au niveau des filtres after :

      -
      after do
      -  puts response.status
      -end
      -
      -

      Note : Le corps de la réponse n'est pas disponible au niveau du filtre after + +

      +
      +
      after do
      +  puts response.status
      +end
      +
      +
      +
      + +

      Note : Le corps de la réponse n’est pas disponible au niveau du filtre after car il ne sera généré que plus tard (sauf dans le cas où vous utilisez la méthode +body+ au lieu de simplement renvoyer une chaine depuis vos routes).

      Les filtres peuvent être associés à un masque, ce qui permet de limiter leur exécution aux cas où la requête correspond à ce masque :

      -
      before '/secret/*' do
      -  authentification!
      -end
      -
      -after '/faire/:travail' do |travail|
      -  session[:dernier_travail] = travail
      -end
      -
      + +
      +
      +
      before '/secret/*' do
      +  authentification!
      +end
      +
      +after '/faire/:travail' do |travail|
      +  session[:dernier_travail] = travail
      +end
      +
      +
      +
      +

      Tout comme les routes, les filtres acceptent également des conditions :

      -
      before :agent => /Songbird/ do
      -  # ...
      -end
      -
      -after '/blog/*', :host_name => 'example.com' do
      -  # ...
      -end
      -
      + +
      +
      +
      before :agent => /Songbird/ do
      +  # ...
      +end
      +
      +after '/blog/*', :host_name => 'example.com' do
      +  # ...
      +end
      +
      +
      +
      +

      Helpers

      Utilisez la méthode de haut niveau helpers pour définir des routines qui seront accessibles dans vos gestionnaires de route et dans vos templates :

      -
      helpers do
      -  def bar(nom)
      -    "#{nom}bar"
      -  end
      -end
      -
      -get '/:nom' do
      -  bar(params[:nom])
      -end
      -
      + +
      +
      +
      helpers do
      +  def bar(nom)
      +    "#{nom}bar"
      +  end
      +end
      +
      +get '/:nom' do
      +  bar(params[:nom])
      +end
      +
      +
      +
      +

      Vous pouvez aussi définir les méthodes helper dans un module séparé :

      -
      module FooUtils
      -  def foo(nom) "#{nom}foo" end
      -end
       
      -module BarUtils
      -  def bar(nom) "#{nom}bar" end
      -end
      +
      +
      +
      module FooUtils
      +  def foo(nom) "#{nom}foo" end
      +end
      +
      +module BarUtils
      +  def bar(nom) "#{nom}bar" end
      +end
       
      -helpers FooUtils, BarUtils
      -
      -

      Cela a le même résultat que d'inclure les modules dans la classe de -l'application.

      +helpers FooUtils, BarUtils +
      + + + +

      Cela a le même résultat que d’inclure les modules dans la classe de +l’application.

      Utiliser les sessions

      Une session est utilisée pour conserver un état entre les requêtes. Une fois activées, vous avez un +hash+ de session par session utilisateur :

      -
      enable :sessions
       
      -get '/' do
      -  "valeur = " << session[:valeur].inspect
      -end
      +
      +
      +
      enable :sessions
      +
      +get '/' do
      +  "valeur = " << session[:valeur].inspect
      +end
      +
      +get '/:value' do
      +  session[:valeur] = params[:valeur]
      +end
      +
      +
      +
      -get '/:value' do - session[:valeur] = params[:valeur] -end -

      Notez que enable :sessions enregistre en fait toutes les données dans -un +cookie+. Ce n'est pas toujours ce que vous voulez (enregistrer beaucoup de -données va augmenter le traffic par exemple). Vous pouvez utiliser n'importe -quel +middleware+ Rack de session afin d'éviter cela. N'utiliser pas +un +cookie+. Ce n’est pas toujours ce que vous voulez (enregistrer beaucoup de +données va augmenter le traffic par exemple). Vous pouvez utiliser n’importe +quel +middleware+ Rack de session afin d’éviter cela. N’utiliser pas enable :sessions dans ce cas mais charger le +middleware+ de votre -choix comme vous le feriez pour n'importe quel autre +middleware+ :

      -
      use Rack::Session::Pool, :expire_after => 2592000
      +choix comme vous le feriez pour n’importe quel autre +middleware+ :

      + +
      +
      +
      use Rack::Session::Pool, :expire_after => 2592000
       
      -get '/' do
      -  "valeur = " << session[:valeur].inspect
      -end
      +get '/' do
      +  "valeur = " << session[:valeur].inspect
      +end
      +
      +get '/:value' do
      +  session[:valeur] = params[:valeur]
      +end
      +
      +
      +
      -get '/:value' do - session[:valeur] = params[:valeur] -end -

      Pour renforcer la sécurité, les données de session dans le cookie sont signées avec une clé secrète de session. Une clé secrète est générée pour vous au hasard par Sinatra. Toutefois, comme cette clé change à chaque démarrage de votre application, vous pouvez définir cette clé vous-même afin que toutes les instances de votre application la partage :

      -
      set :session_secret, 'super secret'
      -
      + +
      +
      +
      set :session_secret, 'super secret'
      +
      +
      +
      +

      Si vous souhaitez avoir plus de contrôle, vous pouvez également enregistrer un +hash+ avec des options lors de la configuration de sessions :

      -
      set :sessions, :domain => 'foo.com'
      -
      + +
      +
      +
      set :sessions, :domain => 'foo.com'
      +
      +
      +
      +

      Halt

      Pour arrêter immédiatement la requête dans un filtre ou un gestionnaire de route :

      -
      halt
      -
      -

      Vous pouvez aussi passer le code retour ...

      -
      halt 410
      -
      -

      Ou le texte ...

      -
      halt 'Ceci est le texte'
      -
      -

      Ou les deux ...

      -
      halt 401, 'Partez !'
      -
      -

      Ainsi que les entêtes ...

      -
      halt 402, {'Content-Type' => 'text/plain'}, 'revanche'
      -
      + +
      +
      +
      halt
      +
      +
      +
      + +

      Vous pouvez aussi passer le code retour …

      + +
      +
      +
      halt 410
      +
      +
      +
      + +

      Ou le texte …

      + +
      +
      +
      halt 'Ceci est le texte'
      +
      +
      +
      + +

      Ou les deux …

      + +
      +
      +
      halt 401, 'Partez !'
      +
      +
      +
      + +

      Ainsi que les entêtes …

      + +
      +
      +
      halt 402, {'Content-Type' => 'text/plain'}, 'revanche'
      +
      +
      +
      +

      Bien sûr il est possible de combiner un template avec halt :

      -
      halt erb(:erreur)
      -
      + +
      +
      +
      halt erb(:erreur)
      +
      +
      +
      +

      Passer

      Une route peut passer le relais aux autres routes qui correspondent également avec pass :

      -
      get '/devine/:qui' do
      -  pass unless params[:qui] == 'Frank'
      -  "Tu m'as eu !"
      -end
      -
      -get '/devine/*' do
      -  'Manqué !'
      -end
      -
      + +
      +
      +
      get '/devine/:qui' do
      +  pass unless params[:qui] == 'Frank'
      +  "Tu m'as eu !"
      +end
      +
      +get '/devine/*' do
      +  'Manqué !'
      +end
      +
      +
      +
      +

      On sort donc immédiatement de ce gestionnaire et on continue à chercher, dans les masques suivants, le prochain qui correspond à la requête. Si aucun des masques suivants ne correspond, un code 404 est retourné.

      @@ -1120,24 +1481,30 @@

      Passer

      Déclencher une autre route

      -

      Parfois, +pass+ n'est pas ce que vous recherchez, au lieu de cela vous -souhaitez obtenir le résultat d'une autre route. Pour cela, utilisez +

      Parfois, +pass+ n’est pas ce que vous recherchez, au lieu de cela vous +souhaitez obtenir le résultat d’une autre route. Pour cela, utilisez simplement call :

      -
      get '/foo' do
      -  status, headers, body = call env.merge("PATH_INFO" => '/bar')
      -  [status, headers, body.map(&:upcase)]
      -end
      -
      -get '/bar' do
      -  "bar"
      -end
      -
      -

      Notez que dans l'exemple ci-dessus, vous faciliterez les tests et améliorerez -la performance en déplaçant simplement "bar" dans un helper + +

      +
      +
      get '/foo' do
      +  status, headers, body = call env.merge("PATH_INFO" => '/bar')
      +  [status, headers, body.map(&:upcase)]
      +end
      +
      +get '/bar' do
      +  "bar"
      +end
      +
      +
      +
      + +

      Notez que dans l’exemple ci-dessus, vous faciliterez les tests et améliorerez +la performance en déplaçant simplement "bar" dans un helper utilisé à la fois par /foo et /bar.

      Si vous souhiatez que la requête soit envoyée à la même instance de -l'application plutôt qu'à une copie, utilisez call! au lieu de +l’application plutôt qu’à une copie, utilisez call! au lieu de call.

      Lisez la spécification Rack si vous souhaitez en savoir plus sur @@ -1147,33 +1514,45 @@

      Déclencher une autre route

      Définir le corps, le code retour et les entêtes

      Il est possible et recommandé de définir le code retour et le corps de la -réponse au moyen de la valeur de retour d'un bloc définissant une route. -Quoiqu'il en soit, dans certains cas vous pourriez avoir besoin de définir -le coprs de la réponse à un moment arbitraire de l'exécution. Vous pouvez le +réponse au moyen de la valeur de retour d’un bloc définissant une route. +Quoiqu’il en soit, dans certains cas vous pourriez avoir besoin de définir +le coprs de la réponse à un moment arbitraire de l’exécution. Vous pouvez le faire au moyen de la méthode +body+. Si vous faites ainsi, vous pouvez alors utiliser cette même méthode pour accéder au corps de la réponse :

      -
      get '/foo' do
      -  body "bar"
      -end
      -
      -after do
      -  puts body
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  body "bar"
      +end
      +
      +after do
      +  puts body
      +end
      +
      +
      +
      +

      Il est également possible de passer un bloc à body, qui sera exécuté par le gestionnaire Rack (ceci peut être utilisé pour implémenter un streaming, -voir "Valeurs de retour").

      +voir “Valeurs de retour”).

      Pareillement au corps de la réponse, vous pouvez également définir le code retour et les entêtes :

      -
      get '/foo' do
      -  status 418
      -  headers \
      -    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
      -    "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
      -  body "Je suis une théière !"
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  status 418
      +  headers \
      +    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
      +    "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
      +  body "Je suis une théière !"
      +end
      +
      +
      +
      +

      Comme body headers et status peuvent être utilisés sans arguments pour accéder à leurs valeurs.

      @@ -1183,76 +1562,100 @@

      Faire du streaming

      Il y a des cas où vous voulez commencer à renvoyer des données pendant que vous êtes en train de générer le reste de la réponse. Dans les cas les plus extrèmes, vous souhaitez continuer à envoyer des données tant que le client -n'abandonne pas la connection. Vous pouvez alors utiliser le helper stream +n’abandonne pas la connection. Vous pouvez alors utiliser le helper stream pour éviter de créer votre propre système :

      -
      get '/' do
      -  stream do |out|
      -    out << "Ca va être hallu -\n"
      -    sleep 0.5
      -    out << " (attends la suite) \n"
      -    sleep 1
      -    out << "- cinant !\n"
      -  end
      -end
      -
      -

      Cela permet d'implémenter des API de streaming ou de + +

      +
      +
      get '/' do
      +  stream do |out|
      +    out << "Ca va être hallu -\n"
      +    sleep 0.5
      +    out << " (attends la suite) \n"
      +    sleep 1
      +    out << "- cinant !\n"
      +  end
      +end
      +
      +
      +
      + +

      Cela permet d’implémenter des API de streaming ou de Server Sent Events et peut servir de base pour des WebSockets. Vous -pouvez aussi l'employer pour augmenter le débit quand une partie du contenu -provient d'une resource lente.

      +pouvez aussi l’employer pour augmenter le débit quand une partie du contenu +provient d’une resource lente.

      Le fonctionnement du streaming, notamment le nombre de requêtes simultanées, dépend énormément du serveur web utilisé. Certains ne prennent pas du tout en charge le streaming (WEBRick par exemple). Lorsque le serveur ne gère pas le streaming, la partie body de la réponse sera envoyée au client en une seule -fois, après que l'exécution du bloc passé au helper +stream+ sera terminée. Le +fois, après que l’exécution du bloc passé au helper +stream+ sera terminée. Le streaming ne fonctionne pas du tout avec Shotgun.

      -

      En utilisant le helper +stream+ avec le paramètre +keep_open+, il n'appelera +

      En utilisant le helper +stream+ avec le paramètre +keep_open+, il n’appelera pas la méthode +close+ du flux, vous laissant la possibilité de le fermer à -tout moment au cours de l'exécution. Ceci ne fonctionne qu'avec les serveurs +tout moment au cours de l’exécution. Ceci ne fonctionne qu’avec les serveurs evented (ie non threadés) tels que Thin et Rainbows. Les autres serveurs fermeront malgré tout le flux :

      -
      set :server, :thin
      -connections = []
      -
      -get '/' do
      -  # conserve le flux ouvert
      -  stream(:keep_open) { |out| connections << out }
      -end
      -
      -post '/' do
      -  # écrit dans tous les flux ouverts
      -  connections.each { |out| out << params[:message] << "\n" }
      -  "message sent"
      -end
      -
      + +
      +
      +
      set :server, :thin
      +connections = []
      +
      +get '/' do
      +  # conserve le flux ouvert
      +  stream(:keep_open) { |out| connections << out }
      +end
      +
      +post '/' do
      +  # écrit dans tous les flux ouverts
      +  connections.each { |out| out << params[:message] << "\n" }
      +  "message sent"
      +end
      +
      +
      +
      +

      Journalisation (Logging)

      Dans le contexte de la requête, la méthode utilitaire +logger+ expose une instance de +logger+ :

      -
      get '/' do
      -  logger.info "chargement des données"
      -  # ...
      -end
      -
      + +
      +
      +
      get '/' do
      +  logger.info "chargement des données"
      +  # ...
      +end
      +
      +
      +
      +

      Ce logger va automatiquement prendre en compte les paramètres de configuration pour la journalisation de votre gestionnaire Rack. Si la journalisation est désactivée, cette méthode renverra un objet factice et -vous n'avez pas à vous en inquiéter dans vos routes en le filtrant.

      +vous n’avez pas à vous en inquiéter dans vos routes en le filtrant.

      Notez que la journalisation est seulement activée par défaut pour Sinatra::Application, donc si vous héritez de >Sinatra::Base, -vous aurez à l'activer vous-même :

      -
      class MonApp < Sinatra::Base
      -  configure :production, :development do
      -    enable :logging
      -  end
      -end
      -
      +vous aurez à l’activer vous-même :

      + +
      +
      +
      class MonApp < Sinatra::Base
      +  configure :production, :development do
      +    enable :logging
      +  end
      +end
      +
      +
      +
      +

      Si vous souhaitez utiliser votre propre logger, vous devez définir le paramètre -logging à nil pour être certain qu'aucun middleware de logging ne sera +logging à nil pour être certain qu’aucun middleware de logging ne sera installé (notez toutefois que +logger+ renverra alors +nil+). Dans ce cas, Sinatra utilisera ce qui sera présent dans env['rack.logger'].

      @@ -1262,26 +1665,44 @@

      Types Mime

      Quand vous utilisez send_file ou des fichiers statiques, vous pouvez rencontrer des types mime que Sinatra ne connaît pas. Utilisez mime_type pour les déclarer par extension de fichier :

      -
      configure do
      -  mime_type :foo, 'text/foo'
      -end
      -
      + +
      +
      +
      configure do
      +  mime_type :foo, 'text/foo'
      +end
      +
      +
      +
      +

      Vous pouvez également les utiliser avec la méthode content_type :

      -
      get '/' do
      -  content_type :foo
      -  "foo foo foo"
      -end
      -
      + +
      +
      +
      get '/' do
      +  content_type :foo
      +  "foo foo foo"
      +end
      +
      +
      +
      +

      Former des URLs

      Pour former des URLs, vous devriez utiliser la méthode +url+, par exemple en Haml :

      -
      %a{:href => url('/foo')} foo
      -
      -

      Cela prend en compte les proxy inverse et les routeurs Rack, s'ils existent.

      -

      Cette méthode est également disponible sous l'alias +to+ (voir ci-dessous +

      +
      +
      %a{:href => url('/foo')} foo
      +
      +
      +
      + +

      Cela prend en compte les proxy inverse et les routeurs Rack, s’ils existent.

      + +

      Cette méthode est également disponible sous l’alias +to+ (voir ci-dessous pour un exemple).

      @@ -1289,124 +1710,210 @@

      Redirection du navigateur

      Vous pouvez déclencher une redirection du navigateur avec la méthode redirect :

      -
      get '/foo' do
      -  redirect to('/bar')
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  redirect to('/bar')
      +end
      +
      +
      +
      +

      Tout paramètre additionnel est géré comme des arguments pour la méthode halt :

      -
      redirect to('/bar'), 303
      -redirect 'http://google.com', 'mauvais endroit mon pote'
      -
      -

      Vous pouvez aussi rediriger vers la page dont l'utilisateur venait au moyen de + +

      +
      +
      redirect to('/bar'), 303
      +redirect 'http://google.com', 'mauvais endroit mon pote'
      +
      +
      +
      + +

      Vous pouvez aussi rediriger vers la page dont l’utilisateur venait au moyen de redirect back :

      -
      get '/foo' do
      -  "<a href='/bar'>faire quelque chose</a>"
      -end
      -
      -get '/bar' do
      -  faire_quelque_chose
      -  redirect back
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  "<a href='/bar'>faire quelque chose</a>"
      +end
      +
      +get '/bar' do
      +  faire_quelque_chose
      +  redirect back
      +end
      +
      +
      +
      +

      Pour passer des arguments à une redirection, ajoutez-les soit à la requête :

      -
      redirect to('/bar?sum=42')
      -
      + +
      +
      +
      redirect to('/bar?sum=42')
      +
      +
      +
      +

      Ou bien utilisez une session :

      -
      enable :sessions
       
      -get '/foo' do
      -  session[:secret] = 'foo'
      -  redirect to('/bar')
      -end
      +
      +
      +
      enable :sessions
      +
      +get '/foo' do
      +  session[:secret] = 'foo'
      +  redirect to('/bar')
      +end
      +
      +get '/bar' do
      +  session[:secret]
      +end
      +
      +
      +
      -get '/bar' do - session[:secret] -end -

      Contrôle du cache

      Définir correctement vos entêtes à la base pour un bon cache HTTP.

      -

      Vous pouvez facilement définir l'entête Cache-Control de la manière suivante :

      -
      get '/' do
      -  cache_control :public
      -  "met le en cache !"
      -end
      -
      +

      Vous pouvez facilement définir l’entête Cache-Control de la manière suivante :

      + +
      +
      +
      get '/' do
      +  cache_control :public
      +  "met le en cache !"
      +end
      +
      +
      +
      +

      Conseil de pro : définir le cache dans un filtre +before+ :

      -
      before do
      -  cache_control :public, :must_revalidate, :max_age => 60
      -end
      -
      -

      Si vous utilisez la méthode +expires+ pour définir l'entête correspondant, + +

      +
      +
      before do
      +  cache_control :public, :must_revalidate, :max_age => 60
      +end
      +
      +
      +
      + +

      Si vous utilisez la méthode +expires+ pour définir l’entête correspondant, Cache-Control sera alors défini automatiquement :

      -
      before do
      -  expires 500, :public, :must_revalidate
      -end
      -
      + +
      +
      +
      before do
      +  expires 500, :public, :must_revalidate
      +end
      +
      +
      +
      +

      Pour utiliser correctement les caches, vous devriez utiliser +etag+ ou -+last_modified+. Il est recommandé d'utiliser ces méthodes avant de faire -d'importantes modifications, car elles vont immédiatement déclencher la réponse ++last_modified+. Il est recommandé d’utiliser ces méthodes avant de faire +d’importantes modifications, car elles vont immédiatement déclencher la réponse si le client a déjà la version courante dans son cache :

      -
      get '/article/:id' do
      -  @article = Article.find params[:id]
      -  last_modified @article.updated_at
      -  etag @article.sha1
      -  erb :article
      -end
      -
      -

      Il est également possible d'utiliser un + +

      +
      +
      get '/article/:id' do
      +  @article = Article.find params[:id]
      +  last_modified @article.updated_at
      +  etag @article.sha1
      +  erb :article
      +end
      +
      +
      +
      + +

      Il est également possible d’utiliser un weak ETag :

      -
      etag @article.sha1, :weak
      -
      + +
      +
      +
      etag @article.sha1, :weak
      +
      +
      +
      +

      Ces méthodes ne sont pas chargées de mettre des données en cache, mais elles fournissent les informations nécessaires pour votre cache. Si vous êtes à la recherche de solutions rapides pour un reverse-proxy de cache, essayez rack-cache :

      -
      require "rack/cache"
      -require "sinatra"
      -
      -use Rack::Cache
      -
      -get '/' do
      -  cache_control :public, :max_age => 36000
      -  sleep 5
      -  "hello"
      -end
      -
      -

      Utilisez le paramètre :static_cache_control pour ajouter l'information -d'en-tête Cache-Control (voir plus loin).

      - -

      D'après la RFC 2616, votre application devrait se comporter différement lorsque -l'en-tête If-Match ou If-None-Match est défini à * en tenant compte du + +

      +
      +
      require "rack/cache"
      +require "sinatra"
      +
      +use Rack::Cache
      +
      +get '/' do
      +  cache_control :public, :max_age => 36000
      +  sleep 5
      +  "hello"
      +end
      +
      +
      +
      + +

      Utilisez le paramètre :static_cache_control pour ajouter l’information +d’en-tête Cache-Control (voir plus loin).

      + +

      D’après la RFC 2616, votre application devrait se comporter différement lorsque +l’en-tête If-Match ou If-None-Match est défini à * en tenant compte du fait que la resource demandée existe déjà ou pas. Sinatra considère que les requêtes portant sur des resources sûres (tel que get) ou idempotentes (tel que put) existent déjà et pour les autres resources (par exemple dans le cas -de requêtes post) qu'il s'agit de nouvelles resources. Vous pouvez modifier ce +de requêtes post) qu’il s’agit de nouvelles resources. Vous pouvez modifier ce comportement en passant une option :new_resource :

      -
      get '/create' do
      -  etag '', :new_resource => true
      -  Article.create
      -  erb :new_article
      -end
      -
      -

      Si vous souhaitez utilisez un ETag faible, utilisez l'option :kind :

      -
      etag '', :new_resource => true, :kind => :weak
      -
      + +
      +
      +
      get '/create' do
      +  etag '', :new_resource => true
      +  Article.create
      +  erb :new_article
      +end
      +
      +
      +
      + +

      Si vous souhaitez utilisez un ETag faible, utilisez l’option :kind :

      + +
      etag '', :new_resource => true, :kind => :weak
      +
      +

      Envoyer des fichiers

      Pour envoyer des fichiers, vous pouvez utiliser la méthode send_file :

      -
      get '/' do
      -  send_file 'foo.png'
      -end
      -
      + +
      +
      +
      get '/' do
      +  send_file 'foo.png'
      +end
      +
      +
      +
      +

      Quelques options sont également acceptées :

      -
      send_file 'foo.png', :type => :jpg
      -
      + +
      +
      +
      send_file 'foo.png', :type => :jpg
      +
      +
      +
      +

      Les options sont :

      @@ -1434,191 +1941,258 @@

      Envoyer des fichiers

      code état à renvoyer. Utile quand un fichier statique sert de page d’erreur.
      -

      Si le gestionnaire Rack le supporte, d'autres moyens que le +streaming+ via le +

      Si le gestionnaire Rack le supporte, d’autres moyens que le +streaming+ via le processus Ruby seront utilisés. Si vous utilisez cette méthode, Sinatra gérera automatiquement les requêtes de type +range+.

      - -

      Accéder à l'objet requête

      + +

      Accéder à l’objet requête

      -

      L'objet correspondant à la requête envoyée peut être récupéré dans le contexte -de la requête (filtres, routes, gestionnaires d'erreur) au moyen de la méthode +

      L’objet correspondant à la requête envoyée peut être récupéré dans le contexte +de la requête (filtres, routes, gestionnaires d’erreur) au moyen de la méthode +request+ :

      -
      # application tournant à l'adresse http://exemple.com/exemple
      -get '/foo' do
      -  t = %w[text/css text/html application/javascript]
      -  request.accept              # ['text/html', '*/*']
      -  request.accept? 'text/xml'  # true
      -  request.preferred_type(t)   # 'text/html'
      -  request.body                # corps de la requête envoyée par le client
      -                              # (voir ci-dessous)
      -  request.scheme              # "http"
      -  request.script_name         # "/exemple"
      -  request.path_info           # "/foo"
      -  request.port                # 80
      -  request.request_method      # "GET"
      -  request.query_string        # ""
      -  request.content_length      # taille de request.body
      -  request.media_type          # type de média pour request.body
      -  request.host                # "exemple.com"
      -  request.get?                # true (méthodes similaires pour les autres
      -                              # verbes HTTP)
      -  request.form_data?          # false
      -  request["UN_ENTETE"]        # valeur de l'entête UN_ENTETE
      -  request.referer             # référant du client ou '/'
      -  request.user_agent          # user agent (utilisé par la condition :agent)
      -  request.cookies             # tableau contenant les cookies du navigateur
      -  request.xhr?                # requête AJAX ?
      -  request.url                 # "http://exemple.com/exemple/foo"
      -  request.path                # "/exemple/foo"
      -  request.ip                  # adresse IP du client
      -  request.secure?             # false
      -  request.forwarded?          # vrai (si on est derrière un proxy inverse)
      -  request.env                 # tableau brut de l'environnement fourni par
      -                              # Rack
      -end
      -
      + +
      +
      +
      # application tournant à l'adresse http://exemple.com/exemple
      +get '/foo' do
      +  t = %w[text/css text/html application/javascript]
      +  request.accept              # ['text/html', '*/*']
      +  request.accept? 'text/xml'  # true
      +  request.preferred_type(t)   # 'text/html'
      +  request.body                # corps de la requête envoyée par le client
      +                              # (voir ci-dessous)
      +  request.scheme              # "http"
      +  request.script_name         # "/exemple"
      +  request.path_info           # "/foo"
      +  request.port                # 80
      +  request.request_method      # "GET"
      +  request.query_string        # ""
      +  request.content_length      # taille de request.body
      +  request.media_type          # type de média pour request.body
      +  request.host                # "exemple.com"
      +  request.get?                # true (méthodes similaires pour les autres
      +                              # verbes HTTP)
      +  request.form_data?          # false
      +  request["UN_ENTETE"]        # valeur de l'entête UN_ENTETE
      +  request.referer             # référant du client ou '/'
      +  request.user_agent          # user agent (utilisé par la condition :agent)
      +  request.cookies             # tableau contenant les cookies du navigateur
      +  request.xhr?                # requête AJAX ?
      +  request.url                 # "http://exemple.com/exemple/foo"
      +  request.path                # "/exemple/foo"
      +  request.ip                  # adresse IP du client
      +  request.secure?             # false
      +  request.forwarded?          # vrai (si on est derrière un proxy inverse)
      +  request.env                 # tableau brut de l'environnement fourni par
      +                              # Rack
      +end
      +
      +
      +
      +

      Certaines options, telles que script_name ou path_info peuvent également être modifiées :

      -
      before { request.path_info = "/" }
       
      -get "/" do
      -  "toutes les requêtes arrivent ici"
      -end
      -
      +
      +
      +
      before { request.path_info = "/" }
      +
      +get "/" do
      +  "toutes les requêtes arrivent ici"
      +end
      +
      +
      +
      +

      request.body est un objet IO ou StringIO :

      -
      post "/api" do
      -  request.body.rewind  # au cas où il a déjà été lu
      -  donnees = JSON.parse request.body.read
      -  "Bonjour #{donnees['nom']} !"
      -end
      -
      + +
      +
      +
      post "/api" do
      +  request.body.rewind  # au cas où il a déjà été lu
      +  donnees = JSON.parse request.body.read
      +  "Bonjour #{donnees['nom']} !"
      +end
      +
      +
      +
      +

      Fichiers joints

      Vous pouvez utiliser la méthode +attachment+ pour indiquer au navigateur que -la réponse devrait être stockée sur le disque plutôt qu'affichée :

      -
      get '/' do
      -  attachment
      -  "enregistre-le !"
      -end
      -
      +la réponse devrait être stockée sur le disque plutôt qu’affichée :

      + +
      +
      +
      get '/' do
      +  attachment
      +  "enregistre-le !"
      +end
      +
      +
      +
      +

      Vous pouvez également lui passer un nom de fichier :

      -
      get '/' do
      -  attachment "info.txt"
      -  "enregistre-le !"
      -end
      -
      + +
      +
      +
      get '/' do
      +  attachment "info.txt"
      +  "enregistre-le !"
      +end
      +
      +
      +
      +

      Gérer Date et Time

      Sinatra fourni un helper +time_for+ pour convertir une valeur donnée en -objet Time. Il peut aussi faire la conversion à partir d'objets +DateTime+, +objet Time. Il peut aussi faire la conversion à partir d’objets +DateTime+, Date ou de classes similaires :

      -
      get '/' do
      -  pass if Time.now > time_for('Dec 23, 2012')
      -  "encore temps"
      -end
      -
      -

      Cette méthode est utilisée en interne par +expires+, +lastmodified+ et + +

      +
      +
      get '/' do
      +  pass if Time.now > time_for('Dec 23, 2012')
      +  "encore temps"
      +end
      +
      +
      +
      + +

      Cette méthode est utilisée en interne par +expires+, +last_modified+ et consorts. Par conséquent, vous pouvez très facilement étendre le -fonctionnement de ces méthodes en surchargeant le helper +timefor+ dans +fonctionnement de ces méthodes en surchargeant le helper +time_for+ dans votre application :

      -
      helpers do
      -  def time_for(value)
      -    case value
      -    when :yesterday then Time.now - 24*60*60
      -    when :tomorrow  then Time.now + 24*60*60
      -    else super
      -    end
      -  end
      -end
      -
      -get '/' do
      -  last_modified :yesterday
      -  expires :tomorrow
      -  "salut"
      -end
      -
      + +
      +
      +
      helpers do
      +  def time_for(value)
      +    case value
      +    when :yesterday then Time.now - 24*60*60
      +    when :tomorrow  then Time.now + 24*60*60
      +    else super
      +    end
      +  end
      +end
      +
      +get '/' do
      +  last_modified :yesterday
      +  expires :tomorrow
      +  "salut"
      +end
      +
      +
      +
      +

      Chercher les fichiers de templates

      La méthode find_template est utilisée pour trouver les fichiers de templates à générer :

      -
      find_template settings.views, 'foo', Tilt[:haml] do |file|
      -  puts "pourrait être #{file}"
      -end
      -
      -

      Ce n'est pas très utilise. En revanche, il est utile de pouvoir surcharger + +

      +
      +
      find_template settings.views, 'foo', Tilt[:haml] do |file|
      +  puts "pourrait être #{file}"
      +end
      +
      +
      +
      + +

      Ce n’est pas très utilise. En revanche, il est utile de pouvoir surcharger cette méthode afin de définir son propre mécanisme de recherche. Par exemple, -vous pouvez utiliser plus d'un répertoire de vues :

      -
      set :views, ['views', 'templates']
      -
      -helpers do
      -  def find_template(views, name, engine, &block)
      -    Array(views).each { |v| super(v, name, engine, &block) }
      -  end
      -end
      -
      -

      Un autre exemple est d'utiliser des répertoires différents pour des moteurs +vous pouvez utiliser plus d’un répertoire de vues :

      + +
      +
      +
      set :views, ['views', 'templates']
      +
      +helpers do
      +  def find_template(views, name, engine, &block)
      +    Array(views).each { |v| super(v, name, engine, &block) }
      +  end
      +end
      +
      +
      +
      + +

      Un autre exemple est d’utiliser des répertoires différents pour des moteurs de rendu différents :

      -
      set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
      -
      -helpers do
      -  def find_template(views, name, engine, &block)
      -    _, folder = views.detect { |k,v| engine == Tilt[k] }
      -    folder ||= views[:default]
      -    super(folder, name, engine, &block)
      -  end
      -end
      -
      + +
      +
      +
      set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
      +
      +helpers do
      +  def find_template(views, name, engine, &block)
      +    _, folder = views.detect { |k,v| engine == Tilt[k] }
      +    folder ||= views[:default]
      +    super(folder, name, engine, &block)
      +  end
      +end
      +
      +
      +
      +

      Vous pouvez également écrire cela dans une extension et la partager avec -d'autres !

      +d’autres !

      Notez que find_template ne vérifie pas que le fichier existe mais -va plutôt exécuter le bloc pour tous les chemins possibles. Cela n'induit pas +va plutôt exécuter le bloc pour tous les chemins possibles. Cela n’induit pas un problème de performance dans le sens où render va utiliser +break+ dès -qu'un fichier est trouvé. De plus, l'emplacement des templates (et leur -contenu) est mis en cache si vous n'êtes pas en mode développement. Vous +qu’un fichier est trouvé. De plus, l’emplacement des templates (et leur +contenu) est mis en cache si vous n’êtes pas en mode développement. Vous devriez garder cela en tête si vous écrivez une méthode vraiment dingue.

      Configuration

      Lancé une seule fois au démarrage de tous les environnements :

      -
      configure do
      -  # définir un paramètre
      -  set :option, 'value'
       
      -  # définir plusieurs paramètre
      -  set :a => 1, :b => 2
      +
      +
      +
      configure do
      +  # définir un paramètre
      +  set :option, 'value'
      +
      +  # définir plusieurs paramètre
      +  set :a => 1, :b => 2
       
      -  # identique à "set :option, true"
      -  enable :option
      +  # identique à "set :option, true"
      +  enable :option
       
      -  # identique à "set :option, false""
      -  disable :option
      +  # identique à "set :option, false""
      +  disable :option
       
      -  # vous pouvez également avoir des paramètres dynamiques avec des blocs
      -  set(:css_dir) { File.join(views, 'css') }
      -end
      -
      -

      Lancé si l'environnement (variable d'environnement RACK_ENV) est défini comme + # vous pouvez également avoir des paramètres dynamiques avec des blocs + set(:css_dir) { File.join(views, 'css') } +end +

      + + + +

      Lancé si l’environnement (variable d’environnement RACK_ENV) est défini comme :production :

      configure :production do - ... + … end

      -

      Lancé si l'environnement est :production ou +

      Lancé si l’environnement est :production ou :test :

      configure :production, :test do - ... + … end

      Vous pouvez accéder à ces paramètres via settings :

      -
      configure do
      +
      +
      configure do
         set :foo, 'bar'
       end
       
      @@ -1627,7 +2201,8 @@ 

      Configuration

      settings.foo # => 'bar' ... end -
      +
      +

      Se protéger des attaques

      @@ -1635,16 +2210,34 @@

      Se protéger des attaques

      pour protéger votre application contre les principales attaques opportunistes. Vous pouvez très simplement désactiver cette fonctionnalité (ce qui exposera votre application à beaucoup de vulnerabilités courantes) :

      -
      disable :protection
      -
      + +
      +
      +
      disable :protection
      +
      +
      +
      +

      Pour désactiver seulement un type de protection, vous pouvez définir protection -avec un hash d'options :

      -
      set :protection, :except => :path_traversal
      -
      +avec un hash d’options :

      + +
      +
      +
      set :protection, :except => :path_traversal
      +
      +
      +
      +

      Vous pouvez également lui passer un tableau pour désactiver plusieurs types de protection :

      -
      set :protection, :except => [:path_traversal, :session_hijacking]
      -
      + +
      +
      +
      set :protection, :except => [:path_traversal, :session_hijacking]
      +
      +
      +
      +

      Paramètres disponibles

      @@ -1794,24 +2387,30 @@

      Environements

      Il existe trois environnements prédéfinis : "development", "production" et "test". Les environements peuvent être -sélectionné via la variable d'environnement +RACK_ENV+. Sa valeur par défaut +sélectionné via la variable d’environnement +RACK_ENV+. Sa valeur par défaut est "development". Dans ce mode, tous les templates sont rechargés à chaque requête. Des handlers spécifiques pour not_found et -error sont installés pour vous permettre d'avoir une pile de trace +error sont installés pour vous permettre d’avoir une pile de trace dans votre navigateur. En mode "production" et "test" les templates sont mis en cache par défaut.

      Pour exécuter votre application dans un environnement différent, utilisez -l'option -e de Ruby :

      -
      $ ruby mon_application.rb -e [ENVIRONMENT]
      -
      +l’option -e de Ruby :

      + +
      +
      +
      $ ruby mon_application.rb -e [ENVIRONMENT]
      +
      +
      +
      +

      Vous pouvez utiliser une des méthodes +development?+, +test?+ et +production?+ -pour déterminer quel est l'environnement en cours.

      +pour déterminer quel est l’environnement en cours.

      Gérer les erreurs

      -

      Les gestionnaires d'erreur s'exécutent dans le même contexte que les routes ou +

      Les gestionnaires d’erreur s’exécutent dans le même contexte que les routes ou les filtres, ce qui veut dire que vous avez accès (entre autres) aux bons vieux haml, erb, halt, etc.

      @@ -1820,49 +2419,85 @@

      NotFound

      Quand une exception Sinatra::NotFound est soulevée, ou que le code retour est 404, le gestionnaire not_found est invoqué :

      -
      not_found do
      -  'Pas moyen de trouver ce que vous cherchez'
      -end
      -
      + +
      +
      +
      not_found do
      +  'Pas moyen de trouver ce que vous cherchez'
      +end
      +
      +
      +
      +

      Error

      -

      Le gestionnaire +error+ est invoqué à chaque fois qu'une exception est -soulevée dans une route ou un filtre. L'objet exception est accessible via la +

      Le gestionnaire +error+ est invoqué à chaque fois qu’une exception est +soulevée dans une route ou un filtre. L’objet exception est accessible via la variable Rack sinatra.error :

      -
      error do
      -  'Désolé mais une méchante erreur est survenue - ' + env['sinatra.error'].name
      -end
      -
      + +
      +
      +
      error do
      +  'Désolé mais une méchante erreur est survenue - ' + env['sinatra.error'].name
      +end
      +
      +
      +
      +

      Erreur sur mesure :

      -
      error MonErreurSurMesure do
      -  'Donc il est arrivé ceci...' + env['sinatra.error'].message
      -end
      -
      + +
      +
      +
      error MonErreurSurMesure do
      +  'Donc il est arrivé ceci...' + env['sinatra.error'].message
      +end
      +
      +
      +
      +

      Donc si ceci arrive :

      -
      get '/' do
      -  raise MonErreurSurMesure, 'quelque chose de mal'
      -end
      -
      + +
      +
      +
      get '/' do
      +  raise MonErreurSurMesure, 'quelque chose de mal'
      +end
      +
      +
      +
      +

      Vous obtenez ça :

      -

      Donc il est arrivé ceci... quelque chose de mal

      +

      Donc il est arrivé ceci… quelque chose de mal

      -

      Alternativement, vous pouvez avoir un gestionnaire d'erreur associé à un code +

      Alternativement, vous pouvez avoir un gestionnaire d’erreur associé à un code particulier :

      -
      error 403 do
      -  'Accès interdit'
      -end
      -
      -get '/secret' do
      -  403
      -end
      -
      + +
      +
      +
      error 403 do
      +  'Accès interdit'
      +end
      +
      +get '/secret' do
      +  403
      +end
      +
      +
      +
      +

      Ou un intervalle :

      -
      error 400..510 do
      -  'Boom'
      -end
      -
      + +
      +
      +
      error 400..510 do
      +  'Boom'
      +end
      +
      +
      +
      +

      Sinatra installe pour vous quelques gestionnaires not_found et error génériques lorsque vous êtes en environnement development.

      @@ -1872,91 +2507,111 @@

      Les Middlewares Rack

      Sinatra tourne avec Rack, une interface standard et minimale pour les web frameworks Ruby. Un des points forts de Rack est le -support de ce que l'on appelle des "middlewares" -- composant qui vient se +support de ce que l’on appelle des “middlewares” – composant qui vient se situer entre le serveur et votre application, et dont le but est de -visualiser/manipuler la requête/réponse HTTP, et d'offrir diverses +visualiser/manipuler la requête/réponse HTTP, et d’offrir diverses fonctionnalités classiques.

      Sinatra permet de construire facilement des middlewares Rack via la méthode de haut niveau +use+ :

      -
      require 'sinatra'
      -require 'mon_middleware_perso'
       
      -use Rack::Lint
      -use MonMiddlewarePerso
      +
      +
      +
      require 'sinatra'
      +require 'mon_middleware_perso'
      +
      +use Rack::Lint
      +use MonMiddlewarePerso
      +
      +get '/bonjour' do
      +  'Bonjour le monde'
      +end
      +
      +
      +
      -get '/bonjour' do - 'Bonjour le monde' -end -

      La sémantique de +use+ est identique à celle définie dans le DSL de Rack::Builder (le plus souvent utilisé dans un fichier rackup). Par exemple, la méthode +use+ accepte divers arguments ainsi que des blocs :

      -
      use Rack::Auth::Basic do |login, password|
      +
      +
      use Rack::Auth::Basic do |login, password|
         login == 'admin' && password == 'secret'
       end
      -
      +
      +

      Rack est distribué avec une bonne variété de middlewares standards pour les -logs, débuguer, faire du routage URL, de l'authentification, gérer des +logs, débuguer, faire du routage URL, de l’authentification, gérer des sessions. Sinatra utilise beaucoup de ces composants automatiquement via la -configuration, donc pour ceux-ci vous n'aurez pas à utiliser la méthode use.

      +configuration, donc pour ceux-ci vous n’aurez pas à utiliser la méthode use.

      Tester

      -

      Les tests pour Sinatra peuvent être écrit avec n'importe quelle bibliothèque +

      Les tests pour Sinatra peuvent être écrit avec n’importe quelle bibliothèque basée sur Rack. Rack::Test est recommandé :

      -
      require 'mon_application_sinatra'
      -require 'test/unit'
      -require 'rack/test'
      -
      -class MonTest < Test::Unit::TestCase
      -  include Rack::Test::Methods
      -
      -  def app
      -    Sinatra::Application
      -  end
      -
      -  def test_ma_racine
      -    get '/'
      -    assert_equal 'Bonjour le monde !', last_response.body
      -  end
      -
      -  def test_avec_des_parametres
      -    get '/rencontrer', :name => 'Frank'
      -    assert_equal 'Salut Frank !', last_response.body
      -  end
      -
      -  def test_avec_rack_env
      -    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      -    assert_equal "Vous utilisez Songbird !", last_response.body
      -  end
      -end
      -
      + +
      +
      +
      require 'mon_application_sinatra'
      +require 'test/unit'
      +require 'rack/test'
      +
      +class MonTest < Test::Unit::TestCase
      +  include Rack::Test::Methods
      +
      +  def app
      +    Sinatra::Application
      +  end
      +
      +  def test_ma_racine
      +    get '/'
      +    assert_equal 'Bonjour le monde !', last_response.body
      +  end
      +
      +  def test_avec_des_parametres
      +    get '/rencontrer', :name => 'Frank'
      +    assert_equal 'Salut Frank !', last_response.body
      +  end
      +
      +  def test_avec_rack_env
      +    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      +    assert_equal "Vous utilisez Songbird !", last_response.body
      +  end
      +end
      +
      +
      +
      +

      Sinatra::Base - Les Middlewares, Bibliothèques, et Applications Modulaires

      Définir votre application au niveau supérieur fonctionne bien dans le cas des -micro-applications mais présente pas mal d'inconvénients pour créer des +micro-applications mais présente pas mal d’inconvénients pour créer des composants réutilisables sous forme de middlewares Rack, de Rails metal, de -simples librairies avec un composant serveur ou même d'extensions Sinatra. Le +simples librairies avec un composant serveur ou même d’extensions Sinatra. Le niveau supérieur suppose une configuration dans le style des micro-applications -(une application d'un seul fichier, des répertoires ./public et -./views, des logs, une page d'erreur, etc...). C'est là que +(une application d’un seul fichier, des répertoires ./public et +./views, des logs, une page d’erreur, etc…). C’est là que Sinatra::Base prend tout son intérêt :

      -
      require 'sinatra/base'
       
      -class MonApplication < Sinatra::Base
      -  set :sessions, true
      -  set :foo, 'bar'
      +
      +
      +
      require 'sinatra/base'
      +
      +class MonApplication < Sinatra::Base
      +  set :sessions, true
      +  set :foo, 'bar'
      +
      +  get '/' do
      +    'Bonjour le monde !'
      +  end
      +end
      +
      +
      +
      - get '/' do - 'Bonjour le monde !' - end -end -

      Les méthodes de la classe Sinatra::Base sont parfaitement identiques à celles disponibles via le DSL de haut niveau. Il suffit de deux modifications pour transformer la plupart des applications de haut niveau en un composant @@ -1964,37 +2619,37 @@

      Sinatra::Base - Les Middlewares, Bibliothèques, et Applications Modulaires<
      • Votre fichier doit charger sinatra/base au lieu de sinatra, sinon toutes -les méthodes du DSL Sinatra seront importées dans l'espace de nom principal.
      • -
      • Les gestionnaires de routes, la gestion d'erreur, les filtres et les options +les méthodes du DSL Sinatra seront importées dans l’espace de nom principal.
      • +
      • Les gestionnaires de routes, la gestion d’erreur, les filtres et les options doivent être placés dans une classe héritant de Sinatra::Base.

      Sinatra::Base est une page blanche. La plupart des options sont désactivées par défaut, y compris le serveur intégré. Reportez-vous à Options et Configuration -pour plus d'informations sur les options et leur fonctionnement.

      +pour plus d’informations sur les options et leur fonctionnement.

      Style modulaire vs. style classique

      -

      Contrairement aux idées reçues, il n'y a rien de mal à utiliser le style -classique. Si c'est ce qui convient pour votre application, vous n'avez pas +

      Contrairement aux idées reçues, il n’y a rien de mal à utiliser le style +classique. Si c’est ce qui convient pour votre application, vous n’avez pas aucune raison de passer à une application modulaire.

      Le principal inconvénient du style classique sur le style modulaire est que vous -ne pouvez avoir qu'une application Ruby par processus Ruby. Si vous pensez en +ne pouvez avoir qu’une application Ruby par processus Ruby. Si vous pensez en utiliser plus, passez au style modulaire. Et rien ne vous empêche de mixer style classique et style modulaire.

      -

      Si vous passez d'un style à l'autre, souvenez-vous des quelques différences +

      Si vous passez d’un style à l’autre, souvenez-vous des quelques différences mineures en ce qui concerne les paramètres par défaut :

      Paramètre Classique Modulaire

      -

      appfile fichier chargeant sinatra fichier héritant de Sinatra::Base - run $0 == appfile false +

      app_file fichier chargeant sinatra fichier héritant de Sinatra::Base + run $0 == app_file false logging true false - methodoverride true false - inlinetemplates true false + method_override true false + inline_templates true false static true false

      @@ -2002,43 +2657,79 @@

      Servir une application modulaire

      Il y a deux façons de faire pour démarrer une application modulaire, démarrez avec run! :

      -
      # my_app.rb
      -require 'sinatra/base'
       
      -class MyApp < Sinatra::Base
      -  # ... code de l'application ici ...
      +
      +
      +
      # my_app.rb
      +require 'sinatra/base'
      +
      +class MyApp < Sinatra::Base
      +  # ... code de l'application ici ...
      +
      +  # démarre le serveur si ce fichier est directement exécuté
      +  run! if app_file == $0
      +end
      +
      +
      +
      - # démarre le serveur si ce fichier est directement exécuté - run! if app_file == $0 -end -

      Démarrez ensuite avec :

      -
      $ ruby my_app.rb
      -
      -

      Ou alors avec un fichier config.ru, qui permet d'utiliser n'importe + +

      +
      +
      $ ruby my_app.rb
      +
      +
      +
      + +

      Ou alors avec un fichier config.ru, qui permet d’utiliser n’importe quel gestionnaire Rack :

      -
      # config.ru
      -require './my_app'
      -run MyApp
      -
      + +
      +
      +
      # config.ru
      +require './my_app'
      +run MyApp
      +
      +
      +
      +

      Exécutez :

      -
      $ rackup -p 4567
      -
      + +
      +
      +
      $ rackup -p 4567
      +
      +
      +
      +

      Utiliser une application de style classique avec un fichier config.ru

      Ecrivez votre application :

      -
      # app.rb
      -require 'sinatra'
       
      -get '/' do
      -  'Bonjour le monde !'
      -end
      -
      +
      +
      +
      # app.rb
      +require 'sinatra'
      +
      +get '/' do
      +  'Bonjour le monde !'
      +end
      +
      +
      +
      +

      Et un fichier config.ru correspondant :

      -
      require './app'
      -run Sinatra::Application
      -
      + +
      +
      +
      require './app'
      +run Sinatra::Application
      +
      +
      +
      +

      Quand utiliser un fichier config.ru ?

      @@ -2046,178 +2737,214 @@

      Quand utiliser un fichier config.ru ?

      • Vous souhaitez déployer avec un autre gestionnaire Rack (Passenger, Unicorn, -Heroku, ...).
      • -
      • Vous souhaitez utiliser plus d'une sous-classe de Sinatra::Base.
      • -
      • Vous voulez utiliser Sinatra comme un middleware, non en tant que +Heroku, …).
      • +
      • Vous souhaitez utiliser plus d’une sous-classe de Sinatra::Base.
      • +
      • Vous voulez utiliser Sinatra comme un middleware, non en tant que endpoint.
      -

      Il n'est pas nécessaire de passer par un fichier config.ru pour la -seule raison que vous êtes passé au style modulaire, et vous n'avez pas besoin +

      Il n’est pas nécessaire de passer par un fichier config.ru pour la +seule raison que vous êtes passé au style modulaire, et vous n’avez pas besoin de passer au style modulaire pour utiliser un fichier config.ru.

      Utiliser Sinatra comme Middleware

      -

      Non seulement Sinatra peut utiliser d'autres middlewares Rack, il peut -également être à son tour utilisé au-dessus de n'importe quel endpoint Rack +

      Non seulement Sinatra peut utiliser d’autres middlewares Rack, il peut +également être à son tour utilisé au-dessus de n’importe quel endpoint Rack en tant que middleware. Ce endpoint peut très bien être une autre -application Sinatra, ou n'importe quelle application basée sur Rack -(Rails/Ramaze/Camping/...) :

      -
      require 'sinatra/base'
      -
      -class EcranDeConnexion < Sinatra::Base
      -  enable :sessions
      -
      -  get('/connexion') { haml :connexion }
      -
      -  post('/connexion') do
      -    if params[:nom] = 'admin' && params[:motdepasse] = 'admin'
      -      session['nom_utilisateur'] = params[:nom]
      -    else
      -      redirect '/connexion'
      -    end
      -  end
      -end
      -
      -class MonApp < Sinatra::Base
      -  # le middleware sera appelé avant les filtres
      -  use EcranDeConnexion
      -
      -  before do
      -    unless session['nom_utilisateur']
      -      halt "Accès refusé, merci de vous <a href='/connexion'>connecter</a>."
      -    end
      -  end
      -
      -  get('/') { "Bonjour #{session['nom_utilisateur']}." }
      -end
      -
      - -

      Création dynamique d'applications

      - -

      Il se peut que vous ayez besoin de créer une nouvelle application à l'exécution +application Sinatra, ou n’importe quelle application basée sur Rack +(Rails/Ramaze/Camping/…) :

      + +
      +
      +
      require 'sinatra/base'
      +
      +class EcranDeConnexion < Sinatra::Base
      +  enable :sessions
      +
      +  get('/connexion') { haml :connexion }
      +
      +  post('/connexion') do
      +    if params[:nom] = 'admin' && params[:motdepasse] = 'admin'
      +      session['nom_utilisateur'] = params[:nom]
      +    else
      +      redirect '/connexion'
      +    end
      +  end
      +end
      +
      +class MonApp < Sinatra::Base
      +  # le middleware sera appelé avant les filtres
      +  use EcranDeConnexion
      +
      +  before do
      +    unless session['nom_utilisateur']
      +      halt "Accès refusé, merci de vous <a href='/connexion'>connecter</a>."
      +    end
      +  end
      +
      +  get('/') { "Bonjour #{session['nom_utilisateur']}." }
      +end
      +
      +
      +
      + + +

      Création dynamique d’applications

      + +

      Il se peut que vous ayez besoin de créer une nouvelle application à l’exécution sans avoir à les assigner à une constante, vous pouvez le faire grâce à Sinatra.new :

      -
      require 'sinatra/base'
      -mon_app = Sinatra.new { get('/') { "salut" } }
      -mon_app.run!
      -
      -

      L'application dont elle hérite peut être passé en argument optionnel :

      -
      # config.ru
      -require 'sinatra/base'
      -
      -controleur = Sinatra.new do
      -  enable :logging
      -  helpers MyHelpers
      -end
      -
      -map('/a') do
      -  run Sinatra.new(controleur) { get('/') { 'a' } }
      -end
      -
      -map('/b') do
      -  run Sinatra.new(controleur) { get('/') { 'b' } }
      -end
      -
      -

      C'est notamment utile pour tester des extensions à Sinatra ou bien pour + +

      +
      +
      require 'sinatra/base'
      +mon_app = Sinatra.new { get('/') { "salut" } }
      +mon_app.run!
      +
      +
      +
      + +

      L’application dont elle hérite peut être passé en argument optionnel :

      + +
      +
      +
      # config.ru
      +require 'sinatra/base'
      +
      +controleur = Sinatra.new do
      +  enable :logging
      +  helpers MyHelpers
      +end
      +
      +map('/a') do
      +  run Sinatra.new(controleur) { get('/') { 'a' } }
      +end
      +
      +map('/b') do
      +  run Sinatra.new(controleur) { get('/') { 'b' } }
      +end
      +
      +
      +
      + +

      C’est notamment utile pour tester des extensions à Sinatra ou bien pour utiliser Sinatra dans votre propre bibliothèque.

      -

      Cela permet également d'utiliser très facilement Sinatra comme middleware :

      -
      require 'sinatra/base'
      +

      Cela permet également d’utiliser très facilement Sinatra comme middleware :

      + +
      +
      +
      require 'sinatra/base'
      +
      +use Sinatra do
      +  get('/') { ... }
      +end
       
      -use Sinatra do
      -  get('/') { ... }
      -end
      +run RailsProject::Application
      +
      +
      +
      -run RailsProject::Application -

      Contextes et Binding

      Le contexte dans lequel vous êtes détermine les méthodes et variables disponibles.

      - -

      Contexte de l'application/classe

      + +

      Contexte de l’application/classe

      Toute application Sinatra correspond à une sous-classe de Sinatra::Base. Si vous utilisez le DSL haut niveau (require 'sinatra'), alors cette -classe est Sinatra::Application, sinon il s'agit de la sous-classe que +classe est Sinatra::Application, sinon il s’agit de la sous-classe que vous avez définie. Dans le contexte de la classe, vous avez accès aux méthodes -telles que get ou before, mais vous n'avez pas accès aux objets +request+ -ou +session+ car c'est la même classe d'application qui traitera toutes les +telles que get ou before, mais vous n’avez pas accès aux objets +request+ +ou +session+ car c’est la même classe d’application qui traitera toutes les requêtes.

      Les options définies au moyen de +set+ deviennent des méthodes de classe :

      -
      class MonApp < Sinatra::Base
      -  # Eh, je suis dans le contexte de l'application !
      -  set :foo, 42
      -  foo # => 42
      -
      -  get '/foo' do
      -    # Eh, je ne suis plus dans le contexte de l'application !
      -  end
      -end
      -
      -

      Vous avez le binding du contexte de l'application dans :

      + +
      +
      +
      class MonApp < Sinatra::Base
      +  # Eh, je suis dans le contexte de l'application !
      +  set :foo, 42
      +  foo # => 42
      +
      +  get '/foo' do
      +    # Eh, je ne suis plus dans le contexte de l'application !
      +  end
      +end
      +
      +
      +
      + +

      Vous avez le binding du contexte de l’application dans :

        -
      • Le corps de la classe d'application
      • -
      • Les méthodes définies par les extensions
      • -
      • Le bloc passé à helpers +
      • Le corps de la classe d’application
      • +
      • Les méthodes définies par les extensions
      • +
      • Le bloc passé à helpers
      • -
      • Les procs/blocs utilisés comme argument pour set +
      • Les procs/blocs utilisés comme argument pour set
      • -
      • Le bloc passé à Sinatra.new +
      • Le bloc passé à Sinatra.new

      Vous pouvez atteindre ce contexte (donc la classe) de la façon suivante :

        -
      • Via l'objet passé dans les blocs configure (configure { |c| ... })
      • -
      • En utilisant settings dans le contexte de la requête
      • +
      • Via l’objet passé dans les blocs configure (configure { |c| ... })
      • +
      • En utilisant settings dans le contexte de la requête

      Contexte de la requête/instance

      -

      Pour tout traitement d'une requête, une nouvelle instance de votre classe -d'application est créée et tous vos gestionnaires sont exécutés dans ce +

      Pour tout traitement d’une requête, une nouvelle instance de votre classe +d’application est créée et tous vos gestionnaires sont exécutés dans ce contexte. Dans ce dernier, vous pouvez accéder aux objets request et session et faire appel aux fonctions de rendu telles que erb ou haml. -Vous pouvez accéder au contexte de l'application depuis le contexte de la +Vous pouvez accéder au contexte de l’application depuis le contexte de la requête au moyen de settings :

      -
      class MonApp < Sinatra::Base
      -  # Eh, je suis dans le contexte de l'application !
      -  get '/ajouter_route/:nom' do
      -    # Contexte de la requête pour '/ajouter_route/:nom'
      -    @value = 42
      -
      -    settings.get("/#{params[:nom]}") do
      -      # Contexte de la requête pour "/#{params[:nom]}"
      -      @value # => nil (on est pas au sein de la même requête)
      -    end
      -
      -    "Route ajoutée !"
      -  end
      -end
      -
      + +
      +
      +
      class MonApp < Sinatra::Base
      +  # Eh, je suis dans le contexte de l'application !
      +  get '/ajouter_route/:nom' do
      +    # Contexte de la requête pour '/ajouter_route/:nom'
      +    @value = 42
      +
      +    settings.get("/#{params[:nom]}") do
      +      # Contexte de la requête pour "/#{params[:nom]}"
      +      @value # => nil (on est pas au sein de la même requête)
      +    end
      +
      +    "Route ajoutée !"
      +  end
      +end
      +
      +
      +
      +

      Vous avez le binding du contexte de la requête dans :

      • les blocs get/head/post/put/delete/options
      • -
      • les filtres before/after
      • -
      • les méthodes utilitaires (définies au moyen de helpers)
      • -
      • les vues/templates
      • +
      • les filtres before/after
      • +
      • les méthodes utilitaires (définies au moyen de helpers)
      • +
      • les vues/templates

      Le contexte de délégation

      Le contexte de délégation se contente de transmettre les appels de méthodes au contexte de classe. Toutefois, il ne se comporte pas à 100% comme le contexte -de classe car vous n'avez pas le binding de la classe : seules les méthodes -spécifiquement déclarées pour délégation sont disponibles et il n'est pas +de classe car vous n’avez pas le binding de la classe : seules les méthodes +spécifiquement déclarées pour délégation sont disponibles et il n’est pas possible de partager des variables/états avec le contexte de classe -(comprenez : self n'est pas le même). Vous pouvez ajouter des délégation de +(comprenez : self n’est pas le même). Vous pouvez ajouter des délégation de méthodes en appelant Sinatra::Delegator.delegate :method_name.

      Vous avez le binding du contexte de délégation dans :

      @@ -2225,27 +2952,35 @@

      Le contexte de délégation

      • Le binding de haut niveau, si vous avez utilisé require "sinatra"
      • -
      • Un objet qui inclut le module Sinatra::Delegator +
      • Un objet qui inclut le module Sinatra::Delegator

      Jetez un oeil pour vous faire une idée : voici le mixin Sinatra::Delegator -qui étend l'objet principal.

      +qui étend l’objet principal.

      Ligne de commande

      Les applications Sinatra peuvent être lancées directement :

      -
      $ ruby mon_application.rb [-h] [-x] [-e ENVIRONNEMENT] [-p PORT] [-o HOTE] [-s SERVEUR]
      -
      + +
      +
      +
      $ ruby mon_application.rb [-h] [-x] [-e ENVIRONNEMENT] [-p PORT] [-o HOTE] [-s SERVEUR]
      +
      +
      +
      +

      Les options sont :

      -
      -h # aide
      +
      +
      -h # aide
       -p # déclare le port (4567 par défaut)
       -o # déclare l'hôte (0.0.0.0 par défaut)
       -e # déclare l'environnement (+development+ par défaut)
       -s # déclare le serveur/gestionnaire à utiliser (thin par défaut)
       -x # active le mutex lock (off par défaut)
      -
      +
      +

      Configuration nécessaire

      @@ -2276,12 +3011,17 @@

      Configuration nécessaire

      Rubinius
      -
      Rubinius est officiellement supporté (Rubinius - -
      -
      JRuby
      -
      JRuby est officiellement supporté (JRuby -
      +
      Rubinius est officiellement supporté (Rubinius <= 1.2.4), tout + fonctionne, y compris tous les langages de template. La version 2.0 à + venir est également supportée.
      + +
      JRuby
      +
      JRuby est officiellement supporté (JRuby <= 1.6.5). Aucune anomalie + avec des bibliothèques de templates tierces ne sont connues. Toutefois, si + vous choisissez JRuby, alors tournez vous vers des gestionnaires Rack JRuby + car le serveur Thin n’est pas complètement supporté par JRuby. Le + support des extensions C dans JRuby est encore expérimental, ce qui + n’affecte que RDiscount, Redcarpet and RedCloth pour l’instant.

      Nous gardons également un oeil sur les versions Ruby à venir.

      @@ -2290,37 +3030,43 @@

      Configuration nécessaire

      • Versions plus anciennes de JRuby et Rubinius
      • -
      • Ruby Enterprise Edition
      • -
      • MacRuby, Maglev, IronRuby
      • -
      • Ruby 1.9.0 et 1.9.1 (mais nous déconseillons leur utilisation)
      • +
      • Ruby Enterprise Edition
      • +
      • MacRuby, Maglev, IronRuby
      • +
      • Ruby 1.9.0 et 1.9.1 (mais nous déconseillons leur utilisation)

      Le fait de ne pas être officiellement supporté signifie que si quelque chose -ne fonctionne pas uniquement sur cette plateforme alors c'est un problème de la +ne fonctionne pas uniquement sur cette plateforme alors c’est un problème de la plateforme et pas un bug de Sinatra.

      Nous lançons également notre intégration continue (CI) avec ruby-head (la future 2.0.0) et la branche 1.9.4, mais étant donné les évolutions continuelles, -nous ne pouvont rien garantir, si ce n'est que les versions 1.9.4p0 et 2.0.0p0 +nous ne pouvont rien garantir, si ce n’est que les versions 1.9.4p0 et 2.0.0p0 seront supportées.

      -

      Sinatra devrait fonctionner sur n'importe quel système d'exploitation -supportant l'implémentation Ruby choisie.

      +

      Sinatra devrait fonctionner sur n’importe quel système d’exploitation +supportant l’implémentation Ruby choisie.

      -

      Il n'est pas possible d'utiliser Sinatra sur Cardinal, SmallRuby, Blueuby ou -toute version de Ruby antérieure à 1.8.7 à l'heure actuelle.

      +

      Il n’est pas possible d’utiliser Sinatra sur Cardinal, SmallRuby, Blueuby ou +toute version de Ruby antérieure à 1.8.7 à l’heure actuelle.

      Essuyer les plâtres

      -

      Si vous voulez utiliser la toute dernière version de Sinatra, n'ayez pas peur +

      Si vous voulez utiliser la toute dernière version de Sinatra, n’ayez pas peur de faire tourner votre application sur la branche master, cela devrait être stable.

      Nous publions également une gem de +prerelease+ de temps en temps que vous pouvez installer comme suit :

      -
      $ gem install sinatra --pre
      -
      -

      afin d'avoir les toutes dernières fonctionnalités.

      + +
      +
      +
      $ gem install sinatra --pre
      +
      +
      +
      + +

      afin d’avoir les toutes dernières fonctionnalités.

      Avec Bundler

      @@ -2328,52 +3074,88 @@

      Avec Bundler

      Si vous voulez faire tourner votre application avec le tout dernier Sinatra, Bundler est recommandé.

      -

      Tout d'abord, installer bundler si vous ne l'avez pas :

      -
      $ gem install bundler
      -
      +

      Tout d’abord, installer bundler si vous ne l’avez pas :

      + +
      +
      +
      $ gem install bundler
      +
      +
      +
      +

      Ensuite, dans le dossier de votre projet, créez un fichier +Gemfile+ :

      -
      source :rubygems
      -gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
      -
      -# autres dépendances
      -gem 'haml'                    # par exemple, si vous utilisez haml
      -gem 'activerecord', '~> 3.0'  # peut-être que vous avez également besoin
      -                              # de ActiveRecord 3.x
      -
      + +
      +
      +
      source :rubygems
      +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
      +
      +# autres dépendances
      +gem 'haml'                    # par exemple, si vous utilisez haml
      +gem 'activerecord', '~> 3.0'  # peut-être que vous avez également besoin
      +                              # de ActiveRecord 3.x
      +
      +
      +
      +

      Notez que vous aurez à lister toutes les dépendances de votre application dans ce fichier. Les dépendances directes de Sinatra (Rack et Tilt) seront automatiquement téléchargées et ajoutées par Bundler.

      Maintenant, vous pouvez faire tourner votre application de la façon suivante :

      -
      $ bundle exec ruby myapp.rb
      -
      + +
      +
      +
      $ bundle exec ruby myapp.rb
      +
      +
      +
      +

      Faites le vous-même

      Créez un clone local et démarrez votre application avec le dossier sinatra/lib dans le $LOAD_PATH :

      -
      $ cd myapp
      -$ git clone git://github.com/sinatra/sinatra.git
      -$ ruby -Isinatra/lib myapp.rb
       
      -A l'avenir, pour mettre à jour le code source de Sinatra :
      +
      +
      +
      $ cd myapp
      +$ git clone git://github.com/sinatra/sinatra.git
      +$ ruby -Isinatra/lib myapp.rb
       
      -```bash
      +A l'avenir, pour mettre à jour le code source de Sinatra :
      +
      +~~~~bash
       $ cd myapp/sinatra
       $ git pull
      -
      +
      + + +

      Installez globalement

      Vous pouvez construire la gem vous-même :

      -
      $ git clone git://github.com/sinatra/sinatra.git
      -$ cd sinatra
      -$ rake sinatra.gemspec
      -$ rake install
      -
      + +
      +
      +
      $ git clone git://github.com/sinatra/sinatra.git
      +$ cd sinatra
      +$ rake sinatra.gemspec
      +$ rake install
      +
      +
      +
      +

      Si vous installez les gems en tant que +root+, la dernière étape sera :

      -
      $ sudo rake install
      -
      + +
      +
      +
      $ sudo rake install
      +
      +
      +
      +

      Versions

      @@ -2386,23 +3168,25 @@

      Mais encore

      diff --git a/_includes/README.html b/_includes/README.html index 4111bd89..9b95676e 100644 --- a/_includes/README.html +++ b/_includes/README.html @@ -92,7 +92,6 @@
      1. With Bundler
      2. Roll Your Own
      3. -
      4. Install Globally
    17. Versioning
    18. Further Reading
    19. @@ -103,17 +102,29 @@

      Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort:

      -
      # myapp.rb
      -require 'sinatra'
       
      -get '/' do
      -  'Hello world!'
      -end
      -
      +
      +
      +
      # myapp.rb
      +require 'sinatra'
      +
      +get '/' do
      +  'Hello world!'
      +end
      +
      +
      +
      +

      Install the gem and run with:

      -
      gem install sinatra
      +
      +
      +
      +
      gem install sinatra
       ruby myapp.rb
      -
      +
      + + +

      View at: http://localhost:4567

      It is recommended to also run gem install thin, which Sinatra will @@ -126,86 +137,134 @@

      Routes

      In Sinatra, a route is an HTTP method paired with a URL-matching pattern. Each route is associated with a block:

      -
      get '/' do
      -  .. show something ..
      -end
      -
      -post '/' do
      -  .. create something ..
      -end
      -
      -put '/' do
      -  .. replace something ..
      -end
      -
      -patch '/' do
      -  .. modify something ..
      -end
      -
      -delete '/' do
      -  .. annihilate something ..
      -end
      -
      -options '/' do
      -  .. appease something ..
      -end
      -
      -link '/' do
      -  .. affiliate something ..
      -end
      -
      -unlink '/' do
      -  .. separate something ..
      -end
      -
      + +
      +
      +
      get '/' do
      +  .. show something ..
      +end
      +
      +post '/' do
      +  .. create something ..
      +end
      +
      +put '/' do
      +  .. replace something ..
      +end
      +
      +patch '/' do
      +  .. modify something ..
      +end
      +
      +delete '/' do
      +  .. annihilate something ..
      +end
      +
      +options '/' do
      +  .. appease something ..
      +end
      +
      +link '/' do
      +  .. affiliate something ..
      +end
      +
      +unlink '/' do
      +  .. separate something ..
      +end
      +
      +
      +
      +

      Routes are matched in the order they are defined. The first route that matches the request is invoked.

      Route patterns may include named parameters, accessible via the params hash:

      -
      get '/hello/:name' do
      -  # matches "GET /hello/foo" and "GET /hello/bar"
      -  # params[:name] is 'foo' or 'bar'
      -  "Hello #{params[:name]}!"
      -end
      -
      + +
      +
      +
      get '/hello/:name' do
      +  # matches "GET /hello/foo" and "GET /hello/bar"
      +  # params[:name] is 'foo' or 'bar'
      +  "Hello #{params[:name]}!"
      +end
      +
      +
      +
      +

      You can also access named parameters via block parameters:

      -
      get '/hello/:name' do |n|
      -  "Hello #{n}!"
      -end
      -
      + +
      +
      +
      get '/hello/:name' do |n|
      +  "Hello #{n}!"
      +end
      +
      +
      +
      +

      Route patterns may also include splat (or wildcard) parameters, accessible via the params[:splat] array:

      -
      get '/say/*/to/*' do
      -  # matches /say/hello/to/world
      -  params[:splat] # => ["hello", "world"]
      -end
      -
      -get '/download/*.*' do
      -  # matches /download/path/to/file.xml
      -  params[:splat] # => ["path/to/file", "xml"]
      -end
      -
      + +
      +
      +
      get '/say/*/to/*' do
      +  # matches /say/hello/to/world
      +  params[:splat] # => ["hello", "world"]
      +end
      +
      +get '/download/*.*' do
      +  # matches /download/path/to/file.xml
      +  params[:splat] # => ["path/to/file", "xml"]
      +end
      +
      +
      +
      +

      Or with block parameters:

      -
      get '/download/*.*' do |path, ext|
      -  [path, ext] # => ["path/to/file", "xml"]
      -end
      -
      + +
      +
      +
      get '/download/*.*' do |path, ext|
      +  [path, ext] # => ["path/to/file", "xml"]
      +end
      +
      +
      +
      +

      Route matching with Regular Expressions:

      -
      get %r{/hello/([\w]+)} do
      -  "Hello, #{params[:captures].first}!"
      -end
      -
      + +
      +
      +
      get %r{/hello/([\w]+)} do
      +  "Hello, #{params[:captures].first}!"
      +end
      +
      +
      +
      +

      Or with a block parameter:

      -
      get %r{/hello/([\w]+)} do |c|
      -  "Hello, #{c}!"
      -end
      -
      + +
      +
      +
      get %r{/hello/([\w]+)} do |c|
      +  "Hello, #{c}!"
      +end
      +
      +
      +
      +

      Route patterns may have optional parameters:

      -
      get '/posts.?:format?' do
      -  # matches "GET /posts" and any extension "GET /posts.json", "GET /posts.xml" etc.
      -end
      -
      + +
      +
      +
      get '/posts.?:format?' do
      +  # matches "GET /posts" and any extension "GET /posts.json", "GET /posts.xml" etc.
      +end
      +
      +
      +
      +

      By the way, unless you disable the path traversal attack protection (see below), the request path might be modified before matching against your routes.

      @@ -213,55 +272,79 @@

      Routes

      Conditions

      Routes may include a variety of matching conditions, such as the user agent:

      -
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      -  "You're using Songbird version #{params[:agent][0]}"
      -end
      -
      -get '/foo' do
      -  # Matches non-songbird browsers
      -end
      -
      + +
      +
      +
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      +  "You're using Songbird version #{params[:agent][0]}"
      +end
      +
      +get '/foo' do
      +  # Matches non-songbird browsers
      +end
      +
      +
      +
      +

      Other available conditions are host_name and provides:

      -
      get '/', :host_name => /^admin\./ do
      -  "Admin Area, Access denied!"
      -end
      -
      -get '/', :provides => 'html' do
      -  haml :index
      -end
      -
      -get '/', :provides => ['rss', 'atom', 'xml'] do
      -  builder :feed
      -end
      -
      + +
      +
      +
      get '/', :host_name => /^admin\./ do
      +  "Admin Area, Access denied!"
      +end
      +
      +get '/', :provides => 'html' do
      +  haml :index
      +end
      +
      +get '/', :provides => ['rss', 'atom', 'xml'] do
      +  builder :feed
      +end
      +
      +
      +
      +

      You can easily define your own conditions:

      -
      set(:probability) { |value| condition { rand <= value } }
       
      -get '/win_a_car', :probability => 0.1 do
      -  "You won!"
      -end
      +
      +
      +
      set(:probability) { |value| condition { rand <= value } }
      +
      +get '/win_a_car', :probability => 0.1 do
      +  "You won!"
      +end
      +
      +get '/win_a_car' do
      +  "Sorry, you lost."
      +end
      +
      +
      +
      -get '/win_a_car' do - "Sorry, you lost." -end -

      For a condition that takes multiple values use a splat:

      -
      set(:auth) do |*roles|   # <- notice the splat here
      -  condition do
      -    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      -      redirect "/login/", 303
      -    end
      -  end
      -end
      -
      -get "/my/account/", :auth => [:user, :admin] do
      -  "Your Account Details"
      -end
      -
      -get "/only/admin/", :auth => :admin do
      -  "Only admins are allowed here!"
      -end
      -
      + +
      +
      +
      set(:auth) do |*roles|   # <- notice the splat here
      +  condition do
      +    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      +      redirect "/login/", 303
      +    end
      +  end
      +end
      +
      +get "/my/account/", :auth => [:user, :admin] do
      +  "Your Account Details"
      +end
      +
      +get "/only/admin/", :auth => :admin do
      +  "Only admins are allowed here!"
      +end
      +
      +
      +
      +

      Return Values

      @@ -277,22 +360,28 @@

      Return Values

    20. An Array with three elements: [status (Fixnum), headers (Hash), response body (responds to #each)]
    21. -
    22. An Array with two elements: [status (Fixnum), response body (responds to +
    23. An Array with two elements: [status (Fixnum), response body (responds to #each)]
    24. -
    25. An object that responds to #each and passes nothing but strings to +
    26. An object that responds to #each and passes nothing but strings to the given block
    27. -
    28. A Fixnum representing the status code
    29. +
    30. A Fixnum representing the status code
    31. That way we can, for instance, easily implement a streaming example:

      -
      class Stream
      -  def each
      -    100.times { |i| yield "#{i}\n" }
      -  end
      -end
      -
      -get('/') { Stream.new }
      -
      + +
      +
      +
      class Stream
      +  def each
      +    100.times { |i| yield "#{i}\n" }
      +  end
      +end
      +
      +get('/') { Stream.new }
      +
      +
      +
      +

      You can also use the stream helper method (described below) to reduce boiler plate and embed the streaming logic in the route.

      @@ -302,46 +391,70 @@

      Custom Route Matchers

      As shown above, Sinatra ships with built-in support for using String patterns and regular expressions as route matches. However, it does not stop there. You can easily define your own matchers:

      -
      class AllButPattern
      -  Match = Struct.new(:captures)
      -
      -  def initialize(except)
      -    @except   = except
      -    @captures = Match.new([])
      -  end
      -
      -  def match(str)
      -    @captures unless @except === str
      -  end
      -end
      -
      -def all_but(pattern)
      -  AllButPattern.new(pattern)
      -end
      -
      -get all_but("/index") do
      -  # ...
      -end
      -
      + +
      +
      +
      class AllButPattern
      +  Match = Struct.new(:captures)
      +
      +  def initialize(except)
      +    @except   = except
      +    @captures = Match.new([])
      +  end
      +
      +  def match(str)
      +    @captures unless @except === str
      +  end
      +end
      +
      +def all_but(pattern)
      +  AllButPattern.new(pattern)
      +end
      +
      +get all_but("/index") do
      +  # ...
      +end
      +
      +
      +
      +

      Note that the above example might be over-engineered, as it can also be expressed as:

      -
      get // do
      -  pass if request.path_info == "/index"
      -  # ...
      -end
      -
      + +
      +
      +
      get // do
      +  pass if request.path_info == "/index"
      +  # ...
      +end
      +
      +
      +
      +

      Or, using negative look ahead:

      -
      get %r{^(?!/index$)} do
      -  # ...
      -end
      -
      + +
      +
      +
      get %r{^(?!/index$)} do
      +  # ...
      +end
      +
      +
      +
      +

      Static Files

      Static files are served from the ./public directory. You can specify a different location by setting the :public_folder option:

      -
      set :public_folder, File.dirname(__FILE__) + '/static'
      -
      + +
      +
      +
      set :public_folder, File.dirname(__FILE__) + '/static'
      +
      +
      +
      +

      Note that the public directory name is not included in the URL. A file ./public/css/style.css is made available as http://example.com/css/style.css.

      @@ -354,40 +467,70 @@

      Views / Templates

      Each template language is exposed via its own rendering method. These methods simply return a string:

      -
      get '/' do
      -  erb :index
      -end
      -
      + +
      +
      +
      get '/' do
      +  erb :index
      +end
      +
      +
      +
      +

      This renders views/index.erb.

      Instead of a template name, you can also just pass in the template content directly:

      -
      get '/' do
      -  code = "<%= Time.now %>"
      -  erb code
      -end
      -
      + +
      +
      +
      get '/' do
      +  code = "<%= Time.now %>"
      +  erb code
      +end
      +
      +
      +
      +

      Templates take a second argument, the options hash:

      -
      get '/' do
      -  erb :index, :layout => :post
      -end
      -
      + +
      +
      +
      get '/' do
      +  erb :index, :layout => :post
      +end
      +
      +
      +
      +

      This will render views/index.erb embedded in the views/post.erb (default is views/layout.erb, if it exists).

      Any options not understood by Sinatra will be passed on to the template engine:

      -
      get '/' do
      -  haml :index, :format => :html5
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml :index, :format => :html5
      +end
      +
      +
      +
      +

      You can also set options per template language in general:

      -
      set :haml, :format => :html5
       
      -get '/' do
      -  haml :index
      -end
      -
      +
      +
      +
      set :haml, :format => :html5
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      +

      Options passed to the render method override options set via set.

      Available Options:

      @@ -396,7 +539,7 @@

      Views / Templates

      locals
      List of locals passed to the document. Handy with partials. - Example: erb "", :locals => {:foo => "bar"} + Example: erb "<%= foo %>", :locals => {:foo => "bar"}
      default_encoding
      @@ -442,19 +585,25 @@

      Views / Templates

      Templates are assumed to be located directly under the ./views directory. To use a different views directory: -set :views, settings.root + '/templates'

      +set :views, settings.root + ‘/templates’

      One important thing to remember is that you always have to reference templates -with symbols, even if they're in a subdirectory (in this case, use: -'subdir/template'). You must use a symbol because otherwise rendering +with symbols, even if they’re in a subdirectory (in this case, use: +‘subdir/template’). You must use a symbol because otherwise rendering methods will render any strings passed to them directly.

      Literal Templates

      -
      get '/' do
      -  haml '%div.title Hello World'
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml '%div.title Hello World'
      +end
      +
      +
      +
      +

      Renders the template string.

      @@ -462,9 +611,15 @@

      Available Template Languages

      Some languages have multiple implementations. To specify what implementation to use (and to be thread-safe), you should simply require it first:

      -
      require 'rdiscount' # or require 'bluecloth'
      -get('/') { markdown :index }
      -
      + +
      +
      +
      require 'rdiscount' # or require 'bluecloth'
      +get('/') { markdown :index }
      +
      +
      +
      +

      Haml Templates

      @@ -643,12 +798,24 @@

      Markdown Templates

      It is not possible to call methods from markdown, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine:

      -
      erb :overview, :locals => { :text => markdown(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => markdown(:introduction) }
      +
      +
      +
      +

      Note that you may also call the markdown method from within other templates:

      -
      %h1 Hello From Haml!
      -%p= markdown(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= markdown(:greetings)
      +
      +
      +
      +

      Since you cannot call Ruby from Markdown, you cannot use layouts written in Markdown. However, it is possible to use another rendering engine for the template than for the layout by passing the :layout_engine option.

      @@ -672,12 +839,24 @@

      Textile Templates

      It is not possible to call methods from textile, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine:

      -
      erb :overview, :locals => { :text => textile(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => textile(:introduction) }
      +
      +
      +
      +

      Note that you may also call the textile method from within other templates:

      -
      %h1 Hello From Haml!
      -%p= textile(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= textile(:greetings)
      +
      +
      +
      +

      Since you cannot call Ruby from Textile, you cannot use layouts written in Textile. However, it is possible to use another rendering engine for the template than for the layout by passing the :layout_engine option.

      @@ -701,12 +880,24 @@

      RDoc Templates

      It is not possible to call methods from rdoc, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine:

      -
      erb :overview, :locals => { :text => rdoc(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => rdoc(:introduction) }
      +
      +
      +
      +

      Note that you may also call the rdoc method from within other templates:

      -
      %h1 Hello From Haml!
      -%p= rdoc(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= rdoc(:greetings)
      +
      +
      +
      +

      Since you cannot call Ruby from RDoc, you cannot use layouts written in RDoc. However, it is possible to use another rendering engine for the template than for the layout by passing the :layout_engine option.

      @@ -803,12 +994,24 @@

      Creole Templates

      It is not possible to call methods from creole, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine:

      -
      erb :overview, :locals => { :text => creole(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => creole(:introduction) }
      +
      +
      +
      +

      Note that you may also call the creole method from within other templates:

      -
      %h1 Hello From Haml!
      -%p= creole(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= creole(:greetings)
      +
      +
      +
      +

      Since you cannot call Ruby from Creole, you cannot use layouts written in Creole. However, it is possible to use another rendering engine for the template than for the layout by passing the :layout_engine option.

      @@ -863,14 +1066,20 @@

      Stylus Templates

      Before being able to use Stylus templates, you need to load stylus and stylus/tilt first:

      -
      require 'sinatra'
      -require 'stylus'
      -require 'stylus/tilt'
      -
      -get '/' do
      -  stylus :example
      -end
      -
      + +
      +
      +
      require 'sinatra'
      +require 'stylus'
      +require 'stylus/tilt'
      +
      +get '/' do
      +  stylus :example
      +end
      +
      +
      +
      +

      Yajl Templates

      @@ -897,13 +1106,25 @@

      Yajl Templates

      The template source is evaluated as a Ruby string, and the resulting json variable is converted using #to_json:

      -
      json = { :foo => 'bar' }
      -json[:baz] = key
      -
      + +
      +
      +
      json = { :foo => 'bar' }
      +json[:baz] = key
      +
      +
      +
      +

      The :callback and :variable options can be used to decorate the rendered object:

      -
      var resource = {"foo":"bar","baz":"qux"}; present(resource);
      -
      + +
      +
      +
      var resource = {"foo":"bar","baz":"qux"}; present(resource);
      +
      +
      +
      +

      WLang Templates

      @@ -929,17 +1150,29 @@

      Accessing Variables in Templates

      Templates are evaluated within the same context as route handlers. Instance variables set in route handlers are directly accessible by templates:

      -
      get '/:id' do
      -  @foo = Foo.find(params[:id])
      -  haml '%h1= @foo.name'
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  @foo = Foo.find(params[:id])
      +  haml '%h1= @foo.name'
      +end
      +
      +
      +
      +

      Or, specify an explicit Hash of local variables:

      -
      get '/:id' do
      -  foo = Foo.find(params[:id])
      -  haml '%h1= bar.name', :locals => { :bar => foo }
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  foo = Foo.find(params[:id])
      +  haml '%h1= bar.name', :locals => { :bar => foo }
      +end
      +
      +
      +
      +

      This is typically used when rendering templates as partials from within other templates.

      @@ -949,48 +1182,72 @@

      Templates with yield and nested layouts

      A layout is usually just a template that calls yield. Such a template can by used either through the :template option as described above, or it can be rendered with a block as follows:

      -
      erb :post, :layout => false do
      -  erb :index
      -end
      -
      + +
      +
      +
      erb :post, :layout => false do
      +  erb :index
      +end
      +
      +
      +
      +

      This code is mostly equivalent to erb :index, :layout => :post.

      Passing blocks to rendering methods is most useful for creating nested layouts:

      -
      erb :main_layout, :layout => false do
      -  erb :admin_layout do
      -    erb :user
      -  end
      -end
      -
      + +
      +
      +
      erb :main_layout, :layout => false do
      +  erb :admin_layout do
      +    erb :user
      +  end
      +end
      +
      +
      +
      +

      This can also be done in fewer lines of code with:

      -
      erb :admin_layout, :layout => :main_layout do
      -  erb :user
      -end
      -
      + +
      +
      +
      erb :admin_layout, :layout => :main_layout do
      +  erb :user
      +end
      +
      +
      +
      +

      Currently the following rendering method accept a block: erb, haml, -liquid, slim, wlang. +liquid, slim , wlang. Also the general render method accepts a block.

      Inline Templates

      Templates may be defined at the end of the source file:

      -
      require 'sinatra'
       
      -get '/' do
      -  haml :index
      -end
      +
      +
      +
      require 'sinatra'
       
      -__END__
      +get '/' do
      +  haml :index
      +end
      +
      +__END__
       
       @@ layout
       %html
         = yield
       
       @@ index
      -%div.title Hello world.
      -
      +%div.title Hello world. +
      + + +

      NOTE: Inline templates defined in the source file that requires sinatra are automatically loaded. Call enable :inline_templates explicitly if you have inline templates in other source files.

      @@ -999,48 +1256,72 @@

      Inline Templates

      Named Templates

      Templates may also be defined using the top-level template method:

      -
      template :layout do
      -  "%html\n  =yield\n"
      -end
      -
      -template :index do
      -  '%div.title Hello World!'
      -end
      -
      -get '/' do
      -  haml :index
      -end
      -
      -

      If a template named "layout" exists, it will be used each time a template + +

      +
      +
      template :layout do
      +  "%html\n  =yield\n"
      +end
      +
      +template :index do
      +  '%div.title Hello World!'
      +end
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      + +

      If a template named “layout” exists, it will be used each time a template is rendered. You can individually disable layouts by passing :layout => false or disable them by default via set :haml, :layout => false:

      -
      get '/' do
      -  haml :index, :layout => !request.xhr?
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml :index, :layout => !request.xhr?
      +end
      +
      +
      +
      +

      Associating File Extensions

      To associate a file extension with a template engine, use Tilt.register. For instance, if you like to use the file extension tt for Textile templates, you can do the following:

      -
      Tilt.register :tt, Tilt[:textile]
      -
      + +
      +
      +
      Tilt.register :tt, Tilt[:textile]
      +
      +
      +
      +

      Adding Your Own Template Engine

      First, register your engine with Tilt, then create a rendering method:

      -
      Tilt.register :myat, MyAwesomeTemplateEngine
       
      -helpers do
      -  def myat(*args) render(:myat, *args) end
      -end
      +
      +
      +
      Tilt.register :myat, MyAwesomeTemplateEngine
      +
      +helpers do
      +  def myat(*args) render(:myat, *args) end
      +end
      +
      +get '/' do
      +  myat :index
      +end
      +
      +
      +
      -get '/' do - myat :index -end -

      Renders ./views/index.myat. See https://github.com/rtomayko/tilt to learn more about Tilt.

      @@ -1050,72 +1331,108 @@

      Filters

      Before filters are evaluated before each request within the same context as the routes will be and can modify the request and response. Instance variables set in filters are accessible by routes and templates:

      -
      before do
      -  @note = 'Hi!'
      -  request.path_info = '/foo/bar/baz'
      -end
      -
      -get '/foo/*' do
      -  @note #=> 'Hi!'
      -  params[:splat] #=> 'bar/baz'
      -end
      -
      + +
      +
      +
      before do
      +  @note = 'Hi!'
      +  request.path_info = '/foo/bar/baz'
      +end
      +
      +get '/foo/*' do
      +  @note #=> 'Hi!'
      +  params[:splat] #=> 'bar/baz'
      +end
      +
      +
      +
      +

      After filters are evaluated after each request within the same context and can also modify the request and response. Instance variables set in before filters and routes are accessible by after filters:

      -
      after do
      -  puts response.status
      -end
      -
      + +
      +
      +
      after do
      +  puts response.status
      +end
      +
      +
      +
      +

      Note: Unless you use the body method rather than just returning a String from the routes, the body will not yet be available in the after filter, since it is generated later on.

      Filters optionally take a pattern, causing them to be evaluated only if the request path matches that pattern:

      -
      before '/protected/*' do
      -  authenticate!
      -end
      -
      -after '/create/:slug' do |slug|
      -  session[:last_slug] = slug
      -end
      -
      + +
      +
      +
      before '/protected/*' do
      +  authenticate!
      +end
      +
      +after '/create/:slug' do |slug|
      +  session[:last_slug] = slug
      +end
      +
      +
      +
      +

      Like routes, filters also take conditions:

      -
      before :agent => /Songbird/ do
      -  # ...
      -end
      -
      -after '/blog/*', :host_name => 'example.com' do
      -  # ...
      -end
      -
      + +
      +
      +
      before :agent => /Songbird/ do
      +  # ...
      +end
      +
      +after '/blog/*', :host_name => 'example.com' do
      +  # ...
      +end
      +
      +
      +
      +

      Helpers

      Use the top-level helpers method to define helper methods for use in route handlers and templates:

      -
      helpers do
      -  def bar(name)
      -    "#{name}bar"
      -  end
      -end
      -
      -get '/:name' do
      -  bar(params[:name])
      -end
      -
      + +
      +
      +
      helpers do
      +  def bar(name)
      +    "#{name}bar"
      +  end
      +end
      +
      +get '/:name' do
      +  bar(params[:name])
      +end
      +
      +
      +
      +

      Alternatively, helper methods can be separately defined in a module:

      -
      module FooUtils
      -  def foo(name) "#{name}foo" end
      -end
       
      -module BarUtils
      -  def bar(name) "#{name}bar" end
      -end
      +
      +
      +
      module FooUtils
      +  def foo(name) "#{name}foo" end
      +end
      +
      +module BarUtils
      +  def bar(name) "#{name}bar" end
      +end
      +
      +helpers FooUtils, BarUtils
      +
      +
      +
      -helpers FooUtils, BarUtils -

      The effect is the same as including the modules in the application class.

      @@ -1123,75 +1440,141 @@

      Using Sessions

      A session is used to keep state during requests. If activated, you have one session hash per user session:

      -
      enable :sessions
       
      -get '/' do
      -  "value = " << session[:value].inspect
      -end
      +
      +
      +
      enable :sessions
      +
      +get '/' do
      +  "value = " << session[:value].inspect
      +end
      +
      +get '/:value' do
      +  session[:value] = params[:value]
      +end
      +
      +
      +
      -get '/:value' do - session[:value] = params[:value] -end -

      Note that enable :sessions actually stores all data in a cookie. This might not always be what you want (storing lots of data will increase your traffic, for instance). You can use any Rack session middleware: in order to do so, do not call enable :sessions, but instead pull in your middleware of choice as you would any other middleware:

      -
      use Rack::Session::Pool, :expire_after => 2592000
       
      -get '/' do
      -  "value = " << session[:value].inspect
      -end
      +
      +
      +
      use Rack::Session::Pool, :expire_after => 2592000
      +
      +get '/' do
      +  "value = " << session[:value].inspect
      +end
      +
      +get '/:value' do
      +  session[:value] = params[:value]
      +end
      +
      +
      +
      -get '/:value' do - session[:value] = params[:value] -end -

      To improve security, the session data in the cookie is signed with a session secret. A random secret is generated for you by Sinatra. However, since this secret will change with every start of your application, you might want to set the secret yourself, so all your application instances share it:

      -
      set :session_secret, 'super secret'
      -
      + +
      +
      +
      set :session_secret, 'super secret'
      +
      +
      +
      +

      If you want to configure it further, you may also store a hash with options in the sessions setting:

      -
      set :sessions, :domain => 'foo.com'
      -
      + +
      +
      +
      set :sessions, :domain => 'foo.com'
      +
      +
      +
      +

      Halting

      To immediately stop a request within a filter or route use:

      -
      halt
      -
      + +
      +
      +
      halt
      +
      +
      +
      +

      You can also specify the status when halting:

      -
      halt 410
      -
      + +
      +
      +
      halt 410
      +
      +
      +
      +

      Or the body:

      -
      halt 'this will be the body'
      -
      + +
      +
      +
      halt 'this will be the body'
      +
      +
      +
      +

      Or both:

      -
      halt 401, 'go away!'
      -
      + +
      +
      +
      halt 401, 'go away!'
      +
      +
      +
      +

      With headers:

      -
      halt 402, {'Content-Type' => 'text/plain'}, 'revenge'
      -
      + +
      +
      +
      halt 402, {'Content-Type' => 'text/plain'}, 'revenge'
      +
      +
      +
      +

      It is of course possible to combine a template with halt:

      -
      halt erb(:error)
      -
      + +
      +
      +
      halt erb(:error)
      +
      +
      +
      +

      Passing

      A route can punt processing to the next matching route using pass:

      -
      get '/guess/:who' do
      -  pass unless params[:who] == 'Frank'
      -  'You got me!'
      -end
      -
      -get '/guess/*' do
      -  'You missed!'
      -end
      -
      + +
      +
      +
      get '/guess/:who' do
      +  pass unless params[:who] == 'Frank'
      +  'You got me!'
      +end
      +
      +get '/guess/*' do
      +  'You missed!'
      +end
      +
      +
      +
      +

      The route block is immediately exited and control continues with the next matching route. If no matching route is found, a 404 is returned.

      @@ -1200,15 +1583,21 @@

      Triggering Another Route

      Sometimes pass is not what you want, instead you would like to get the result of calling another route. Simply use call to achieve this:

      -
      get '/foo' do
      -  status, headers, body = call env.merge("PATH_INFO" => '/bar')
      -  [status, headers, body.map(&:upcase)]
      -end
      -
      -get '/bar' do
      -  "bar"
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  status, headers, body = call env.merge("PATH_INFO" => '/bar')
      +  [status, headers, body.map(&:upcase)]
      +end
      +
      +get '/bar' do
      +  "bar"
      +end
      +
      +
      +
      +

      Note that in the example above, you would ease testing and increase performance by simply moving "bar" into a helper used by both /foo and /bar.

      @@ -1226,26 +1615,38 @@

      Setting Body, Status Code and Headers

      set the body at an arbitrary point in the execution flow. You can do so with the body helper method. If you do so, you can use that method from there on to access the body:

      -
      get '/foo' do
      -  body "bar"
      -end
      -
      -after do
      -  puts body
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  body "bar"
      +end
      +
      +after do
      +  puts body
      +end
      +
      +
      +
      +

      It is also possible to pass a block to body, which will be executed by the -Rack handler (this can be used to implement streaming, see "Return Values").

      +Rack handler (this can be used to implement streaming, see “Return Values”).

      Similar to the body, you can also set the status code and headers:

      -
      get '/foo' do
      -  status 418
      -  headers \
      -    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
      -    "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
      -  body "I'm a tea pot!"
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  status 418
      +  headers \
      +    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
      +    "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
      +  body "I'm a tea pot!"
      +end
      +
      +
      +
      +

      Like body, headers and status with no arguments can be used to access their current values.

      @@ -1256,16 +1657,22 @@

      Streaming Responses

      the response body. In extreme examples, you want to keep sending data until the client closes the connection. You can use the stream helper to avoid creating your own wrapper:

      -
      get '/' do
      -  stream do |out|
      -    out << "It's gonna be legen -\n"
      -    sleep 0.5
      -    out << " (wait for it) \n"
      -    sleep 1
      -    out << "- dary!\n"
      -  end
      -end
      -
      + +
      +
      +
      get '/' do
      +  stream do |out|
      +    out << "It's gonna be legen -\n"
      +    sleep 0.5
      +    out << " (wait for it) \n"
      +    sleep 1
      +    out << "- dary!\n"
      +  end
      +end
      +
      +
      +
      +

      This allows you to implement streaming APIs, Server Sent Events and can be used as the basis for WebSockets. It can also be @@ -1282,57 +1689,75 @@

      Streaming Responses

      the stream object, allowing you to close it at any later point in the execution flow. This only works on evented servers, like Thin and Rainbows. Other servers will still close the stream:

      -
      # long polling
       
      -set :server, :thin
      -connections = []
      +
      +
      +
      # long polling
      +
      +set :server, :thin
      +connections = []
      +
      +get '/subscribe' do
      +  # register a client's interest in server events
      +  stream(:keep_open) { |out| connections << out }
       
      -get '/subscribe' do
      -  # register a client's interest in server events
      -  stream(:keep_open) { |out| connections << out }
      +  # purge dead connections
      +  connections.reject!(&:closed?)
       
      -  # purge dead connections
      -  connections.reject!(&:closed?)
      +  # acknowledge
      +  "subscribed"
      +end
       
      -  # acknowledge
      -  "subscribed"
      -end
      +post '/message' do
      +  connections.each do |out|
      +    # notify client that a new message has arrived
      +    out << params[:message] << "\n"
       
      -post '/message' do
      -  connections.each do |out|
      -    # notify client that a new message has arrived
      -    out << params[:message] << "\n"
      +    # indicate client to connect again
      +    out.close
      +  end
       
      -    # indicate client to connect again
      -    out.close
      -  end
      +  # acknowledge
      +  "message received"
      +end
      +
      +
      +
      - # acknowledge - "message received" -end -

      Logging

      In the request scope, the logger helper exposes a Logger instance:

      -
      get '/' do
      -  logger.info "loading data"
      -  # ...
      -end
      -
      -

      This logger will automatically take your Rack handler's logging settings into + +

      +
      +
      get '/' do
      +  logger.info "loading data"
      +  # ...
      +end
      +
      +
      +
      + +

      This logger will automatically take your Rack handler’s logging settings into account. If logging is disabled, this method will return a dummy object, so you do not have to worry in your routes and filters about it.

      Note that logging is only enabled for Sinatra::Application by default, so if you inherit from Sinatra::Base, you probably want to enable it yourself:

      -
      class MyApp < Sinatra::Base
      -  configure :production, :development do
      -    enable :logging
      -  end
      -end
      -
      + +
      +
      +
      class MyApp < Sinatra::Base
      +  configure :production, :development do
      +    enable :logging
      +  end
      +end
      +
      +
      +
      +

      To avoid any logging middleware to be set up, set the logging setting to nil. However, keep in mind that logger will in that case return nil. A common use case is when you want to set your own logger. Sinatra will use @@ -1342,24 +1767,42 @@

      Logging

      Mime Types

      When using send_file or static files you may have mime types Sinatra -doesn't understand. Use mime_type to register them by file extension:

      -
      configure do
      -  mime_type :foo, 'text/foo'
      -end
      -
      +doesn’t understand. Use mime_type to register them by file extension:

      + +
      +
      +
      configure do
      +  mime_type :foo, 'text/foo'
      +end
      +
      +
      +
      +

      You can also use it with the content_type helper:

      -
      get '/' do
      -  content_type :foo
      -  "foo foo foo"
      -end
      -
      + +
      +
      +
      get '/' do
      +  content_type :foo
      +  "foo foo foo"
      +end
      +
      +
      +
      +

      Generating URLs

      For generating URLs you should use the url helper method, for instance, in Haml:

      -
      %a{:href => url('/foo')} foo
      -
      + +
      +
      +
      %a{:href => url('/foo')} foo
      +
      +
      +
      +

      It takes reverse proxies and Rack routers into account, if present.

      This method is also aliased to to (see below for an example).

      @@ -1368,91 +1811,157 @@

      Generating URLs

      Browser Redirect

      You can trigger a browser redirect with the redirect helper method:

      -
      get '/foo' do
      -  redirect to('/bar')
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  redirect to('/bar')
      +end
      +
      +
      +
      +

      Any additional parameters are handled like arguments passed to halt:

      -
      redirect to('/bar'), 303
      -redirect 'http://google.com', 'wrong place, buddy'
      -
      + +
      +
      +
      redirect to('/bar'), 303
      +redirect 'http://google.com', 'wrong place, buddy'
      +
      +
      +
      +

      You can also easily redirect back to the page the user came from with redirect back:

      -
      get '/foo' do
      -  "<a href='/bar'>do something</a>"
      -end
      -
      -get '/bar' do
      -  do_something
      -  redirect back
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  "<a href='/bar'>do something</a>"
      +end
      +
      +get '/bar' do
      +  do_something
      +  redirect back
      +end
      +
      +
      +
      +

      To pass arguments with a redirect, either add them to the query:

      -
      redirect to('/bar?sum=42')
      -
      + +
      +
      +
      redirect to('/bar?sum=42')
      +
      +
      +
      +

      Or use a session:

      -
      enable :sessions
       
      -get '/foo' do
      -  session[:secret] = 'foo'
      -  redirect to('/bar')
      -end
      +
      +
      +
      enable :sessions
      +
      +get '/foo' do
      +  session[:secret] = 'foo'
      +  redirect to('/bar')
      +end
      +
      +get '/bar' do
      +  session[:secret]
      +end
      +
      +
      +
      -get '/bar' do - session[:secret] -end -

      Cache Control

      Setting your headers correctly is the foundation for proper HTTP caching.

      You can easily set the Cache-Control header like this:

      -
      get '/' do
      -  cache_control :public
      -  "cache it!"
      -end
      -
      + +
      +
      +
      get '/' do
      +  cache_control :public
      +  "cache it!"
      +end
      +
      +
      +
      +

      Pro tip: Set up caching in a before filter:

      -
      before do
      -  cache_control :public, :must_revalidate, :max_age => 60
      -end
      -
      + +
      +
      +
      before do
      +  cache_control :public, :must_revalidate, :max_age => 60
      +end
      +
      +
      +
      +

      If you are using the expires helper to set the corresponding header, Cache-Control will be set automatically for you:

      -
      before do
      -  expires 500, :public, :must_revalidate
      -end
      -
      + +
      +
      +
      before do
      +  expires 500, :public, :must_revalidate
      +end
      +
      +
      +
      +

      To properly use caches, you should consider using etag or last_modified. It is recommended to call those helpers before doing any heavy lifting, as they will immediately flush a response if the client already has the current version in its cache:

      -
      get '/article/:id' do
      -  @article = Article.find params[:id]
      -  last_modified @article.updated_at
      -  etag @article.sha1
      -  erb :article
      -end
      -
      + +
      +
      +
      get '/article/:id' do
      +  @article = Article.find params[:id]
      +  last_modified @article.updated_at
      +  etag @article.sha1
      +  erb :article
      +end
      +
      +
      +
      +

      It is also possible to use a weak ETag:

      -
      etag @article.sha1, :weak
      -
      + +
      +
      +
      etag @article.sha1, :weak
      +
      +
      +
      +

      These helpers will not do any caching for you, but rather feed the necessary information to your cache. If you are looking for a quick reverse-proxy caching solution, try rack-cache:

      -
      require "rack/cache"
      -require "sinatra"
       
      -use Rack::Cache
      +
      +
      +
      require "rack/cache"
      +require "sinatra"
      +
      +use Rack::Cache
      +
      +get '/' do
      +  cache_control :public, :max_age => 36000
      +  sleep 5
      +  "hello"
      +end
      +
      +
      +
      -get '/' do - cache_control :public, :max_age => 36000 - sleep 5 - "hello" -end -

      Use the :static_cache_control setting (see below) to add Cache-Control header info to static files.

      @@ -1462,26 +1971,50 @@

      Cache Control

      and idempotent (like put) requests are already in existence, whereas other resources (for instance for post requests), are treated as new resources. You can change this behavior by passing in a :new_resource option:

      -
      get '/create' do
      -  etag '', :new_resource => true
      -  Article.create
      -  erb :new_article
      -end
      -
      + +
      +
      +
      get '/create' do
      +  etag '', :new_resource => true
      +  Article.create
      +  erb :new_article
      +end
      +
      +
      +
      +

      If you still want to use a weak ETag, pass in a :kind option:

      -
      etag '', :new_resource => true, :kind => :weak
      -
      + +
      +
      +
      etag '', :new_resource => true, :kind => :weak
      +
      +
      +
      +

      Sending Files

      For sending files, you can use the send_file helper method:

      -
      get '/' do
      -  send_file 'foo.png'
      -end
      -
      + +
      +
      +
      get '/' do
      +  send_file 'foo.png'
      +end
      +
      +
      +
      +

      It also takes options:

      -
      send_file 'foo.png', :type => :jpg
      -
      + +
      +
      +
      send_file 'foo.png', :type => :jpg
      +
      +
      +
      +

      The options are:

      @@ -1517,128 +2050,188 @@

      Accessing the Request Object

      The incoming request object can be accessed from request level (filter, routes, error handlers) through the request method:

      -
      # app running on http://example.com/example
      -get '/foo' do
      -  t = %w[text/css text/html application/javascript]
      -  request.accept              # ['text/html', '*/*']
      -  request.accept? 'text/xml'  # true
      -  request.preferred_type(t)   # 'text/html'
      -  request.body                # request body sent by the client (see below)
      -  request.scheme              # "http"
      -  request.script_name         # "/example"
      -  request.path_info           # "/foo"
      -  request.port                # 80
      -  request.request_method      # "GET"
      -  request.query_string        # ""
      -  request.content_length      # length of request.body
      -  request.media_type          # media type of request.body
      -  request.host                # "example.com"
      -  request.get?                # true (similar methods for other verbs)
      -  request.form_data?          # false
      -  request["some_param"]       # value of some_param parameter. [] is a shortcut to the params hash.
      -  request.referrer            # the referrer of the client or '/'
      -  request.user_agent          # user agent (used by :agent condition)
      -  request.cookies             # hash of browser cookies
      -  request.xhr?                # is this an ajax request?
      -  request.url                 # "http://example.com/example/foo"
      -  request.path                # "/example/foo"
      -  request.ip                  # client IP address
      -  request.secure?             # false (would be true over ssl)
      -  request.forwarded?          # true (if running behind a reverse proxy)
      -  request.env                 # raw env hash handed in by Rack
      -end
      -
      + +
      +
      +
      # app running on http://example.com/example
      +get '/foo' do
      +  t = %w[text/css text/html application/javascript]
      +  request.accept              # ['text/html', '*/*']
      +  request.accept? 'text/xml'  # true
      +  request.preferred_type(t)   # 'text/html'
      +  request.body                # request body sent by the client (see below)
      +  request.scheme              # "http"
      +  request.script_name         # "/example"
      +  request.path_info           # "/foo"
      +  request.port                # 80
      +  request.request_method      # "GET"
      +  request.query_string        # ""
      +  request.content_length      # length of request.body
      +  request.media_type          # media type of request.body
      +  request.host                # "example.com"
      +  request.get?                # true (similar methods for other verbs)
      +  request.form_data?          # false
      +  request["some_param"]       # value of some_param parameter. [] is a shortcut to the params hash.
      +  request.referrer            # the referrer of the client or '/'
      +  request.user_agent          # user agent (used by :agent condition)
      +  request.cookies             # hash of browser cookies
      +  request.xhr?                # is this an ajax request?
      +  request.url                 # "http://example.com/example/foo"
      +  request.path                # "/example/foo"
      +  request.ip                  # client IP address
      +  request.secure?             # false (would be true over ssl)
      +  request.forwarded?          # true (if running behind a reverse proxy)
      +  request.env                 # raw env hash handed in by Rack
      +end
      +
      +
      +
      +

      Some options, like script_name or path_info, can also be written:

      -
      before { request.path_info = "/" }
       
      -get "/" do
      -  "all requests end up here"
      -end
      -
      +
      +
      +
      before { request.path_info = "/" }
      +
      +get "/" do
      +  "all requests end up here"
      +end
      +
      +
      +
      +

      The request.body is an IO or StringIO object:

      -
      post "/api" do
      -  request.body.rewind  # in case someone already read it
      -  data = JSON.parse request.body.read
      -  "Hello #{data['name']}!"
      -end
      -
      + +
      +
      +
      post "/api" do
      +  request.body.rewind  # in case someone already read it
      +  data = JSON.parse request.body.read
      +  "Hello #{data['name']}!"
      +end
      +
      +
      +
      +

      Attachments

      You can use the attachment helper to tell the browser the response should be stored on disk rather than displayed in the browser:

      -
      get '/' do
      -  attachment
      -  "store it!"
      -end
      -
      + +
      +
      +
      get '/' do
      +  attachment
      +  "store it!"
      +end
      +
      +
      +
      +

      You can also pass it a file name:

      -
      get '/' do
      -  attachment "info.txt"
      -  "store it!"
      -end
      -
      + +
      +
      +
      get '/' do
      +  attachment "info.txt"
      +  "store it!"
      +end
      +
      +
      +
      +

      Dealing with Date and Time

      Sinatra offers a time_for helper method that generates a Time object from the given value. It is also able to convert DateTime, Date and similar classes:

      -
      get '/' do
      -  pass if Time.now > time_for('Dec 23, 2012')
      -  "still time"
      -end
      -
      + +
      +
      +
      get '/' do
      +  pass if Time.now > time_for('Dec 23, 2012')
      +  "still time"
      +end
      +
      +
      +
      +

      This method is used internally by expires, last_modified and akin. You can therefore easily extend the behavior of those methods by overriding time_for in your application:

      -
      helpers do
      -  def time_for(value)
      -    case value
      -    when :yesterday then Time.now - 24*60*60
      -    when :tomorrow  then Time.now + 24*60*60
      -    else super
      -    end
      -  end
      -end
      -
      -get '/' do
      -  last_modified :yesterday
      -  expires :tomorrow
      -  "hello"
      -end
      -
      + +
      +
      +
      helpers do
      +  def time_for(value)
      +    case value
      +    when :yesterday then Time.now - 24*60*60
      +    when :tomorrow  then Time.now + 24*60*60
      +    else super
      +    end
      +  end
      +end
      +
      +get '/' do
      +  last_modified :yesterday
      +  expires :tomorrow
      +  "hello"
      +end
      +
      +
      +
      +

      Looking Up Template Files

      The find_template helper is used to find template files for rendering:

      -
      find_template settings.views, 'foo', Tilt[:haml] do |file|
      -  puts "could be #{file}"
      -end
      -
      + +
      +
      +
      find_template settings.views, 'foo', Tilt[:haml] do |file|
      +  puts "could be #{file}"
      +end
      +
      +
      +
      +

      This is not really useful. But it is useful that you can actually override this method to hook in your own lookup mechanism. For instance, if you want to be able to use more than one view directory:

      -
      set :views, ['views', 'templates']
      -
      -helpers do
      -  def find_template(views, name, engine, &block)
      -    Array(views).each { |v| super(v, name, engine, &block) }
      -  end
      -end
      -
      + +
      +
      +
      set :views, ['views', 'templates']
      +
      +helpers do
      +  def find_template(views, name, engine, &block)
      +    Array(views).each { |v| super(v, name, engine, &block) }
      +  end
      +end
      +
      +
      +
      +

      Another example would be using different directories for different engines:

      -
      set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
      -
      -helpers do
      -  def find_template(views, name, engine, &block)
      -    _, folder = views.detect { |k,v| engine == Tilt[k] }
      -    folder ||= views[:default]
      -    super(folder, name, engine, &block)
      -  end
      -end
      -
      + +
      +
      +
      set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
      +
      +helpers do
      +  def find_template(views, name, engine, &block)
      +    _, folder = views.detect { |k,v| engine == Tilt[k] }
      +    folder ||= views[:default]
      +    super(folder, name, engine, &block)
      +  end
      +end
      +
      +
      +
      +

      You can also easily wrap this up in an extension and share with others!

      Note that find_template does not check if the file really exists but @@ -1652,46 +2245,70 @@

      Looking Up Template Files

      Configuration

      Run once, at startup, in any environment:

      -
      configure do
      -  # setting one option
      -  set :option, 'value'
       
      -  # setting multiple options
      -  set :a => 1, :b => 2
      +
      +
      +
      configure do
      +  # setting one option
      +  set :option, 'value'
       
      -  # same as `set :option, true`
      -  enable :option
      +  # setting multiple options
      +  set :a => 1, :b => 2
       
      -  # same as `set :option, false`
      -  disable :option
      +  # same as `set :option, true`
      +  enable :option
      +
      +  # same as `set :option, false`
      +  disable :option
      +
      +  # you can also have dynamic settings with blocks
      +  set(:css_dir) { File.join(views, 'css') }
      +end
      +
      +
      +
      - # you can also have dynamic settings with blocks - set(:css_dir) { File.join(views, 'css') } -end -

      Run only when the environment (RACK_ENV environment variable) is set to :production:

      -
      configure :production do
      -  ...
      -end
      -
      + +
      +
      +
      configure :production do
      +  ...
      +end
      +
      +
      +
      +

      Run when the environment is set to either :production or :test:

      -
      configure :production, :test do
      -  ...
      -end
      -
      + +
      +
      +
      configure :production, :test do
      +  ...
      +end
      +
      +
      +
      +

      You can access those options via settings:

      -
      configure do
      -  set :foo, 'bar'
      -end
      -
      -get '/' do
      -  settings.foo? # => true
      -  settings.foo  # => 'bar'
      -  ...
      -end
      -
      + +
      +
      +
      configure do
      +  set :foo, 'bar'
      +end
      +
      +get '/' do
      +  settings.foo? # => true
      +  settings.foo  # => 'bar'
      +  ...
      +end
      +
      +
      +
      +

      Configuring attack protection

      @@ -1700,20 +2317,43 @@

      Configuring attack protection

      your application against common, opportunistic attacks. You can easily disable this behavior (which will open up your application to tons of common vulnerabilities):

      -
      disable :protection
      -
      + +
      +
      +
      disable :protection
      +
      +
      +
      +

      To skip a single defense layer, set protection to an options hash:

      -
      set :protection, :except => :path_traversal
      -
      + +
      +
      +
      set :protection, :except => :path_traversal
      +
      +
      +

      You can also hand in an array in order to disable a list of protections:

      -
      set :protection, :except => [:path_traversal, :session_hijacking]
      -
      + +
      +
      +
      set :protection, :except => [:path_traversal, :session_hijacking]
      +
      +
      +
      +

      By default, Sinatra will only set up session based protection if :sessions has been enabled. Sometimes you want to set up sessions on your own, though. In that case you can get it to set up session based protections by passing the :session option:

      -
      use Rack::Session::Pool
      -set :protection, :session => true
      -
      + +
      +
      +
      use Rack::Session::Pool
      +set :protection, :session => true
      +
      +
      +
      +

      Available Settings

      @@ -1734,9 +2374,8 @@

      Available Settings

      mime types the content_type helper will automatically add the charset info to. You should add to it rather than overriding this option: - settings.add_charsets - -
      + settings.add_charsets << "application/foobar" +
      app_file
      @@ -1897,18 +2536,30 @@

      Environments

      In the "production" and "test" environments, templates are cached by default.

      To run different environments, set the RACK_ENV environment variable:

      -
      RACK_ENV=production ruby my_app.rb
      -
      + +
      +
      +
      RACK_ENV=production ruby my_app.rb
      +
      +
      +
      +

      You can use predefined methods: development?, test? and production? to check the current environment setting:

      -
      get '/' do
      -  if settings.development?
      -    "development!"
      -  else
      -    "not development!"
      -  end
      -end
      -
      + +
      +
      +
      get '/' do
      +  if settings.development?
      +    "development!"
      +  else
      +    "not development!"
      +  end
      +end
      +
      +
      +
      +

      Error Handling

      @@ -1919,49 +2570,87 @@

      Error Handling

      Not Found

      -

      When a Sinatra::NotFound exception is raised, or the response's status +

      When a Sinatra::NotFound exception is raised, or the response’s status code is 404, the not_found handler is invoked:

      -
      not_found do
      -  'This is nowhere to be found.'
      -end
      -
      + +
      +
      +
      not_found do
      +  'This is nowhere to be found.'
      +end
      +
      +
      +
      +

      Error

      The error handler is invoked any time an exception is raised from a route block or a filter. The exception object can be obtained from the sinatra.error Rack variable:

      -
      error do
      -  'Sorry there was a nasty error - ' + env['sinatra.error'].name
      -end
      -
      + +
      +
      +
      error do
      +  'Sorry there was a nasty error - ' + env['sinatra.error'].name
      +end
      +
      +
      +
      +

      Custom errors:

      -
      error MyCustomError do
      -  'So what happened was...' + env['sinatra.error'].message
      -end
      -
      + +
      +
      +
      error MyCustomError do
      +  'So what happened was...' + env['sinatra.error'].message
      +end
      +
      +
      +
      +

      Then, if this happens:

      -
      get '/' do
      -  raise MyCustomError, 'something bad'
      -end
      -
      + +
      +
      +
      get '/' do
      +  raise MyCustomError, 'something bad'
      +end
      +
      +
      +
      +

      You get this:

      -
      So what happened was... something bad
      -
      + +
      So what happened was... something bad
      +
      +

      Alternatively, you can install an error handler for a status code:

      -
      error 403 do
      -  'Access forbidden'
      -end
      -
      -get '/secret' do
      -  403
      -end
      -
      + +
      +
      +
      error 403 do
      +  'Access forbidden'
      +end
      +
      +get '/secret' do
      +  403
      +end
      +
      +
      +
      +

      Or a range:

      -
      error 400..510 do
      -  'Boom'
      -end
      -
      + +
      +
      +
      error 400..510 do
      +  'Boom'
      +end
      +
      +
      +
      +

      Sinatra installs special not_found and error handlers when running under the development environment to display nice stack traces and additional debugging information in your browser.

      @@ -1970,35 +2659,47 @@

      Error

      Rack Middleware

      Sinatra rides on Rack, a minimal standard -interface for Ruby web frameworks. One of Rack's most interesting capabilities -for application developers is support for "middleware" -- components that sit +interface for Ruby web frameworks. One of Rack’s most interesting capabilities +for application developers is support for “middleware” – components that sit between the server and your application monitoring and/or manipulating the HTTP request/response to provide various types of common functionality.

      Sinatra makes building Rack middleware pipelines a cinch via a top-level use method:

      -
      require 'sinatra'
      -require 'my_custom_middleware'
       
      -use Rack::Lint
      -use MyCustomMiddleware
      +
      +
      +
      require 'sinatra'
      +require 'my_custom_middleware'
      +
      +use Rack::Lint
      +use MyCustomMiddleware
      +
      +get '/hello' do
      +  'Hello World'
      +end
      +
      +
      +
      -get '/hello' do - 'Hello World' -end -

      The semantics of use are identical to those defined for the Rack::Builder DSL (most frequently used from rackup files). For example, the use method accepts multiple/variable args as well as blocks:

      -
      use Rack::Auth::Basic do |username, password|
      -  username == 'admin' && password == 'secret'
      -end
      -
      + +
      +
      +
      use Rack::Auth::Basic do |username, password|
      +  username == 'admin' && password == 'secret'
      +end
      +
      +
      +
      +

      Rack is distributed with a variety of standard middleware for logging, debugging, URL routing, authentication, and session handling. Sinatra uses many of these components automatically based on configuration so you -typically don't have to use them explicitly.

      +typically don’t have to use them explicitly.

      You can find useful middleware in rack, @@ -2012,33 +2713,39 @@

      Testing

      Sinatra tests can be written using any Rack-based testing library or framework. Rack::Test is recommended:

      -
      require 'my_sinatra_app'
      -require 'test/unit'
      -require 'rack/test'
      -
      -class MyAppTest < Test::Unit::TestCase
      -  include Rack::Test::Methods
      -
      -  def app
      -    Sinatra::Application
      -  end
      -
      -  def test_my_default
      -    get '/'
      -    assert_equal 'Hello World!', last_response.body
      -  end
      -
      -  def test_with_params
      -    get '/meet', :name => 'Frank'
      -    assert_equal 'Hello Frank!', last_response.body
      -  end
      -
      -  def test_with_rack_env
      -    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      -    assert_equal "You're using Songbird!", last_response.body
      -  end
      -end
      -
      + +
      +
      +
      require 'my_sinatra_app'
      +require 'test/unit'
      +require 'rack/test'
      +
      +class MyAppTest < Test::Unit::TestCase
      +  include Rack::Test::Methods
      +
      +  def app
      +    Sinatra::Application
      +  end
      +
      +  def test_my_default
      +    get '/'
      +    assert_equal 'Hello World!', last_response.body
      +  end
      +
      +  def test_with_params
      +    get '/meet', :name => 'Frank'
      +    assert_equal 'Hello Frank!', last_response.body
      +  end
      +
      +  def test_with_rack_env
      +    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      +    assert_equal "You're using Songbird!", last_response.body
      +  end
      +end
      +
      +
      +
      +

      Note: If you are using Sinatra in the modular style, replace Sinatra::Application above with the class name of your app.

      @@ -2050,28 +2757,34 @@

      Sinatra::Base - Middleware, Libraries, and Modular Apps

      middleware, Rails metal, simple libraries with a server component, or even Sinatra extensions. The top-level assumes a micro-app style configuration (e.g., a single application file, ./public and ./views -directories, logging, exception detail page, etc.). That's where +directories, logging, exception detail page, etc.). That’s where Sinatra::Base comes into play:

      -
      require 'sinatra/base'
       
      -class MyApp < Sinatra::Base
      -  set :sessions, true
      -  set :foo, 'bar'
      +
      +
      +
      require 'sinatra/base'
      +
      +class MyApp < Sinatra::Base
      +  set :sessions, true
      +  set :foo, 'bar'
      +
      +  get '/' do
      +    'Hello world!'
      +  end
      +end
      +
      +
      +
      - get '/' do - 'Hello world!' - end -end -

      The methods available to Sinatra::Base subclasses are exactly the same as those available via the top-level DSL. Most top-level apps can be converted to Sinatra::Base components with two modifications:

      • Your file should require sinatra/base instead of sinatra; -otherwise, all of Sinatra's DSL methods are imported into the main +otherwise, all of Sinatra’s DSL methods are imported into the main namespace.
      • -
      • Put your app's routes, error handlers, filters, and options in a subclass +
      • Put your app’s routes, error handlers, filters, and options in a subclass of Sinatra::Base.

      Sinatra::Base is a blank slate. Most options are disabled by default, @@ -2135,42 +2848,78 @@

      Serving a Modular Application

      There are two common options for starting a modular app, actively starting with run!:

      -
      # my_app.rb
      -require 'sinatra/base'
       
      -class MyApp < Sinatra::Base
      -  # ... app code here ...
      +
      +
      +
      # my_app.rb
      +require 'sinatra/base'
      +
      +class MyApp < Sinatra::Base
      +  # ... app code here ...
      +
      +  # start the server if ruby file executed directly
      +  run! if app_file == $0
      +end
      +
      +
      +
      - # start the server if ruby file executed directly - run! if app_file == $0 -end -

      Start with:

      -
      ruby my_app.rb
      -
      + +
      +
      +
      ruby my_app.rb
      +
      +
      +
      +

      Or with a config.ru file, which allows using any Rack handler:

      -
      # config.ru (run with rackup)
      -require './my_app'
      -run MyApp
      -
      + +
      +
      +
      # config.ru (run with rackup)
      +require './my_app'
      +run MyApp
      +
      +
      +
      +

      Run:

      -
      rackup -p 4567
      -
      + +
      +
      +
      rackup -p 4567
      +
      +
      +
      +

      Using a Classic Style Application with a config.ru

      Write your app file:

      -
      # app.rb
      -require 'sinatra'
       
      -get '/' do
      -  'Hello world!'
      -end
      -
      +
      +
      +
      # app.rb
      +require 'sinatra'
      +
      +get '/' do
      +  'Hello world!'
      +end
      +
      +
      +
      +

      And a corresponding config.ru:

      -
      require './app'
      -run Sinatra::Application
      -
      + +
      +
      +
      require './app'
      +run Sinatra::Application
      +
      +
      +
      +

      When to use a config.ru?

      @@ -2178,12 +2927,12 @@

      When to use a config.ru?

      • You want to deploy with a different Rack handler (Passenger, Unicorn, -Heroku, ...).
      • -
      • You want to use more than one subclass of Sinatra::Base.
      • -
      • You want to use Sinatra only for middleware, and not as an endpoint.
      • +Heroku, …). +
      • You want to use more than one subclass of Sinatra::Base.
      • +
      • You want to use Sinatra only for middleware, and not as an endpoint.

      There is no need to switch to a config.ru simply because you -switched to the modular style, and you don't have to use the modular style for running +switched to the modular style, and you don’t have to use the modular style for running with a config.ru.

      @@ -2192,74 +2941,98 @@

      Using Sinatra as Middleware

      Not only is Sinatra able to use other Rack middleware, any Sinatra application can in turn be added in front of any Rack endpoint as middleware itself. This endpoint could be another Sinatra application, or any other Rack-based -application (Rails/Ramaze/Camping/...):

      -
      require 'sinatra/base'
      -
      -class LoginScreen < Sinatra::Base
      -  enable :sessions
      -
      -  get('/login') { haml :login }
      -
      -  post('/login') do
      -    if params[:name] == 'admin' && params[:password] == 'admin'
      -      session['user_name'] = params[:name]
      -    else
      -      redirect '/login'
      -    end
      -  end
      -end
      -
      -class MyApp < Sinatra::Base
      -  # middleware will run before filters
      -  use LoginScreen
      -
      -  before do
      -    unless session['user_name']
      -      halt "Access denied, please <a href='/login'>login</a>."
      -    end
      -  end
      -
      -  get('/') { "Hello #{session['user_name']}." }
      -end
      -
      +application (Rails/Ramaze/Camping/…):

      + +
      +
      +
      require 'sinatra/base'
      +
      +class LoginScreen < Sinatra::Base
      +  enable :sessions
      +
      +  get('/login') { haml :login }
      +
      +  post('/login') do
      +    if params[:name] == 'admin' && params[:password] == 'admin'
      +      session['user_name'] = params[:name]
      +    else
      +      redirect '/login'
      +    end
      +  end
      +end
      +
      +class MyApp < Sinatra::Base
      +  # middleware will run before filters
      +  use LoginScreen
      +
      +  before do
      +    unless session['user_name']
      +      halt "Access denied, please <a href='/login'>login</a>."
      +    end
      +  end
      +
      +  get('/') { "Hello #{session['user_name']}." }
      +end
      +
      +
      +
      +

      Dynamic Application Creation

      Sometimes you want to create new applications at runtime without having to assign them to a constant, you can do this with Sinatra.new:

      -
      require 'sinatra/base'
      -my_app = Sinatra.new { get('/') { "hi" } }
      -my_app.run!
      -
      + +
      +
      +
      require 'sinatra/base'
      +my_app = Sinatra.new { get('/') { "hi" } }
      +my_app.run!
      +
      +
      +
      +

      It takes the application to inherit from as an optional argument:

      -
      # config.ru (run with rackup)
      -require 'sinatra/base'
      -
      -controller = Sinatra.new do
      -  enable :logging
      -  helpers MyHelpers
      -end
      -
      -map('/a') do
      -  run Sinatra.new(controller) { get('/') { 'a' } }
      -end
      -
      -map('/b') do
      -  run Sinatra.new(controller) { get('/') { 'b' } }
      -end
      -
      + +
      +
      +
      # config.ru (run with rackup)
      +require 'sinatra/base'
      +
      +controller = Sinatra.new do
      +  enable :logging
      +  helpers MyHelpers
      +end
      +
      +map('/a') do
      +  run Sinatra.new(controller) { get('/') { 'a' } }
      +end
      +
      +map('/b') do
      +  run Sinatra.new(controller) { get('/') { 'b' } }
      +end
      +
      +
      +
      +

      This is especially useful for testing Sinatra extensions or using Sinatra in your own library.

      This also makes using Sinatra as middleware extremely easy:

      -
      require 'sinatra/base'
       
      -use Sinatra do
      -  get('/') { ... }
      -end
      +
      +
      +
      require 'sinatra/base'
      +
      +use Sinatra do
      +  get('/') { ... }
      +end
      +
      +run RailsProject::Application
      +
      +
      +
      -run RailsProject::Application -

      Scopes and Binding

      @@ -2277,33 +3050,39 @@

      Application/Class Scope

      single application class for all requests.

      Options created via set are methods at class level:

      -
      class MyApp < Sinatra::Base
      -  # Hey, I'm in the application scope!
      -  set :foo, 42
      -  foo # => 42
      -
      -  get '/foo' do
      -    # Hey, I'm no longer in the application scope!
      -  end
      -end
      -
      + +
      +
      +
      class MyApp < Sinatra::Base
      +  # Hey, I'm in the application scope!
      +  set :foo, 42
      +  foo # => 42
      +
      +  get '/foo' do
      +    # Hey, I'm no longer in the application scope!
      +  end
      +end
      +
      +
      +
      +

      You have the application scope binding inside:

      • Your application class body
      • -
      • Methods defined by extensions
      • -
      • The block passed to helpers +
      • Methods defined by extensions
      • +
      • The block passed to helpers
      • -
      • Procs/blocks used as value for set +
      • Procs/blocks used as value for set
      • -
      • The block passed to Sinatra.new +
      • The block passed to Sinatra.new

      You can reach the scope object (the class) like this:

      • Via the object passed to configure blocks (configure { |c| ... })
      • -
      • +
      • settings from within the request scope
      @@ -2314,28 +3093,34 @@

      Request/Instance Scope

      can access the request and session objects or call rendering methods like erb or haml. You can access the application scope from within the request scope via the settings helper:

      -
      class MyApp < Sinatra::Base
      -  # Hey, I'm in the application scope!
      -  get '/define_route/:name' do
      -    # Request scope for '/define_route/:name'
      -    @value = 42
      -
      -    settings.get("/#{params[:name]}") do
      -      # Request scope for "/#{params[:name]}"
      -      @value # => nil (not the same request)
      -    end
      -
      -    "Route defined!"
      -  end
      -end
      -
      + +
      +
      +
      class MyApp < Sinatra::Base
      +  # Hey, I'm in the application scope!
      +  get '/define_route/:name' do
      +    # Request scope for '/define_route/:name'
      +    @value = 42
      +
      +    settings.get("/#{params[:name]}") do
      +      # Request scope for "/#{params[:name]}"
      +      @value # => nil (not the same request)
      +    end
      +
      +    "Route defined!"
      +  end
      +end
      +
      +
      +
      +

      You have the request scope binding inside:

      • get, head, post, put, delete, options, patch, link, and unlink blocks
      • -
      • before and after filters
      • -
      • helper methods
      • -
      • templates/views
      • +
      • before and after filters
      • +
      • helper methods
      • +
      • templates/views

      Delegation Scope

      @@ -2352,9 +3137,9 @@

      Delegation Scope

      • The top level binding, if you did require "sinatra"
      • -
      • An object extended with the Sinatra::Delegator mixin
      • +
      • An object extended with the Sinatra::Delegator mixin
      -

      Have a look at the code for yourself: here's the +

      Have a look at the code for yourself: here’s the Sinatra::Delegator mixin being extending the main object.

      @@ -2362,21 +3147,28 @@

      Delegation Scope

      Command Line

      Sinatra applications can be run directly:

      -
      ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
      -
      + +
      +
      +
      ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
      +
      +
      +
      +

      Options are:

      -
      -h # help
      +
      +
      -h # help
       -p # set the port (default is 4567)
       -o # set the host (default is 0.0.0.0)
       -e # set the environment (default is development)
       -s # specify rack server/handler (default is thin)
       -x # turn on the mutex lock (default is off)
      -
      +
      +

      Requirement

      The following Ruby versions are officially supported:

      -
      Ruby 1.8.7
      @@ -2425,14 +3217,14 @@

      Requirement

      • Older versions of JRuby and Rubinius
      • -
      • Ruby Enterprise Edition
      • -
      • MacRuby, Maglev, IronRuby
      • -
      • Ruby 1.9.0 and 1.9.1 (but we do recommend against using those)
      • +
      • Ruby Enterprise Edition
      • +
      • MacRuby, Maglev, IronRuby
      • +
      • Ruby 1.9.0 and 1.9.1 (but we do recommend against using those)

      Not being officially supported means if things only break there and not on a -supported platform, we assume it's not our issue but theirs.

      +supported platform, we assume it’s not our issue but theirs.

      -

      We also run our CI against ruby-head (the upcoming 2.1.0), but we can't +

      We also run our CI against ruby-head (the upcoming 2.1.0), but we can’t guarantee anything, since it is constantly moving. Expect 2.1.0 to be fully supported.

      @@ -2441,18 +3233,24 @@

      Requirement

      If you run MacRuby, you should gem install control_tower.

      -

      Sinatra currently doesn't run on Cardinal, SmallRuby, BlueRuby or any +

      Sinatra currently doesn’t run on Cardinal, SmallRuby, BlueRuby or any Ruby version prior to 1.8.7.

      The Bleeding Edge

      -

      If you would like to use Sinatra's latest bleeding-edge code, feel free to run your +

      If you would like to use Sinatra’s latest bleeding-edge code, feel free to run your application against the master branch, it should be rather stable.

      We also push out prerelease gems from time to time, so you can do a

      -
      gem install sinatra --pre
      -
      + +
      +
      +
      gem install sinatra --pre
      +
      +
      +
      +

      To get some of the latest features.

      @@ -2461,49 +3259,89 @@

      With Bundler

      If you want to run your application with the latest Sinatra, using Bundler is the recommended way.

      -

      First, install bundler, if you haven't:

      -
      gem install bundler
      -
      +

      First, install bundler, if you haven’t:

      + +
      +
      +
      gem install bundler
      +
      +
      +
      +

      Then, in your project directory, create a Gemfile:

      -
      source 'https://rubygems.org'
      -gem 'sinatra', :github => "sinatra/sinatra"
      -
      -# other dependencies
      -gem 'haml'                    # for instance, if you use haml
      -gem 'activerecord', '~> 3.0'  # maybe you also need ActiveRecord 3.x
      -
      -

      Note that you will have to list all your application's dependencies in the Gemfile. -Sinatra's direct dependencies (Rack and Tilt) will, however, be automatically + +

      +
      +
      source 'https://rubygems.org'
      +gem 'sinatra', :github => "sinatra/sinatra"
      +
      +# other dependencies
      +gem 'haml'                    # for instance, if you use haml
      +gem 'activerecord', '~> 3.0'  # maybe you also need ActiveRecord 3.x
      +
      +
      +
      + +

      Note that you will have to list all your application’s dependencies in the Gemfile. +Sinatra’s direct dependencies (Rack and Tilt) will, however, be automatically fetched and added by Bundler.

      Now you can run your app like this:

      -
      bundle exec ruby myapp.rb
      -
      + +
      +
      +
      bundle exec ruby myapp.rb
      +
      +
      +
      +

      Roll Your Own

      Create a local clone and run your app with the sinatra/lib directory on the $LOAD_PATH:

      -
      cd myapp
      +
      +
      +
      +
      cd myapp
       git clone git://github.com/sinatra/sinatra.git
       ruby -I sinatra/lib myapp.rb
      -
      +
      + + +

      To update the Sinatra sources in the future:

      -
      cd myapp/sinatra
      +
      +
      +
      +
      cd myapp/sinatra
       git pull
      -
      - -

      Install Globally

      +
      + + +

      ### Install Globally

      You can build the gem on your own:

      -
      git clone git://github.com/sinatra/sinatra.git
      -cd sinatra
      +
      +
      +
      +
      git clone git://github.com/sinatra/sinatra.git
      +cd sinatra
       rake sinatra.gemspec
       rake install
      -
      +
      + + +

      If you install gems as root, the last step should be

      -
      sudo rake install
      -
      + +
      +
      +
      sudo rake install
      +
      +
      +
      +

      Versioning

      @@ -2517,21 +3355,21 @@

      Further Reading

    32. Project Website - Additional documentation, news, and links to other resources.
    33. -
    34. +
    35. Contributing - Find a bug? Need help? Have a patch?
    36. -
    37. Issue tracker
    38. -
    39. Twitter
    40. -
    41. Mailing List
    42. -
    43. IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on http://freenode.net
    44. -
    45. +
    46. Issue tracker
    47. +
    48. Twitter
    49. +
    50. Mailing List
    51. +
    52. IRC: #sinatra on http://freenode.net
    53. +
    54. Sinatra Book Cookbook Tutorial
    55. -
    56. +
    57. Sinatra Recipes Community contributed recipes
    58. -
    59. API documentation for the latest release +
    60. API documentation for the latest release or the current HEAD on http://rubydoc.info
    61. -
    62. CI server
    63. +
    64. CI server
    65. diff --git a/_includes/README.hu.html b/_includes/README.hu.html index a4aec4b1..a8279189 100644 --- a/_includes/README.hu.html +++ b/_includes/README.hu.html @@ -34,23 +34,34 @@ -

      Fontos megjegyzés: Ez a dokumentum csak egy fordítása az angol nyelvű változat, és lehet, hogy nem naprakész.

      A Sinatra egy DSL webalkalmazások Ruby nyelven történő fejlesztéséhez, minimális energiabefektetéssel:

      -
        # myapp.rb
      -  require 'sinatra'
      -  get '/' do
      -    'Helló Világ!'
      -  end
      -
      + +
      +
      +
        # myapp.rb
      +  require 'sinatra'
      +  get '/' do
      +    'Helló Világ!'
      +  end
      +
      +
      +
      +

      Telepítsd a gem-et és indítsd el az alkalmazást a következőképpen:

      -
        sudo gem install sinatra
      -  ruby myapp.rb
      -
      + +
      +
      +
        sudo gem install sinatra
      +  ruby myapp.rb
      +
      +
      +
      +

      Az alkalmazás elérhető lesz itt: http://localhost:4567

      @@ -58,72 +69,114 @@

      Útvonalak (routes)

      A Sinatrában az útvonalat egy HTTP metódus és egy URL-re illeszkedő minta párosa alkotja. Minden egyes útvonalhoz tartozik egy blokk:

      -
        get '/' do
      -    .. megjelenítünk valamit ..
      -  end
      -
      -  post '/' do
      -    .. létrehozunk valamit ..
      -  end
      -
      -  put '/' do
      -    .. frissítünk valamit ..
      -  end
      -
      -  delete '/' do
      -    .. törlünk valamit ..
      -  end
      -
      + +
      +
      +
        get '/' do
      +    .. megjelenítünk valamit ..
      +  end
      +
      +  post '/' do
      +    .. létrehozunk valamit ..
      +  end
      +
      +  put '/' do
      +    .. frissítünk valamit ..
      +  end
      +
      +  delete '/' do
      +    .. törlünk valamit ..
      +  end
      +
      +
      +
      +

      Az útvonalak illeszkedését a rendszer a definiálásuk sorrendjében ellenőrzi. Sorrendben mindig az első illeszkedő útvonalhoz tartozó metódus kerül meghívásra.

      Az útvonalminták tartalmazhatnak paramétereket is, melyeket a params hash-ből érhetünk el:

      -
        get '/hello/:name' do
      -    # illeszkedik a "GET /hello/foo" és a "GET /hello/bar" útvonalakra
      -    # ekkor params[:name] értéke 'foo' vagy 'bar' lesz
      -    "Helló #{params[:name]}!"
      -  end
      -
      + +
      +
      +
        get '/hello/:name' do
      +    # illeszkedik a "GET /hello/foo" és a "GET /hello/bar" útvonalakra
      +    # ekkor params[:name] értéke 'foo' vagy 'bar' lesz
      +    "Helló #{params[:name]}!"
      +  end
      +
      +
      +
      +

      A kulcsszavas argumentumokat (named parameters) blokk paraméterek útján is el tudod érni:

      -
        get '/hello/:name' do |n|
      -    "Helló #{n}!"
      -  end
      -
      + +
      +
      +
        get '/hello/:name' do |n|
      +    "Helló #{n}!"
      +  end
      +
      +
      +
      +

      Az útvonalmintákban szerepelhetnek joker paraméterek is, melyeket a params[:splat] tömbön keresztül tudunk elérni.

      -
        get '/say/*/to/*' do
      -    # illeszkedik a /say/hello/to/world mintára
      -    params[:splat] # => ["hello", "world"]
      -  end
      -
      -  get '/download/*.*' do
      -    # illeszkedik a /download/path/to/file.xml mintára
      -    params[:splat] # => ["path/to/file", "xml"]
      -  end
      -
      + +
      +
      +
        get '/say/*/to/*' do
      +    # illeszkedik a /say/hello/to/world mintára
      +    params[:splat] # => ["hello", "world"]
      +  end
      +
      +  get '/download/*.*' do
      +    # illeszkedik a /download/path/to/file.xml mintára
      +    params[:splat] # => ["path/to/file", "xml"]
      +  end
      +
      +
      +
      +

      Reguláris kifejezéseket is felvehetünk az útvonalba:

      -
        get %r{/hello/([\w]+)} do
      -    "Helló, #{params[:captures].first}!"
      -  end
      -
      + +
      +
      +
        get %r{/hello/([\w]+)} do
      +    "Helló, #{params[:captures].first}!"
      +  end
      +
      +
      +
      +

      Vagy blokk paramétereket:

      -
        get %r{/hello/([\w]+)} do |c|
      -    "Helló, #{c}!"
      -  end
      -
      + +
      +
      +
        get %r{/hello/([\w]+)} do |c|
      +    "Helló, #{c}!"
      +  end
      +
      +
      +
      +

      Az útvonalak azonban számos egyéb illeszkedési feltétel szerint is tervezhetők, így például az user agent karakterláncot alapul véve:

      -
        get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      -    "A Songbird #{params[:agent][0]} verzióját használod"
      -  end
      -
      -  get '/foo' do
      -    # illeszkedik az egyéb user agentekre
      -  end
      -
      + +
      +
      +
        get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      +    "A Songbird #{params[:agent][0]} verzióját használod"
      +  end
      +
      +  get '/foo' do
      +    # illeszkedik az egyéb user agentekre
      +  end
      +
      +
      +
      +

      Statikus állományok

      @@ -131,7 +184,7 @@

      Statikus állományok

      történik, de természetesen más könyvtárat is megadhatsz erre a célra, mégpedig a :public_folder kapcsoló beállításával:

      -

      set :publicfolder, File.dirname(FILE_) + '/static'

      +

      set :public_folder, File.dirname(FILE) + ‘/static’

      Fontos mgejegyezni, hogy a nyilvános könyvtár neve nem szerepel az URL-ben. A ./public/css/style.css fájl az @@ -143,46 +196,64 @@

      Nézetek és Sablonok

      A sablonfájlokat rendszerint a ./views könyvtárba helyezzük, de itt is lehetőség nyílik egyéb könyvtár használatára:

      -

      set :views, File.dirname(FILE) + '/templates'

      +

      set :views, File.dirname(FILE) + ‘/templates’

      Nagyon fontos észben tartani, hogy a sablononkra mindig szimbólumokkal hivatkozunk, még akkor is, ha egyéb (ebben az esetben a -:'subdir/template') könyvtárban tároljuk őket. A renderelő +:’subdir/template’) könyvtárban tároljuk őket. A renderelő metódusok minden, nekik közvetlenül átadott karakterláncot megjelenítenek.

      Haml sablonok

      HAML sablonok rendereléséhez szükségünk lesz a haml gem-re vagy könyvtárra:

      -
        # Importáljuk be a haml-t az alkalmazásba
      -  require 'haml'
       
      -  get '/' do
      -    haml :index
      -  end
      -
      +
      +
      +
        # Importáljuk be a haml-t az alkalmazásba
      +  require 'haml'
      +
      +  get '/' do
      +    haml :index
      +  end
      +
      +
      +
      +

      Ez szépen lerendereli a ./views/index.haml sablont.

      A Haml kapcsolói globálisan is beállíthatók a Sinatra konfigurációi között, lásd az Options and Configurations lapot. A globális beállításokat lehetőségünk van felülírni metódus szinten is.

      -
        set :haml, {:format => :html5 } # az alapértelmezett Haml formátum az :xhtml
       
      -  get '/' do
      -    haml :index, :haml_options => {:format => :html4 } # immár felülírva
      -  end
      -
      +
      +
      +
        set :haml, {:format => :html5 } # az alapértelmezett Haml formátum az :xhtml
      +
      +  get '/' do
      +    haml :index, :haml_options => {:format => :html4 } # immár felülírva
      +  end
      +
      +
      +
      +

      Erb sablonok

      # Importáljuk be az erb-t az alkalmazásba

      -
        require 'erb'
       
      -  get '/' do
      -    erb :index
      -  end
      -
      +
      +
      +
        require 'erb'
      +
      +  get '/' do
      +    erb :index
      +  end
      +
      +
      +
      +

      Ez a ./views/index.erb sablont fogja lerenderelni.

      @@ -192,12 +263,18 @@

      Builder sablonok

      rendereléséhez:

      # Importáljuk be a builder-t az alkalmazásba

      -
        require 'builder'
       
      -  get '/' do
      -    builder :index
      -  end
      -
      +
      +
      +
        require 'builder'
      +
      +  get '/' do
      +    builder :index
      +  end
      +
      +
      +
      +

      Ez pedig a ./views/index.builder állományt fogja renderelni.

      @@ -206,30 +283,48 @@

      Sass sablonok

      Sass sablonok használatához szükség lesz a haml gem-re vagy könyvtárra:

      # Be kell importálni a haml, vagy a sass könyvtárat

      -
        require 'sass'
       
      -  get '/stylesheet.css' do
      -    sass :stylesheet
      -  end
      -
      +
      +
      +
        require 'sass'
      +
      +  get '/stylesheet.css' do
      +    sass :stylesheet
      +  end
      +
      +
      +
      +

      Így a ./views/stylesheet.sass fájl máris renderelhető.

      A Sass kapcsolói globálisan is beállíthatók a Sinatra konfigurációi között, lásd az Options and Configurations lapot. A globális beállításokat lehetőségünk van felülírni metódus szinten is.

      -
        set :sass, {:style => :compact } # az alapértelmezett Sass stílus a :nested
       
      -  get '/stylesheet.css' do
      -    sass :stylesheet, :sass_options => {:style => :expanded } # felülírva
      -  end
      -
      +
      +
      +
        set :sass, {:style => :compact } # az alapértelmezett Sass stílus a :nested
      +
      +  get '/stylesheet.css' do
      +    sass :stylesheet, :sass_options => {:style => :expanded } # felülírva
      +  end
      +
      +
      +
      +

      Beágyazott sablonok

      -
        get '/' do
      -    haml '%div.title Helló Világ'
      -  end
      -
      + +
      +
      +
        get '/' do
      +    haml '%div.title Helló Világ'
      +  end
      +
      +
      +
      +

      Lerendereli a beágyazott sablon karakerláncát.

      @@ -238,17 +333,29 @@

      Változók elérése a sablonokban

      A sablonok ugyanabban a kontextusban kerülnek kiértékelésre, mint az útvonal metódusok (route handlers). Az útvonal metódusokban megadott változók közvetlenül elérhetőek lesznek a sablonokban:

      -
        get '/:id' do
      -    @foo = Foo.find(params[:id])
      -    haml '%h1= @foo.name'
      -  end
      -
      + +
      +
      +
        get '/:id' do
      +    @foo = Foo.find(params[:id])
      +    haml '%h1= @foo.name'
      +  end
      +
      +
      +
      +

      De megadhatod egy lokális változókat tartalmazó explicit hash-ben is:

      -
        get '/:id' do
      -    foo = Foo.find(params[:id])
      -    haml '%h1= foo.name', :locals => { :foo => foo }
      -  end
      -
      + +
      +
      +
        get '/:id' do
      +    foo = Foo.find(params[:id])
      +    haml '%h1= foo.name', :locals => { :foo => foo }
      +  end
      +
      +
      +
      +

      Ezt leginkább akkor érdemes megtenni, ha partial-eket akarunk renderelni valamely más sablonból.

      @@ -257,67 +364,91 @@

      Fájlon belüli sablonok

      Sablonokat úgy is megadhatunk, hogy egyszerűen az alkalmazás fájl végére begépeljük őket:

      -
        require 'rubygems'
      -  require 'sinatra'
       
      -  get '/' do
      -    haml :index
      -  end
      +
      +
      +
        require 'rubygems'
      +  require 'sinatra'
      +
      +  get '/' do
      +    haml :index
      +  end
       
      -  __END__
      +  __END__
       
      -  @@ layout
      +  @@ layout
         %html
      -    = yield
      +    = yield
      +
      +  @@ index
      +  %div.title Helló Világ!!!!!
      +
      +
      +
      - @@ index - %div.title Helló Világ!!!!! -

      Megjegyzés: azok a fájlon belüli sablonok, amelyek az alkalmazás fájl végére kerülnek és függnek a sinatra könyvtártól, automatikusan betöltődnek. Ha ugyanezt más alkalmazásfájlban is szeretnéd megtenni, hívd meg -a useinfile_templates! metódust az adott fájlban.

      +a use_in_file_templates! metódust az adott fájlban.

      Kulcsszavas sablonok

      Sablonokat végül a felsőszintű template metódussal is definiálhatunk:

      -
        template :layout do
      -    "%html\n  =yield\n"
      -  end
      -
      -  template :index do
      -    '%div.title Helló Világ!'
      -  end
      -
      -  get '/' do
      -    haml :index
      -  end
      -
      -

      Ha létezik "layout" nevű sablon, akkor az minden esetben meghívódik, amikor + +

      +
      +
        template :layout do
      +    "%html\n  =yield\n"
      +  end
      +
      +  template :index do
      +    '%div.title Helló Világ!'
      +  end
      +
      +  get '/' do
      +    haml :index
      +  end
      +
      +
      +
      + +

      Ha létezik “layout” nevű sablon, akkor az minden esetben meghívódik, amikor csak egy sablon renderelésre kerül. A layoutokat ki lehet kapcsolni a :layout => false meghívásával.

      -
        get '/' do
      -    haml :index, :layout => !request.xhr?
      -  end
      -
      + +
      +
      +
        get '/' do
      +    haml :index, :layout => !request.xhr?
      +  end
      +
      +
      +
      +

      Helperek

      Használd a felső szintű helpers metódust azokhoz a helper függvényekhez, amiket az útvonal metódusokban és a sablonokban akarsz használni:

      -
        helpers do
      -    def bar(name)
      -      "#{name}bar"
      -    end
      -  end
      -
      -  get '/:name' do
      -    bar(params[:name])
      -  end
      -
      + +
      +
      +
        helpers do
      +    def bar(name)
      +      "#{name}bar"
      +    end
      +  end
      +
      +  get '/:name' do
      +    bar(params[:name])
      +  end
      +
      +
      +
      +

      Szűrők (filters)

      @@ -325,7 +456,10 @@

      Szűrők (filters)

      kérés alkalmával kiértékelődnek, így módosíthatják a kérést és a választ egyaránt. A szűrőkbe felvett példányváltozók elérhetőek lesznek az útvonalakban és a sablonokban is:

      -
        before do
      +
      +
      +
      +
        before do
           @note = 'Csá!'
           request.path_info = '/foo/bar/baz'
         end
      @@ -334,15 +468,24 @@ 

      Szűrők (filters)

      @note #=> 'Szeva!' params[:splat] #=> 'bar/baz' end -
      +
      + + +

      Az utószűrők az egyes kérések után, az adott kérés kontextusában kerülnek kiértékelésre, így ezek is képesek módosítani a kérést és a választ egyaránt. Az előszűrőkben és úvonalakban létrehozott példányváltozók elérhetőek lesznek az utószűrők számára:

      -
        after do
      -    puts response.status
      -  end
      -
      + +
      +
      +
        after do
      +    puts response.status
      +  end
      +
      +
      +
      +

      Megállítás

      @@ -351,28 +494,34 @@

      Megállítás

      halt

      -

      A megállításkor egy blokktörzset is megadhatsz ...

      +

      A megállításkor egy blokktörzset is megadhatsz …

      -

      halt 'ez fog megjelenni a törzsben'

      +

      halt ‘ez fog megjelenni a törzsben’

      -

      Vagy állítsd be a HTTP státuszt és a törzset is egyszerre ...

      +

      Vagy állítsd be a HTTP státuszt és a törzset is egyszerre …

      -

      halt 401, 'menj innen!'

      +

      halt 401, ‘menj innen!’

      Passzolás

      Az útvonalak továbbadhatják a végrehajtást egy másik útvonalnak a pass függvényhívással:

      -
        get '/guess/:who' do
      -    pass unless params[:who] == 'Frici'
      -    "Elkaptál!"
      -  end
      -
      -  get '/guess/*' do
      -    "Elhibáztál!"
      -  end
      -
      + +
      +
      +
        get '/guess/:who' do
      +    pass unless params[:who] == 'Frici'
      +    "Elkaptál!"
      +  end
      +
      +  get '/guess/*' do
      +    "Elhibáztál!"
      +  end
      +
      +
      +
      +

      Az útvonal blokkja azonnal kilép és átadja a vezérlést a következő illeszkedő útvonalnak. Ha nem talál megfelelő útvonalat, a Sinatra egy 404-es hibával tér vissza.

      @@ -381,21 +530,39 @@

      Passzolás

      Beállítások

      Csak indításkor, de minden környezetre érvényesen fusson le:

      -
        configure do
      -    ...
      -  end
      -
      + +
      +
      +
        configure do
      +    ...
      +  end
      +
      +
      +
      +

      Csak akkor fusson le, ha a környezet (a RACK_ENV környezeti változóban) :production-ra van állítva:

      -
        configure :production do
      -    ...
      -  end
      -
      + +
      +
      +
        configure :production do
      +    ...
      +  end
      +
      +
      +
      +

      Csak akkor fusson le, ha a környezet :production vagy :test:

      -
        configure :production, :test do
      -    ...
      -  end
      -
      + +
      +
      +
        configure :production, :test do
      +    ...
      +  end
      +
      +
      +
      +

      Hibakezelés

      @@ -409,33 +576,57 @@

      Nem található

      Amikor a Sinatra::NotFound kivétel fellép, vagy a válasz HTTP státuszkódja 404-es, mindig a not_found metódus hívódik meg.

      -
        not_found do
      -    'Sehol sem találom, amit keresel'
      -  end
      -
      + +
      +
      +
        not_found do
      +    'Sehol sem találom, amit keresel'
      +  end
      +
      +
      +
      +

      Hiba

      Az error metódus hívódik meg olyankor, amikor egy útvonal, blokk vagy előszűrő kivételt vált ki. A kivétel objektum lehívható a sinatra.error Rack változótól:

      -
        error do
      -    'Elnézést, de valami szörnyű hiba lépett fel - ' + env['sinatra.error'].name
      -  end
      -
      + +
      +
      +
        error do
      +    'Elnézést, de valami szörnyű hiba lépett fel - ' + env['sinatra.error'].name
      +  end
      +
      +
      +
      +

      Egyéni hibakezelés:

      -
        error MyCustomError do
      -    'Szóval az van, hogy...' + env['sinatra.error'].message
      -  end
      -
      + +
      +
      +
        error MyCustomError do
      +    'Szóval az van, hogy...' + env['sinatra.error'].message
      +  end
      +
      +
      +
      +

      És amikor fellép:

      -
        get '/' do
      -    raise MyCustomError, 'valami nem stimmel!'
      -  end
      -
      + +
      +
      +
        get '/' do
      +    raise MyCustomError, 'valami nem stimmel!'
      +  end
      +
      +
      +
      +

      Ez fog megjelenni:

      -

      Szóval az van, hogy... valami nem stimmel!

      +

      Szóval az van, hogy… valami nem stimmel!

      A Sinatra speciális not_found és error hibakezelőket használ, amikor a futtatási környezet fejlesztői módba van kapcsolva.

      @@ -447,40 +638,58 @@

      Mime típusok

      kiszolgálásakor előfordulhat, hogy a Sinatra nem ismeri fel a fájlok mime típusát. Ilyenkor használd a +mime_type+ kapcsolót a fájlkiterjesztés bevezetéséhez:

      -
        mime_type :foo, 'text/foo'
      -
      + +
      +
      +
        mime_type :foo, 'text/foo'
      +
      +
      +
      +

      Rack Middleware

      A Sinatra egy Ruby keretrendszerek számára kifejlesztett egyszerű és szabványos interfészre, a Rack -re épül. A Rack fejlesztői szempontból egyik legérdekesebb jellemzője, hogy támogatja az úgynevezett -"middleware" elnevezésű komponenseket, amelyek beékelődnek a szerver és az +“middleware” elnevezésű komponenseket, amelyek beékelődnek a szerver és az alkalmazás közé, így képesek megfigyelni és/vagy módosítani a HTTP kéréseket és válaszokat. Segítségükkel különféle, egységesen működő funkciókat építhetünk be rendszerünkbe.

      A Sinatra keretrendszerben gyerekjáték a Rack middleware-ek behúzása a use metódus segítségével:

      -
        require 'sinatra'
      -  require 'my_custom_middleware'
       
      -  use Rack::Lint
      -  use MyCustomMiddleware
      +
      +
      +
        require 'sinatra'
      +  require 'my_custom_middleware'
      +
      +  use Rack::Lint
      +  use MyCustomMiddleware
      +
      +  get '/hello' do
      +    'Helló Világ'
      +  end
      +
      +
      +
      - get '/hello' do - 'Helló Világ' - end -

      A use metódus szemantikája megegyezik a Rack::Builder DSL-ben használt +use+ metóduséval (az említett DSL-t leginkább rackup állományokban használják). Hogy egy példát említsünk, a use metódus elfogad változókat és blokkokat egyaránt, akár kombinálva is ezeket:

      -
        use Rack::Auth::Basic do |username, password|
      -    username == 'admin' && password == 'titkos'
      -  end
      -
      + +
      +
      +
        use Rack::Auth::Basic do |username, password|
      +    username == 'admin' && password == 'titkos'
      +  end
      +
      +
      +
      +

      A Rack terjesztéssel egy csomó alap middleware komponens is érkezik, amelyekkel a naplózás, URL útvonalak megadása, autentikáció és munkamenet-kezelés könnyen megvalósítható. A Sinatra ezek közül elég @@ -493,32 +702,38 @@

      Tesztelés

      Sinatra teszteket bármely Rack alapú tesztelő könyvtárral vagy keretrendszerrel készíthetsz. Mi a Rack::Test könyvtárat ajánljuk:

      -
        require 'my_sinatra_app'
      -  require 'rack/test'
      -
      -  class MyAppTest < Test::Unit::TestCase
      -    include Rack::Test::Methods
      -
      -    def app
      -      Sinatra::Application
      -    end
      -
      -    def test_my_default
      -      get '/'
      -      assert_equal 'Helló Világ!', last_response.body
      -    end
      -
      -    def test_with_params
      -      get '/meet', :name => 'Frici'
      -      assert_equal 'Helló Frici!', last_response.body
      -    end
      -
      -    def test_with_rack_env
      -      get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      -      assert_equal "Songbird-öt használsz!", last_response.body
      -    end
      -  end
      -
      + +
      +
      +
        require 'my_sinatra_app'
      +  require 'rack/test'
      +
      +  class MyAppTest < Test::Unit::TestCase
      +    include Rack::Test::Methods
      +
      +    def app
      +      Sinatra::Application
      +    end
      +
      +    def test_my_default
      +      get '/'
      +      assert_equal 'Helló Világ!', last_response.body
      +    end
      +
      +    def test_with_params
      +      get '/meet', :name => 'Frici'
      +      assert_equal 'Helló Frici!', last_response.body
      +    end
      +
      +    def test_with_rack_env
      +      get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      +      assert_equal "Songbird-öt használsz!", last_response.body
      +    end
      +  end
      +
      +
      +
      +

      Megjegyzés: A beépített Sinatra::Test és Sinatra::TestHarness osztályok a 0.9.2-es kiadástól kezdve elavultnak számítanak.

      @@ -535,24 +750,36 @@

      Sinatra::Base - Middleware-ek, könyvtárak és moduláris alkalmazások

      ./public
      és ./views könyvtár meglétét, naplózást, kivételkezelő oldalt stb.). Itt jön a képbe a Sinatra::Base osztály:

      -
        require 'sinatra/base'
       
      -  class MyApp < Sinatra::Base
      -    set :sessions, true
      -    set :foo, 'bar'
      +
      +
      +
        require 'sinatra/base'
      +
      +  class MyApp < Sinatra::Base
      +    set :sessions, true
      +    set :foo, 'bar'
      +
      +    get '/' do
      +      'Helló Világ!'
      +    end
      +  end
      +
      +
      +
      - get '/' do - 'Helló Világ!' - end - end -

      A MyApp osztály immár önálló Rack komponensként, mondjuk Rack middleware-ként vagy alkalmazásként, esetleg Rails metal-ként is tud működni. Közvetlenül használhatod (use) vagy futtathatod (run) az osztályodat egy rackup konfigurációs állományban (config.ru), vagy egy szerverkomponenst tartalmazó könyvtár vezérlésekor:

      -
         MyApp.run! :host => 'localhost', :port => 9090
      -
      + +
      +
      +
         MyApp.run! :host => 'localhost', :port => 9090
      +
      +
      +
      +

      A Sinatra::Base gyermekosztályaiban elérhető metódusok egyúttal a felső szintű DSL-en keresztül is hozzáférhetők. A legtöbb felső szintű alkalmazás átalakítható Sinatra::Base alapú komponensekké két lépésben:

      @@ -576,8 +803,10 @@

      Sinatra::Base - Middleware-ek, könyvtárak és moduláris alkalmazások

      Parancssori lehetőségek

      Sinatra alkalmazásokat közvetlenül futtathatunk:

      -
        ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-s HANDLER]
      -
      + +
        ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-s HANDLER]
      +
      +

      Az alábbi kapcsolókat ismeri fel a rendszer:

      -h # segítség @@ -593,24 +822,34 @@

      Fejlesztői változat

      készíts egy helyi másolatot és indítsd az alkalmazásodat úgy, hogy a sinatra/lib könyvtár elérhető legyen a LOAD_PATH-on:

      -
        cd myapp
      +
      +
        cd myapp
         git clone git://github.com/sinatra/sinatra.git
         ruby -Isinatra/lib myapp.rb
      -
      +
      +

      De hozzá is adhatod a sinatra/lib könyvtárat a LOAD_PATH-hoz az alkalmazásodban:

      -
        $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
      -  require 'rubygems'
      -  require 'sinatra'
      -
      -  get '/about' do
      -    "A következő változatot futtatom " + Sinatra::VERSION
      -  end
      -
      + +
      +
      +
        $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
      +  require 'rubygems'
      +  require 'sinatra'
      +
      +  get '/about' do
      +    "A következő változatot futtatom " + Sinatra::VERSION
      +  end
      +
      +
      +
      +

      A Sinatra frissítését később így végezheted el:

      -
        cd myproject/sinatra
      +
      +
        cd myproject/sinatra
         git pull
      -
      +
      +

      További információk

      @@ -618,13 +857,14 @@

      További információk

    66. A projekt weboldala - Kiegészítő dokumentáció, hírek, hasznos linkek
    67. -
    68. +
    69. Közreműködés - Hibát találtál? Segítségre van szükséged? Foltot küldenél be?
    70. -
    71. +
    72. Lighthouse - Hibakövetés és kiadások
    73. -
    74. Twitter
    75. -
    76. Levelezőlista
    77. -
    78. [IRC: #sinatra](irc://chat.freenode.net/#sinatra) a http://freenode.net címen
    79. +
    80. Twitter
    81. +
    82. Levelezőlista
    83. +
    84. +IRC: #sinatra a http://freenode.net címen
    85. diff --git a/_includes/README.jp.html b/_includes/README.jp.html index 3655ee03..bca43c87 100644 --- a/_includes/README.jp.html +++ b/_includes/README.jp.html @@ -67,16 +67,20 @@ 本文書は英語から翻訳したものであり、その内容が最新でない場合もあります。最新の情報はオリジナルの英語版を参照して下さい。

      DSLです。

      -
      # myapp.rb
      +
      +
      # myapp.rb
       require 'sinatra'
       get '/' do
         'Hello world!'
       end
      -
      +
      +

      gemをインストールして動かしてみる。

      -
      gem install sinatra
      +
      +
      gem install sinatra
       ruby myapp.rb
      -
      +
      +

      localhost:4567 を見る。

      @@ -84,7 +88,8 @@

      ルート

      Sinatraでは、ルートはHTTPメソッドとURLマッチングパターンがペアになっています。 ルートはブロックに結び付けられています。

      -
      get '/' do
      +
      +
      get '/' do
         .. 何か見せる ..
       end
       
      @@ -99,26 +104,32 @@ 

      ルート

      delete '/' do .. 何か削除する .. end -
      +
      +

      ルートは定義された順番にマッチします。 リクエストに最初にマッチしたルートが呼び出されます。

      ルートのパターンは名前付きパラメータを含むことができ、 paramsハッシュで取得できます。

      -
      get '/hello/:name' do
      +
      +
      get '/hello/:name' do
         # matches "GET /hello/foo" and "GET /hello/bar"
         # params[:name] is 'foo' or 'bar'
         "Hello #{params[:name]}!"
       end
      -
      +
      +

      また、ブロックパラメータで名前付きパラメータにアクセスすることもできます。

      -
      get '/hello/:name' do |n|
      +
      +
      get '/hello/:name' do |n|
         "Hello #{n}!"
       end
      -
      +
      +

      ルートパターンはsplat(またはワイルドカード)を含むこともでき、 params[:splat] で取得できます。

      -
      get '/say/*/to/*' do
      +
      +
      get '/say/*/to/*' do
         # matches /say/hello/to/world
         params[:splat] # => ["hello", "world"]
       end
      @@ -127,36 +138,46 @@ 

      ルート

      # matches /download/path/to/file.xml params[:splat] # => ["path/to/file", "xml"] end -
      +
      +

      ブロックパラーメータを使用した場合:

      -
      get '/download/*.*' do |path, ext|
      +
      +
      get '/download/*.*' do |path, ext|
         [path, ext] # => ["path/to/file", "xml"]
       end
      -
      +
      +

      正規表現を使ったルート:

      -
      get %r{/hello/([\w]+)} do
      +
      +
      get %r{/hello/([\w]+)} do
         "Hello, #{params[:captures].first}!"
       end
      -
      +
      +

      ブロックパラーメータを使用した場合:

      -
      get %r{/hello/([\w]+)} do |c|
      +
      +
      get %r{/hello/([\w]+)} do |c|
         "Hello, #{c}!"
       end
      -
      +
      +

      条件

      ルートにはユーザエージェントのようなさまざまな条件を含めることができます。

      -
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      +
      +
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
         "You're using Songbird version #{params[:agent][0]}"
       end
       
       get '/foo' do
         # Matches non-songbird browsers
       end
      -
      +
      +

      ほかにhost_nameprovides条件が利用可能です:

      -
      get '/', :host_name => /^admin\./ do
      +
      +
      get '/', :host_name => /^admin\./ do
         "Admin Area, Access denied!"
       end
       
      @@ -167,9 +188,11 @@ 

      条件

      get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end -
      +
      +

      独自の条件を定義することも簡単にできます:

      -
      set(:probability) { |value| condition { rand <= value } }
      +
      +
      set(:probability) { |value| condition { rand <= value } }
       
       get '/win_a_car', :probability => 0.1 do
         "You won!"
      @@ -178,7 +201,8 @@ 

      条件

      get '/win_a_car' do "Sorry, you lost." end -
      +
      +

      戻り値

      @@ -190,21 +214,25 @@

      戻り値

      妥当なオブジェクトであればどのようなオブジェクトでも返すことができます:

      そのように、例えばストリーミングの例を簡単に実装することができます:

      -
      class Stream
      +
      +
      class Stream
         def each
           100.times { |i| yield "#{i}\n" }
         end
       end
       
       get('/') { Stream.new }
      -
      +
      +

      静的ファイル

      静的ファイルは./publicディレクトリから配信されます。 :public_folderオプションを指定することで別の場所を指定することができます。

      -
      set :public_folder, File.dirname(__FILE__) + '/static'
      -
      + +
      set :public_folder, File.dirname(__FILE__) + '/static'
      +
      +

      注意: この静的ファイル用のディレクトリ名はURL中に含まれません。 例えば、./public/css/style.csshttp://example.com/css/style.cssでアクセスできます。

      @@ -213,8 +241,10 @@

      ビュー / テンプレート

      テンプレートは./viewsディレクトリ下に配置されています。 他のディレクトリを使用する場合の例:

      -
      set :views, File.dirname(__FILE__) + '/templates'
      -
      + +
      set :views, File.dirname(__FILE__) + '/templates'
      +
      +

      テンプレートはシンボルを使用して参照させることを覚えておいて下さい。 サブデレクトリでもこの場合は:'subdir/template'のようにします。 レンダリングメソッドは文字列が渡されると、そのまま文字列を出力します。

      @@ -223,13 +253,15 @@

      ビュー / テンプレート

      Haml テンプレート

      hamlを使うにはhamlライブラリが必要です:

      -
      # hamlを読み込みます
      +
      +
      # hamlを読み込みます
       require 'haml'
       
       get '/' do
         haml :index
       end
      -
      +
      +

      ./views/index.hamlを表示します。

      Haml’s @@ -237,73 +269,85 @@

      Haml テンプレート

      はSinatraの設定でグローバルに設定することができます。 Options and Configurations, を参照してそれぞれ設定を上書きして下さい。

      -
      set :haml, {:format => :html5 } # デフォルトのフォーマットは:xhtml
      +
      +
      set :haml, {:format => :html5 } # デフォルトのフォーマットは:xhtml
       
       get '/' do
         haml :index, :haml_options => {:format => :html4 } # 上書き
       end
      -
      +
      +

      Erb テンプレート

      -
      # erbを読み込みます
      +
      +
      # erbを読み込みます
       require 'erb'
       
       get '/' do
         erb :index
       end
      -
      +
      +

      ./views/index.erbを表示します。

      Erubis

      erubisテンプレートを表示するには、erubisライブラリが必要です:

      -
      # erubisを読み込みます
      +
      +
      # erubisを読み込みます
       require 'erubis'
       
       get '/' do
         erubis :index
       end
      -
      +
      +

      ./views/index.erubisを表示します。

      Builder テンプレート

      builderを使うにはbuilderライブラリが必要です:

      -
      # builderを読み込みます
      +
      +
      # builderを読み込みます
       require 'builder'
       
       get '/' do
         builder :index
       end
      -
      +
      +

      ./views/index.builderを表示します。

      鋸 テンプレート

      鋸を使うには鋸ライブラリが必要です:

      -
      # 鋸を読み込みます
      +
      +
      # 鋸を読み込みます
       require 'nokogiri'
       
       get '/' do
         nokogiri :index
       end
      -
      +
      +

      ./views/index.nokogiriを表示します。

      Sass テンプレート

      Sassテンプレートを使うにはsassライブラリが必要です:

      -
      # hamlかsassを読み込みます
      +
      +
      # hamlかsassを読み込みます
       require 'sass'
       
       get '/stylesheet.css' do
         sass :stylesheet
       end
      -
      +
      +

      ./views/stylesheet.sassを表示します。

      Sass’ @@ -311,23 +355,27 @@

      Sass テンプレート

      はSinatraの設定でグローバルに設定することができます。 see Options and Configurations, を参照してそれぞれ設定を上書きして下さい。

      -
      set :sass, {:style => :compact } # デフォルトのSass styleは :nested
      +
      +
      set :sass, {:style => :compact } # デフォルトのSass styleは :nested
       
       get '/stylesheet.css' do
         sass :stylesheet, :sass_options => {:style => :expanded } # 上書き
       end
      -
      +
      +

      Scss テンプレート

      Scssテンプレートを使うにはsassライブラリが必要です:

      -
      # hamlかsassを読み込みます
      +
      +
      # hamlかsassを読み込みます
       require 'sass'
       
       get '/stylesheet.css' do
         scss :stylesheet
       end
      -
      +
      +

      ./views/stylesheet.scssを表示します。

      Sass’ @@ -335,194 +383,236 @@

      Scss テンプレート

      はSinatraの設定でグローバルに設定することができます。 see Options and Configurations, を参照してそれぞれ設定を上書きして下さい。

      -
      set :scss, :style => :compact # デフォルトのScss styleは:nested
      +
      +
      set :scss, :style => :compact # デフォルトのScss styleは:nested
       
       get '/stylesheet.css' do
         scss :stylesheet, :style => :expanded # 上書き
       end
      -
      +
      +

      Less テンプレート

      Lessテンプレートを使うにはlessライブラリが必要です:

      -
      # lessを読み込みます
      +
      +
      # lessを読み込みます
       require 'less'
       
       get '/stylesheet.css' do
         less :stylesheet
       end
      -
      +
      +

      ./views/stylesheet.lessを表示します。

      Liquid テンプレート

      Liquidテンプレートを使うにはliquidライブラリが必要です:

      -
      # liquidを読み込みます
      +
      +
      # liquidを読み込みます
       require 'liquid'
       
       get '/' do
         liquid :index
       end
      -
      +
      +

      ./views/index.liquidを表示します。

      LiquidテンプレートからRubyのメソッド(yieldを除く)を呼び出すことができないため、 ほぼ全ての場合にlocalsを指定する必要があるでしょう:

      -
      liquid :index, :locals => { :key => 'value' }
      -
      + +
      liquid :index, :locals => { :key => 'value' }
      +
      +

      Markdown テンプレート

      Markdownテンプレートを使うにはrdiscountライブラリが必要です:

      -
      # rdiscountを読み込みます
      +
      +
      # rdiscountを読み込みます
       require "rdiscount"
       
       get '/' do
         markdown :index
       end
      -
      +
      +

      ./views/index.markdownを表示します。(mdmkdも妥当な拡張子です)

      markdownからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です:

      -
      erb :overview, :locals => { :text => markdown(:introduction) }
      -
      + +
      erb :overview, :locals => { :text => markdown(:introduction) }
      +
      +

      他のテンプレートからmarkdownメソッドを呼び出してもよいことに注意してください:

      -
      %h1 Hello From Haml!
      +
      +
      %h1 Hello From Haml!
       %p= markdown(:greetings)
      -
      +
      +

      Textile テンプレート

      Textileテンプレートを使うにはRedClothライブラリが必要です:

      -
      # redclothを読み込みます
      +
      +
      # redclothを読み込みます
       require "redcloth"
       
       get '/' do
         textile :index
       end
      -
      +
      +

      ./views/index.textileを表示します。

      textileからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です:

      -
      erb :overview, :locals => { :text => textile(:introduction) }
      -
      + +
      erb :overview, :locals => { :text => textile(:introduction) }
      +
      +

      他のテンプレートからtextileメソッドを呼び出してもよいことに注意してください:

      -
      %h1 Hello From Haml!
      +
      +
      %h1 Hello From Haml!
       %p= textile(:greetings)
      -
      +
      +

      RDoc テンプレート

      RDocテンプレートを使うにはRDocライブラリが必要です:

      -
      # rdoc/markup/to_htmlを読み込みます
      +
      +
      # rdoc/markup/to_htmlを読み込みます
       require "rdoc"
       require "rdoc/markup/to_html"
       
       get '/' do
         rdoc :index
       end
      -
      +
      +

      ./views/index.rdocを表示します。

      rdocからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です:

      -
      erb :overview, :locals => { :text => rdoc(:introduction) }
      -
      + +
      erb :overview, :locals => { :text => rdoc(:introduction) }
      +
      +

      他のテンプレートからrdocメソッドを呼び出してもよいことに注意してください:

      -
      %h1 Hello From Haml!
      +
      +
      %h1 Hello From Haml!
       %p= rdoc(:greetings)
      -
      +
      +

      Radius テンプレート

      Radiusテンプレートを使うにはradiusライブラリが必要です:

      -
      # radiusを読み込みます
      +
      +
      # radiusを読み込みます
       require 'radius'
       
       get '/' do
         radius :index
       end
      -
      +
      +

      ./views/index.radiusを表示します。

      RadiusテンプレートからRubyのメソッド(yieldを除く)を呼び出すことができないため、 ほぼ全ての場合にlocalsを指定する必要があるでしょう:

      -
      radius :index, :locals => { :key => 'value' }
      -
      + +
      radius :index, :locals => { :key => 'value' }
      +
      +

      Markaby テンプレート

      Markabyテンプレートを使うにはmarkabyライブラリが必要です:

      -
      # markabyを読み込みます
      +
      +
      # markabyを読み込みます
       require 'markaby'
       
       get '/' do
         markaby :index
       end
      -
      +
      +

      ./views/index.mabを表示します。

      RABL テンプレート

      RABLテンプレートを使うにはrablライブラリが必要です:

      -
      # rablを読み込みます
      +
      +
      # rablを読み込みます
       require 'rabl'
       
       get '/' do
         rabl :index
       end
      -
      +
      +

      ./views/index.rablを表示します。

      Slim テンプレート

      Slimテンプレートを使うにはslimライブラリが必要です:

      -
      # slimを読み込みます
      +
      +
      # slimを読み込みます
       require 'slim'
       
       get '/' do
         slim :index
       end
      -
      +
      +

      ./views/index.slimを表示します。

      Creole テンプレート

      Creoleテンプレートを使うにはcreoleライブラリが必要です:

      -
      # creoleを読み込みます
      +
      +
      # creoleを読み込みます
       require 'creole'
       
       get '/' do
         creole :index
       end
      -
      +
      +

      ./views/index.creoleを表示します。

      CoffeeScript テンプレート

      CoffeeScriptテンプレートを表示するにはcoffee-scriptライブラリと`coffee`バイナリが必要です:

      -
      # coffee-scriptを読み込みます
      +
      +
      # coffee-scriptを読み込みます
       require 'coffee-script'
       
       get '/application.js' do
         coffee :application
       end
      -
      +
      +

      ./views/application.coffeeを表示します。

      インラインテンプレート

      -
      get '/' do
      +
      +
      get '/' do
         haml '%div.title Hello World'
       end
      -
      +
      +

      文字列をテンプレートとして表示します。

      @@ -531,24 +621,29 @@

      テンプレート内で変数にアクセスする

      テンプレートはルートハンドラと同じコンテキストの中で評価されます。. ルートハンドラでセットされたインスタンス変数は テンプレート内で直接使うことができます。

      -
      get '/:id' do
      +
      +
      get '/:id' do
         @foo = Foo.find(params[:id])
         haml '%h1= @foo.name'
       end
      -
      +
      +

      ローカル変数を明示的に定義することもできます。

      -
      get '/:id' do
      +
      +
      get '/:id' do
         foo = Foo.find(params[:id])
         haml '%h1= foo.name', :locals => { :foo => foo }
       end
      -
      +
      +

      このやり方は他のテンプレート内で部分テンプレートとして表示する時に典型的に使用されます。

      ファイル内テンプレート

      テンプレートはソースファイルの最後で定義することもできます。

      -
      require 'rubygems'
      +
      +
      require 'rubygems'
       require 'sinatra'
       
       get '/' do
      @@ -563,7 +658,8 @@ 

      ファイル内テンプレート

      @@ index %div.title Hello world!!!!! -
      +
      +

      注意: sinatraをrequireするファイル内で定義されたファイル内テンプレートは自動的に読み込まれます。 他のファイルで定義されているテンプレートを使うには @@ -573,7 +669,8 @@

      ファイル内テンプレート

      名前付きテンプレート

      テンプレートはトップレベルのtemplateメソッドで定義することができます。

      -
      template :layout do
      +
      +
      template :layout do
         "%html\n  =yield\n"
       end
       
      @@ -584,19 +681,23 @@ 

      名前付きテンプレート

      get '/' do haml :index end -
      +
      +

      「layout」というテンプレートが存在する場合、そのテンプレートファイルは他のテンプレートが 表示される度に使用されます。:layout => falseすることでlayoutsを無効にできます。

      -
      get '/' do
      +
      +
      get '/' do
         haml :index, :layout => !request.xhr?
       end
      -
      +
      +

      ヘルパー

      トップレベルのhelpersを使用してルートハンドラやテンプレートで使うヘルパメソッドを 定義できます。

      -
      helpers do
      +
      +
      helpers do
         def bar(name)
           "#{name}bar"
         end
      @@ -605,14 +706,16 @@ 

      ヘルパー

      get '/:name' do bar(params[:name]) end -
      +
      +

      フィルタ

      beforeフィルタはリクエストされたコンテキストを実行する前に評価され、 リクエストとレスポンスを変更することができます。フィルタ内でセットされた インスタンス変数はルーティングとテンプレートで使用できます。

      -
      before do
      +
      +
      before do
         @note = 'Hi!'
         request.path_info = '/foo/bar/baz'
       end
      @@ -621,48 +724,64 @@ 

      フィルタ

      @note #=> 'Hi!' params[:splat] #=> 'bar/baz' end -
      +
      +

      afterフィルタは同じコンテキストにあるリクエストの後に評価され、 同じくリクエストとレスポンスを変更することができます。 beforeフィルタとルートで設定されたインスタンス変数は、 afterフィルタからアクセスすることができます:

      -
      after do
      +
      +
      after do
         puts response.status
       end
      -
      +
      +

      フィルタにはオプションとしてパターンを渡すことができ、 この場合はリクエストのパスがパターンにマッチした場合のみフィルタが評価されます:

      -
      before '/protected/*' do
      +
      +
      before '/protected/*' do
         authenticate!
       end
       
       after '/create/:slug' do |slug|
         session[:last_slug] = slug
       end
      -
      +
      +

      強制終了

      ルートかbeforeフィルタ内で直ちに実行を終了する方法:

      -
      halt
      -
      + +
      halt
      +
      +

      ステータスを指定することができます:

      -
      halt 410
      -
      + +
      halt 410
      +
      +

      body部を指定することもできます …

      -
      halt 'ここにbodyを書く'
      -
      + +
      halt 'ここにbodyを書く'
      +
      +

      ステータスとbody部を指定する …

      -
      halt 401, '立ち去れ!'
      -
      + +
      halt 401, '立ち去れ!'
      +
      +

      ヘッダを指定:

      -
      halt 402, {'Content-Type' => 'text/plain'}, 'リベンジ'
      -
      + +
      halt 402, {'Content-Type' => 'text/plain'}, 'リベンジ'
      +
      +

      パッシング(Passing)

      ルートはpassを使って次のルートに飛ばすことができます:

      -
      get '/guess/:who' do
      +
      +
      get '/guess/:who' do
         pass unless params[:who] == 'Frank'
         "見つかっちゃった!"
       end
      @@ -670,7 +789,8 @@ 

      パッシング(Passing)

      get '/guess/*' do "はずれです!" end -
      +
      +

      ルートブロックからすぐに抜け出し、次にマッチするルートを実行します。 マッチするルートが見当たらない場合は404が返されます。

      @@ -678,7 +798,8 @@

      パッシング(Passing)

      リクエストオブジェクトへのアクセス

      受信するリクエストオブジェクトは、`request`メソッドを通じてリクエストレベル(フィルタ、ルート、エラーハンドラ)からアクセスすることができます:

      -
      # アプリケーションが http://example.com/example で動作している場合
      +
      +
      # アプリケーションが http://example.com/example で動作している場合
       get '/foo' do
         request.body              # クライアントによって送信されたリクエストボディ(下記参照)
         request.scheme            # "http"
      @@ -703,39 +824,50 @@ 

      リクエストオブジェクトへのアクセス

      request.secure? # false request.env # Rackによって渡された生のenvハッシュ end -
      +
      +

      script_namepath_infoなどのオプションは次のように利用することもできます:

      -
      before { request.path_info = "/" }
      +
      +
      before { request.path_info = "/" }
       
       get "/" do
         "全てのリクエストはここに来る"
       end
      -
      +
      +

      request.bodyはIOまたはStringIOのオブジェクトです:

      -
      post "/api" do
      +
      +
      post "/api" do
         request.body.rewind  # 既に読まれているときのため
         data = JSON.parse request.body.read
         "Hello #{data['name']}!"
       end
      -
      +
      +

      設定

      どの環境でも起動時に1回だけ実行されます。

      -
      configure do
      +
      +
      configure do
         ...
       end
      -
      +
      +

      環境(RACK_ENV環境変数)が:productionに設定されている時だけ実行する方法:

      -
      configure :production do
      +
      +
      configure :production do
         ...
       end
      -
      +
      +

      環境が:production:testの場合に設定する方法:

      -
      configure :production, :test do
      +
      +
      configure :production, :test do
         ...
       end
      -
      +
      +

      エラーハンドリング

      @@ -747,47 +879,61 @@

      Not Found

      Sinatra::NotFoundが起きた時か レスポンスのステータスコードが 404の時にnot_foundハンドラーが発動します。

      -
      not_found do
      +
      +
      not_found do
         'ファイルが存在しません'
       end
      -
      +
      +

      エラー

      error ハンドラーはルートブロックかbeforeフィルタ内で例外が発生した時はいつでも発動します。 例外オブジェクトはRack変数sinatra.errorから取得できます。

      -
      error do
      +
      +
      error do
         'エラーが発生しました。 - ' + env['sinatra.error'].name
       end
      -
      +
      +

      エラーをカスタマイズする場合は、

      -
      error MyCustomError do
      +
      +
      error MyCustomError do
         'エラーメッセージ...' + env['sinatra.error'].message
       end
      -
      +
      +

      と書いておいて,下記のように呼び出します。

      -
      get '/' do
      +
      +
      get '/' do
         raise MyCustomError, '何かがまずかったようです'
       end
      -
      +
      +

      そうするとこうなります:

      -
      エラーメッセージ... 何かがまずかったようです
      -
      + +
      エラーメッセージ... 何かがまずかったようです
      +
      +

      あるいは、ステータスコードに対応するエラーハンドラを設定することもできます:

      -
      error 403 do
      +
      +
      error 403 do
         'Access forbidden'
       end
       
       get '/secret' do
         403
       end
      -
      +
      +

      範囲指定もできます:

      -
      error 400..510 do
      +
      +
      error 400..510 do
         'Boom'
       end
      -
      +
      +

      開発環境として実行している場合、Sinatraは特別なnot_founderrorハンドラーを インストールしています。

      @@ -796,11 +942,15 @@

      MIMEタイプ

      send_fileか静的ファイルを使う時、Sinatraが理解でいないMIMEタイプがある場合があります。 その時は mime_type を使ってファイル拡張子毎に登録して下さい。

      -
      mime_type :foo, 'text/foo'
      -
      + +
      mime_type :foo, 'text/foo'
      +
      +

      これはcontent_typeヘルパで利用することができます:

      -
      content_type :foo
      -
      + +
      content_type :foo
      +
      +

      Rackミドルウェア

      @@ -812,7 +962,8 @@

      Rackミドルウェア

      Sinatraではトップレベルのuse メソッドを使ってRackにパイプラインを構築します。

      -
      require 'sinatra'
      +
      +
      require 'sinatra'
       require 'my_custom_middleware'
       
       use Rack::Lint
      @@ -821,15 +972,18 @@ 

      Rackミドルウェア

      get '/hello' do 'Hello World' end -
      +
      +

      use Rack::Builder DSLで定義されていることと全て一致します。 例えば use メソッドはブロック構文のように複数の引数を受け取ることができます。

      -
      use Rack::Auth::Basic do |username, password|
      +
      +
      use Rack::Auth::Basic do |username, password|
         username == 'admin' && password == 'secret'
       end
      -
      +
      +

      Rackはログ、デバッギング、URLルーティング、認証、セッションなどいろいろな機能を備えた標準的ミドルウェアです。 Sinatraはその多くのコンポーネントを自動で使うよう基本設定されているため、useで明示的に指定する必要はありません。

      @@ -839,7 +993,8 @@

      テスト

      SinatraでのテストはRack-basedのテストライブラリかフレームワークを使って書くことができます。 Rack::Test をおすすめします。やり方:

      -
      require 'my_sinatra_app'
      +
      +
      require 'my_sinatra_app'
       require 'rack/test'
       
       class MyAppTest < Test::Unit::TestCase
      @@ -864,7 +1019,8 @@ 

      テスト

      assert_equal "あなたはSongbirdを使ってますね!", last_response.body end end -
      +
      +

      注意: ビルトインのSinatra::TestモジュールとSinatra::TestHarnessクラスは 0.9.2リリース以降、廃止予定になっています。

      @@ -876,7 +1032,8 @@

      Sinatra::Base - ミドルウェア、ライブラリ、 モジュラーア ライブラリやSinatraの拡張プログラムを考慮するような場合はそうとは限りません。 トップレベルのDSLがネームスペースを汚染したり、設定を変えてしまうこと(例:./publicや./view)がありえます。 そこでSinatra::Baseの出番です。

      -
      require 'sinatra/base'
      +
      +
      require 'sinatra/base'
       
       class MyApp < Sinatra::Base
         set :sessions, true
      @@ -886,23 +1043,27 @@ 

      Sinatra::Base - ミドルウェア、ライブラリ、 モジュラーア 'Hello world!' end end -

      +
      +

      このMyAppは独立したRackコンポーネントで、RackミドルウェアやRackアプリケーション Rails metalとして使用することができます。config.ruファイル内で use か、または run でこのクラスを指定するか、ライブラリとしてサーバコンポーネントをコントロールします。

      -
      MyApp.run! :host => 'localhost', :port => 9090
      -
      + +
      MyApp.run! :host => 'localhost', :port => 9090
      +
      +

      Sinatra::Baseのサブクラスで使えるメソッドはトップレベルのDSLを経由して確実に使うことができます。 ほとんどのトップレベルで記述されたアプリは、以下の2点を修正することでSinatra::Baseコンポーネントに変えることができます。

        -
      • sinatraの代わりにsinatra/baseを読み込む
      • +
      • +sinatraの代わりにsinatra/baseを読み込む

      (そうしない場合、SinatraのDSLメソッドの全てがメインネームスペースにインポートされます)

        -
      • ルート、エラーハンドラー、フィルター、オプションをSinatra::Baseのサブクラスに書く
      • +
      • ルート、エラーハンドラー、フィルター、オプションをSinatra::Baseのサブクラスに書く

      Sinatra::Base はまっさらです。ビルトインサーバを含む、ほとんどのオプションがデフォルト @@ -927,7 +1088,8 @@

      Sinatraをミドルウェアとして利用する

      全てのSinatraアプリケーションは、それ自体ミドルウェアとして別のRackエンドポイントの前に追加することが可能です。

      このエンドポイントには、別のSinatraアプリケーションまたは他のRackベースのアプリケーション(Rails/Ramaze/Camping/…)が用いられるでしょう。

      -
      require 'sinatra/base'
      +
      +
      require 'sinatra/base'
       
       class LoginScreen < Sinatra::Base
         enable :sessions
      @@ -955,7 +1117,8 @@ 

      Sinatraをミドルウェアとして利用する

      get('/') { "Hello #{session['user_name']}." } end -
      +
      +

      スコープとバインディング

      @@ -971,7 +1134,8 @@

      アプリケーション/クラスのスコープ

      しかし`request`オブジェクトや`session`には、全てのリクエストのために1つのアプリケーションクラスが存在するためアクセスできません。

      `set`によって作られたオプションはクラスレベルのメソッドです:

      -
      class MyApp < Sinatra::Base
      +
      +
      class MyApp < Sinatra::Base
         # Hey, I'm in the application scope!
         set :foo, 42
         foo # => 42
      @@ -980,20 +1144,33 @@ 

      アプリケーション/クラスのスコープ

      # Hey, I'm no longer in the application scope! end end -
      +
      +

      次の場所ではアプリケーションスコープバインディングを持ちます:

        -
      • アプリケーションのクラス本体

      • -
      • 拡張によって定義されたメソッド

      • -
      • `helpers`に渡されたブロック

      • -
      • `set`の値として使われるProcまたはブロック

      • +
      • +

        アプリケーションのクラス本体

        +
      • +
      • +

        拡張によって定義されたメソッド

        +
      • +
      • +

        `helpers`に渡されたブロック

        +
      • +
      • +

        `set`の値として使われるProcまたはブロック

        +

      このスコープオブジェクト(クラス)は次のように利用できます:

        -
      • configureブロックに渡されたオブジェクト経由(configure { |c| ... })

      • -
      • リクエストスコープの中での`settings`

      • +
      • +

        configureブロックに渡されたオブジェクト経由(configure { |c| ... })

        +
      • +
      • +

        リクエストスコープの中での`settings`

        +

      リクエスト/インスタンスのスコープ

      @@ -1001,7 +1178,8 @@

      リクエスト/インスタンスのスコープ

      やってくるリクエストごとに、あなたのアプリケーションクラスの新しいインスタンスが作成され、全てのハンドラブロックがそのスコープで実行されます。 このスコープの内側からは`request`や`session`オブジェクトにアクセスすることができ、`erb`や`haml`のような表示メソッドを呼び出すことができます。 リクエストスコープの内側からは、`settings`ヘルパによってアプリケーションスコープにアクセスすることができます。

      -
      class MyApp < Sinatra::Base
      +
      +
      class MyApp < Sinatra::Base
         # Hey, I'm in the application scope!
         get '/define_route/:name' do
           # Request scope for '/define_route/:name'
      @@ -1015,14 +1193,23 @@ 

      リクエスト/インスタンスのスコープ

      "Route defined!" end end -
      +
      +

      次の場所ではリクエストスコープバインディングを持ちます:

        -
      • get/head/post/put/delete ブロック

      • -
      • before/after フィルタ

      • -
      • helper メソッド

      • -
      • テンプレート/ビュー

      • +
      • +

        get/head/post/put/delete ブロック

        +
      • +
      • +

        before/after フィルタ

        +
      • +
      • +

        helper メソッド

        +
      • +
      • +

        テンプレート/ビュー

        +

      デリゲートスコープ

      @@ -1036,8 +1223,12 @@

      デリゲートスコープ

      次の場所ではデリゲートスコープを持ちます:

        -
      • もしrequire "sinatra"しているならば、トップレベルバインディング

      • -
      • `Sinatra::Delegator` mixinでextendされたオブジェクト

      • +
      • +

        もしrequire "sinatra"しているならば、トップレベルバインディング

        +
      • +
      • +

        `Sinatra::Delegator` mixinでextendされたオブジェクト

        +

      コードをご覧ください: ここでは Sinatra::Delegator mixin @@ -1048,69 +1239,88 @@

      デリゲートスコープ

      コマンドライン

      Sinatraアプリケーションは直接実行できます。

      -
      ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
      -
      + +
      ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
      +
      +

      オプション:

      -
      -h # ヘルプ
      +
      +
      -h # ヘルプ
       -p # ポート指定(デフォルトは4567)
       -o # ホスト指定(デフォルトは0.0.0.0)
       -e # 環境を指定 (デフォルトはdevelopment)
       -s # rackserver/handlerを指定 (デフォルトはthin)
       -x # mutex lockを付ける (デフォルトはoff)
      -
      +
      +

      最新開発版について

      Sinatraの開発版を使いたい場合は、ローカルに開発版を落として、 LOAD_PATHsinatra/libディレクトリを指定して実行して下さい。

      -
      cd myapp
      +
      +
      cd myapp
       git clone git://github.com/sinatra/sinatra.git
       ruby -Isinatra/lib myapp.rb
      -
      +
      +

      sinatra/libディレクトリをアプリケーションのLOAD_PATHに追加する方法もあります。

      -
      $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
      +
      +
      $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
       require 'rubygems'
       require 'sinatra'
       
       get '/about' do
         "今使ってるバージョンは" + Sinatra::VERSION
       end
      -
      +
      +

      Sinatraのソースを更新する方法:

      -
      cd myproject/sinatra
      +
      +
      cd myproject/sinatra
       git pull
      -
      +
      +

      その他

      日本語サイト

      英語サイト

      diff --git a/_includes/README.ko.html b/_includes/README.ko.html index 1ce65513..c259f37c 100644 --- a/_includes/README.ko.html +++ b/_includes/README.ko.html @@ -21,7 +21,6 @@
    86. Markdown 템플릿
    87. Textile 템플릿
    88. RDoc 템플릿
    89. -
    90. Radius 템플릿
    91. Markaby 템플릿
    92. RABL 템플릿
    93. Slim 템플릿
    94. @@ -101,17 +100,29 @@

      주의: 이 문서는 영문판의 번역본이며 최신판 문서와 다를 수 있음.

      Sinatra는 최소한의 노력으로 루비 기반 웹 애플리케이션을 신속하게 만들 수 있게 해 주는 DSL이다:

      -
      # myapp.rb
      -require 'sinatra'
       
      -get '/' do
      -  'Hello world!'
      -end
      -
      +
      +
      +
      # myapp.rb
      +require 'sinatra'
      +
      +get '/' do
      +  'Hello world!'
      +end
      +
      +
      +
      +

      다음과 같이 젬을 설치하고 실행한다:

      -
      gem install sinatra
      -ruby myapp.rb
      -
      + +
      +
      +
      gem install sinatra
      +ruby myapp.rb
      +
      +
      +
      +

      확인: http://localhost:4567

      gem install thin도 함께 실행하기를 권장하며, 그럴 경우 Sinatra는 thin을 부른다.

      @@ -121,75 +132,123 @@

      라우터(Routes)

      Sinatra에서, 라우터(route)는 URL-매칭 패턴과 쌍을 이루는 HTTP 메서드다. 각각의 라우터는 블록과 연결된다:

      -
      get '/' do
      -  .. 무언가 보여주기(show) ..
      -end
      -
      -post '/' do
      -  .. 무언가 만들기(create) ..
      -end
      -
      -put '/' do
      -  .. 무언가 대체하기(replace) ..
      -end
      -
      -patch '/' do
      -  .. 무언가 수정하기(modify) ..
      -end
      -
      -delete '/' do
      -  .. 무언가 없애기(annihilate) ..
      -end
      -
      -options '/' do
      -  .. 무언가 주기(appease) ..
      -end
      -
      + +
      +
      +
      get '/' do
      +  .. 무언가 보여주기(show) ..
      +end
      +
      +post '/' do
      +  .. 무언가 만들기(create) ..
      +end
      +
      +put '/' do
      +  .. 무언가 대체하기(replace) ..
      +end
      +
      +patch '/' do
      +  .. 무언가 수정하기(modify) ..
      +end
      +
      +delete '/' do
      +  .. 무언가 없애기(annihilate) ..
      +end
      +
      +options '/' do
      +  .. 무언가 주기(appease) ..
      +end
      +
      +
      +
      +

      라우터는 정의된 순서에 따라 매치되며 매칭된 첫 번째 라우터가 호출된다.

      라우터 패턴에는 이름을 가진 매개변수가 포함될 수있으며, params 해시로 접근할 수 있다:

      -
      get '/hello/:name' do
      -  # "GET /hello/foo" 및 "GET /hello/bar"와 매치
      -  # params[:name]은 'foo' 또는 'bar'
      -  "Hello #{params[:name]}!"
      -end
      -
      + +
      +
      +
      get '/hello/:name' do
      +  # "GET /hello/foo" 및 "GET /hello/bar"와 매치
      +  # params[:name]은 'foo' 또는 'bar'
      +  "Hello #{params[:name]}!"
      +end
      +
      +
      +
      +

      또한 블록 매개변수를 통하여도 이름을 가진 매개변수에 접근할 수 있다:

      -
      get '/hello/:name' do |n|
      -  "Hello #{n}!"
      -end
      -
      + +
      +
      +
      get '/hello/:name' do |n|
      +  "Hello #{n}!"
      +end
      +
      +
      +
      +

      라우터 패턴에는 스플랫(splat, 또는 와일드카드)도 포함될 수 있으며, 이럴 경우 params[:splat] 배열로 접근할 수 있다:

      -
      get '/say/*/to/*' do
      -  # /say/hello/to/world와 매치
      -  params[:splat] # => ["hello", "world"]
      -end
      -
      -get '/download/*.*' do
      -  # /download/path/to/file.xml과 매치
      -  params[:splat] # => ["path/to/file", "xml"]
      -end
      -
      + +
      +
      +
      get '/say/*/to/*' do
      +  # /say/hello/to/world와 매치
      +  params[:splat] # => ["hello", "world"]
      +end
      +
      +get '/download/*.*' do
      +  # /download/path/to/file.xml과 매치
      +  params[:splat] # => ["path/to/file", "xml"]
      +end
      +
      +
      +
      +

      또는 블록 매개변수도 가능하다:

      -
      get '/download/*.*' do |path, ext|
      -  [path, ext] # => ["path/to/file", "xml"]
      -end
      -
      + +
      +
      +
      get '/download/*.*' do |path, ext|
      +  [path, ext] # => ["path/to/file", "xml"]
      +end
      +
      +
      +
      +

      정규표현식을 이용한 라우터 매칭:

      -
      get %r{/hello/([\w]+)} do
      -  "Hello, #{params[:captures].first}!"
      -end
      -
      + +
      +
      +
      get %r{/hello/([\w]+)} do
      +  "Hello, #{params[:captures].first}!"
      +end
      +
      +
      +
      +

      또는 블록 매개변수로도 가능:

      -
      get %r{/hello/([\w]+)} do |c|
      -  "Hello, #{c}!"
      -end
      -
      + +
      +
      +
      get %r{/hello/([\w]+)} do |c|
      +  "Hello, #{c}!"
      +end
      +
      +
      +
      +

      라우터 패턴에는 선택적인(optional) 매개변수도 올 수 있다:

      -
      get '/posts.?:format?' do
      -  # "GET /posts" 및 "GET /posts.json", "GET /posts.xml" 와 같은 어떤 확장자와도 매칭
      -end
      -
      + +
      +
      +
      get '/posts.?:format?' do
      +  # "GET /posts" 및 "GET /posts.json", "GET /posts.xml" 와 같은 어떤 확장자와도 매칭
      +end
      +
      +
      +
      +

      한편, 경로 탐색 공격 방지(path traversal attack protection, 아래 참조)를 비활성화시키지 않았다면, 요청 경로는 라우터와 매칭되기 이전에 수정될 수 있다.

      @@ -197,55 +256,79 @@

      라우터(Routes)

      조건(Conditions)

      라우터는 예를 들면 사용자 에이전트(user agent)와 같은 다양한 매칭 조건을 포함할 수 있다:

      -
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      -  "Songbird 버전 #{params[:agent][0]}을 사용하는군요!"
      -end
      -
      -get '/foo' do
      -  # songbird 브라우저가 아닌 경우 매치
      -end
      -
      + +
      +
      +
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      +  "Songbird 버전 #{params[:agent][0]}을 사용하는군요!"
      +end
      +
      +get '/foo' do
      +  # songbird 브라우저가 아닌 경우 매치
      +end
      +
      +
      +
      +

      그 밖에 다른 조건으로는 host_nameprovides가 있다:

      -
      get '/', :host_name => /^admin\./ do
      -  "Admin Area, Access denied!"
      -end
      -
      -get '/', :provides => 'html' do
      -  haml :index
      -end
      -
      -get '/', :provides => ['rss', 'atom', 'xml'] do
      -  builder :feed
      -end
      -
      + +
      +
      +
      get '/', :host_name => /^admin\./ do
      +  "Admin Area, Access denied!"
      +end
      +
      +get '/', :provides => 'html' do
      +  haml :index
      +end
      +
      +get '/', :provides => ['rss', 'atom', 'xml'] do
      +  builder :feed
      +end
      +
      +
      +
      +

      여러분만의 조건도 쉽게 정의할 수 있다:

      -
      set(:probability) { |value| condition { rand <= value } }
       
      -get '/win_a_car', :probability => 0.1 do
      -  "내가 졌소!"
      -end
      +
      +
      +
      set(:probability) { |value| condition { rand <= value } }
      +
      +get '/win_a_car', :probability => 0.1 do
      +  "내가 졌소!"
      +end
      +
      +get '/win_a_car' do
      +  "미안해서 어쩌나."
      +end
      +
      +
      +
      -get '/win_a_car' do - "미안해서 어쩌나." -end -

      여러 값을 받는 조건에는 스플랫(splat)을 사용하자:

      -
      set(:auth) do |*roles|   # <- 이게 스플랫
      -  condition do
      -    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      -      redirect "/login/", 303
      -    end
      -  end
      -end
      -
      -get "/my/account/", :auth => [:user, :admin] do
      -  "내 계정 정보"
      -end
      -
      -get "/only/admin/", :auth => :admin do
      -  "관리자 외 접근불가!"
      -end
      -
      + +
      +
      +
      set(:auth) do |*roles|   # <- 이게 스플랫
      +  condition do
      +    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      +      redirect "/login/", 303
      +    end
      +  end
      +end
      +
      +get "/my/account/", :auth => [:user, :admin] do
      +  "내 계정 정보"
      +end
      +
      +get "/only/admin/", :auth => :admin do
      +  "관리자 외 접근불가!"
      +end
      +
      +
      +
      +

      반환값(Return Values)

      @@ -255,14 +338,20 @@

      반환값(Return Values)

      유효한 Rack 응답, Rack 본문 객체 또는 HTTP 상태 코드가 되는 어떠한 객체라도 반환할 수 있다:

      이에 따라 우리는, 예를 들면, 스트리밍(streaming) 예제를 쉽게 구현할 수 있다:

      -
      class Stream
      -  def each
      -100.times { |i| yield "#{i}\n" }
      -  end
      -end
      -
      -get('/') { Stream.new }
      -
      + +
      +
      +
      class Stream
      +  def each
      +100.times { |i| yield "#{i}\n" }
      +  end
      +end
      +
      +get('/') { Stream.new }
      +
      +
      +
      +

      이런 번거로움을 줄이기 위해 stream 헬퍼 메서드(아래 참조)를 사용하여 스트리밍 로직을 라우터 속에 둘 수도 있다.

      @@ -270,45 +359,69 @@

      커스텀 라우터 매처(Custom Route Matchers)

      위에서 보듯, Sinatra에는 문자열 패턴 및 정규표현식을 이용한 라우터 매칭 지원이 내장되어 있다. 그렇지만, 그게 끝이 아니다. 여러분 만의 매처(matcher)도 쉽게 정의할 수 있다:

      -
      class AllButPattern
      -  Match = Struct.new(:captures)
      -
      -  def initialize(except)
      -    @except   = except
      -    @captures = Match.new([])
      -  end
      -
      -  def match(str)
      -    @captures unless @except === str
      -  end
      -end
      -
      -def all_but(pattern)
      -  AllButPattern.new(pattern)
      -end
      -
      -get all_but("/index") do
      -  # ...
      -end
      -
      + +
      +
      +
      class AllButPattern
      +  Match = Struct.new(:captures)
      +  
      +  def initialize(except)
      +    @except   = except
      +    @captures = Match.new([])
      +  end
      +  
      +  def match(str)
      +    @captures unless @except === str
      +  end
      +end
      +  
      +def all_but(pattern)
      +  AllButPattern.new(pattern)
      +end
      +
      +get all_but("/index") do
      +  # ...
      +end
      +
      +
      +
      +

      사실 위의 예제는 조금 과하게 작성된 면이 있다. 다음과 같이 표현할 수도 있다:

      -
      get // do
      -  pass if request.path_info == "/index"
      -  # ...
      -end
      -
      + +
      +
      +
      get // do
      +  pass if request.path_info == "/index"
      +  # ...
      +end
      +
      +
      +
      +

      또는 네거티브 룩어헤드(negative look ahead)를 사용할 수도 있다:

      -
      get %r{^(?!/index$)} do
      -  # ...
      -end
      -
      + +
      +
      +
      get %r{^(?!/index$)} do
      +  # ...
      +end
      +
      +
      +
      +

      정적 파일(Static Files)

      정적 파일들은 ./public에서 제공된다. 위치를 다른 곳으로 변경하려면 :public_folder 옵션을 사용하면 된다:

      -
      set :public_folder, File.dirname(__FILE__) + '/static'
      -
      + +
      +
      +
      set :public_folder, File.dirname(__FILE__) + '/static'
      +
      +
      +
      +

      이 때 public 디렉터리명은 URL에 포함되지 않는다는 점에 유의. ./public/css/style.css 파일은 http://example.com/css/style.css 로 접근할 수 있다.

      @@ -319,38 +432,68 @@

      뷰 / 템플릿(Views / Templates)

      각 템플릿 언어는 그들만의 고유한 렌더링 메서드를 통해 표출된다. 이들 메서드는 단순히 문자열을 반환한다.

      -
      get '/' do
      -  erb :index
      -end
      -
      + +
      +
      +
      get '/' do
      +  erb :index
      +end
      +
      +
      +
      +

      이 메서드는 views/index.erb를 렌더한다.

      템플릿 이름 대신 템플릿의 내용을 직접 전달할 수도 있다:

      -
      get '/' do
      -  code = "<%= Time.now %>"
      -  erb code
      -end
      -
      + +
      +
      +
      get '/' do
      +  code = "<%= Time.now %>"
      +  erb code
      +end
      +
      +
      +
      +

      템플릿은 두 번째 인자로 옵션값의 해시를 받는다:

      -
      get '/' do
      -  erb :index, :layout => :post
      -end
      -
      + +
      +
      +
      get '/' do
      +  erb :index, :layout => :post
      +end
      +
      +
      +
      +

      이렇게 하면 views/post.erb 속에 내장된 views/index.erb를 렌더한다. (기본값은 views/layout.erb이며, 이 파일이 존재할 경우에만 먹는다).

      Sinatra가 이해하지 못하는 모든 옵션값들은 템플릿 엔진으로 전달될 것이다:

      -
      get '/' do
      -  haml :index, :format => :html5
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml :index, :format => :html5
      +end
      +
      +
      +
      +

      옵션값은 템플릿 언어별로 일반적으로 설정할 수도 있다:

      -
      set :haml, :format => :html5
       
      -get '/' do
      -  haml :index
      -end
      -
      +
      +
      +
      set :haml, :format => :html5
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      +

      render 메서드에서 전달된 옵션값들은 set을 통해 설정한 옵션값을 덮어 쓴다.

      가능한 옵션값들:

      @@ -358,7 +501,7 @@

      뷰 / 템플릿(Views / Templates)

      locals
      문서로 전달되는 local 목록. 파셜과 함께 사용하기 좋음. -예제: erb "", :locals => {:foo => "bar"} +예제: erb "<%= foo %>", :locals => {:foo => "bar"}
      default_encoding
      @@ -386,8 +529,14 @@

      뷰 / 템플릿(Views / Templates)

      템플릿은 ./views 아래에 놓이는 것으로 가정됨. 만약 뷰 디렉터리를 다른 곳으로 두려면:

      -
      set :views, settings.root + '/templates'
      -
      + +
      +
      +
      set :views, settings.root + '/templates'
      +
      +
      +
      +

      꼭 알아야 할 중요한 점 한 가지는 템플릿은 언제나 심볼로 참조된다는 것이며, 템플릿이 하위 디렉터리에 위치한 경우라도 마찬가지임(그럴 경우에는 :'subdir/template'을 사용). 반드시 심볼이어야 하는 이유는, 만약 그렇게 하지 않으면 렌더링 메서드가 전달된 문자열을 직접 렌더하려 할 것이기 때문임.

      @@ -397,9 +546,15 @@

      가능한 템플릿 언어들(Available Template Languages)

      일부 언어는 여러 개의 구현이 있음. 어느 구현을 사용할지 저정하려면(그리고 스레드-안전thread-safe 모드로 하려면), 먼저 require 시키기만 하면 됨:

      -
      require 'rdiscount' # or require 'bluecloth'
      -get('/') { markdown :index }
      -
      + +
      +
      +
      require 'rdiscount' # or require 'bluecloth'
      +get('/') { markdown :index }
      +
      +
      +
      +

      Haml 템플릿

      @@ -571,12 +726,24 @@

      Markdown 템플릿

      마크다운에서는 메서드 호출 뿐 아니라 locals 전달도 안됨. 따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 될 것임:

      -
      erb :overview, :locals => { :text => markdown(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => markdown(:introduction) }
      +
      +
      +
      +

      또한 다른 템플릿 속에서 markdown 메서드를 호출할 수도 있음:

      -
      %h1 안녕 Haml!
      -%p= markdown(:greetings)
      -
      + +
      +
      +
      %h1 안녕 Haml!
      +%p= markdown(:greetings)
      +
      +
      +
      +

      Markdown에서 루비를 호출할 수 없기 때문에, Markdown으로 작성된 레이아웃은 사용할 수 없음. 단, :layout_engine 옵션으로 템플릿의 레이아웃은 다른 렌더링 엔진을 사용하는 것은 가능.

      @@ -599,12 +766,24 @@

      Textile 템플릿

      Textile에서 메서드를 호출하거나 locals를 전달하는 것은 불가능함. 따라서 일반적으로 다른 렌더링 엔진과 함께 사용하게 될 것임:

      -
      erb :overview, :locals => { :text => textile(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => textile(:introduction) }
      +
      +
      +
      +

      또한 다른 템플릿 속에서 textile 메서드를 호출할 수도 있음:

      -
      %h1 안녕 Haml!
      -%p= textile(:greetings)
      -
      + +
      +
      +
      %h1 안녕 Haml!
      +%p= textile(:greetings)
      +
      +
      +
      +

      Textile에서 루비를 호출할 수 없기 때문에, Textile로 작성된 레이아웃은 사용할 수 없음. 단, :layout_engine 옵션으로 템플릿의 레이아웃은 다른 렌더링 엔진을 사용하는 것은 가능.

      @@ -627,17 +806,27 @@

      RDoc 템플릿

      rdoc에서 메서드를 호출하거나 locals를 전달하는 것은 불가능함. 따라서 일반적으로 다른 렌더링 엔진과 함께 사용하게 될 것임:

      -
      erb :overview, :locals => { :text => rdoc(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => rdoc(:introduction) }
      +
      +
      +
      +

      또한 다른 템플릿 속에서 rdoc 메서드를 호출할 수도 있음:

      -
      %h1 Hello From Haml!
      -%p= rdoc(:greetings)
      -
      -

      RDoc에서 루비를 호출할 수 없기 때문에, RDoc로 작성된 레이아웃은 사용할 수 없음. -단, :layout_engine 옵션으로 템플릿의 레이아웃은 다른 렌더링 엔진을 사용하는 것은 가능.

      - -

      Radius 템플릿

      +
      +
      +
      %h1 Hello From Haml!
      +%p= rdoc(:greetings)
      +
      +
      +
      + +

      RDoc에서 루비를 호출할 수 없기 때문에, RDoc로 작성된 레이아웃은 사용할 수 없음. +단, :layout_engine 옵션으로 템플릿의 레이아웃은 다른 렌더링 엔진을 사용하는 것은 가능. +### Radius 템플릿

      @@ -727,12 +916,24 @@

      Creole 템플릿

      creole에서는 루비 메서드를 호출할 수 없고 locals도 전달할 수 없음. 따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 될 것임.

      -
      erb :overview, :locals => { :text => creole(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => creole(:introduction) }
      +
      +
      +
      +

      또한 다른 템플릿 속에서 creole 메서드를 호출할 수도 있음:

      -
      %h1 Hello From Haml!
      -%p= creole(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= creole(:greetings)
      +
      +
      +
      +

      Creole에서 루비를 호출할 수 없기 때문에, Creole로 작성된 레이아웃은 사용할 수 없음. 단, :layout_engine 옵션으로 템플릿의 레이아웃은 다른 렌더링 엔진을 사용하는 것은 가능.

      @@ -773,20 +974,38 @@

      Yajl 템플릿

      yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource' -

      The template source is evaluated as a Ruby string, and the resulting json variable is converted #tojson. -템플릿 소스는 루비 문자열로 평가(evaluate)되고, 결과인 json 변수는 #tojson으로 변환됨.

      -
      json = { :foo => 'bar' }
      -json[:baz] = key
      -
      +

      The template source is evaluated as a Ruby string, and the resulting json variable is converted #to_json. +템플릿 소스는 루비 문자열로 평가(evaluate)되고, 결과인 json 변수는 #to_json으로 변환됨.

      + +
      +
      +
      json = { :foo => 'bar' }
      +json[:baz] = key
      +
      +
      +
      +

      :callback:variable 옵션은 렌더된 객체를 꾸미는데(decorate) 사용할 수 있음.

      -
      var resource = {"foo":"bar","baz":"qux"}; present(resource);
      -
      + +
      +
      +
      var resource = {"foo":"bar","baz":"qux"}; present(resource);
      +
      +
      +
      +

      내장된(Embedded) 템플릿

      -
      get '/' do
      -  haml '%div.title Hello World'
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml '%div.title Hello World'
      +end
      +
      +
      +
      +

      내장된 템플릿 문자열을 렌더함.

      @@ -796,17 +1015,29 @@

      템플릿에서 변수에 접근하기

      variables set in route handlers are directly accessible by templates: 템플릿은 라우터 핸들러와 같은 맥락(context)에서 평가된다. 라우터 핸들러에서 설정한 인스턴스 변수들은 템플릿에서 접근 가능하다:

      -
      get '/:id' do
      -  @foo = Foo.find(params[:id])
      -  haml '%h1= @foo.name'
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  @foo = Foo.find(params[:id])
      +  haml '%h1= @foo.name'
      +end
      +
      +
      +
      +

      또는, 명시적으로 로컬 변수의 해시를 지정:

      -
      get '/:id' do
      -  foo = Foo.find(params[:id])
      -  haml '%h1= bar.name', :locals => { :bar => foo }
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  foo = Foo.find(params[:id])
      +  haml '%h1= bar.name', :locals => { :bar => foo }
      +end
      +
      +
      +
      +

      This is typically used when rendering templates as partials from within other templates. 이 방법은 통상적으로 템플릿을 다른 템플릿 속에서 파셜(partial)로 렌더링할 때 사용된다.

      @@ -815,21 +1046,27 @@

      템플릿에서 변수에 접근하기

      인라인 템플릿

      템플릿은 소스 파일의 마지막에서 정의할 수도 있다:

      -
      require 'sinatra'
       
      -get '/' do
      -  haml :index
      -end
      +
      +
      +
      require 'sinatra'
      +
      +get '/' do
      +  haml :index
      +end
       
      -__END__
      +__END__
       
       @@ layout
       %html
       = yield
       
       @@ index
      -%div.title Hello world.
      -
      +%div.title Hello world. +
      + + +

      참고: require sinatra 시킨 소스 파일에 정의된 인라인 템플릿은 자동으로 로드된다. 다른 소스 파일에서 인라인 템플릿을 사용하려면 명시적으로 enable :inline_templates을 호출하면 됨.

      @@ -837,46 +1074,70 @@

      인라인 템플릿

      이름을 가지는 템플릿(Named Templates)

      템플릿은 톱 레벨(top-level)에서 template메서드를 사용하여 정의할 수 있다:

      -
      template :layout do
      -  "%html\n  =yield\n"
      -end
      -
      -template :index do
      -  '%div.title Hello World!'
      -end
      -
      -get '/' do
      -  haml :index
      -end
      -
      -

      "layout"이라는 이름의 템플릿이 존재하면, 매번 템플릿이 렌더될 때마다 사용될 것이다. + +

      +
      +
      template :layout do
      +  "%html\n  =yield\n"
      +end
      +
      +template :index do
      +  '%div.title Hello World!'
      +end
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      + +

      “layout”이라는 이름의 템플릿이 존재하면, 매번 템플릿이 렌더될 때마다 사용될 것이다. 이 때 :layout => false를 전달하여 개별적으로 레이아웃을 비활성시키거나 또는 set :haml, :layout => false으로 기본값을 비활성으로 둘 수 있다:

      -
      get '/' do
      -  haml :index, :layout => !request.xhr?
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml :index, :layout => !request.xhr?
      +end
      +
      +
      +
      +

      파일 확장자 연결하기

      어떤 파일 확장자를 특정 템플릿 엔진과 연결하려면, Tilt.register를 사용하면 된다. 예를 들어, tt라는 파일 확장자를 Textile 템플릿과 연결하고 싶다면, 다음과 같이 하면 된다:

      -
      Tilt.register :tt, Tilt[:textile]
      -
      + +
      +
      +
      Tilt.register :tt, Tilt[:textile]
      +
      +
      +
      +

      나만의 고유한 템플릿 엔진 추가하기

      우선, Tilt로 여러분 엔진을 등록하고, 그런 다음 렌더링 메서드를 생성하자:

      -
      Tilt.register :myat, MyAwesomeTemplateEngine
       
      -helpers do
      -  def myat(*args) render(:myat, *args) end
      -end
      +
      +
      +
      Tilt.register :myat, MyAwesomeTemplateEngine
      +
      +helpers do
      +  def myat(*args) render(:myat, *args) end
      +end
      +
      +get '/' do
      +  myat :index
      +end
      +
      +
      +
      -get '/' do - myat :index -end -

      ./views/index.myat 를 렌더함. Tilt에 대한 더 자세한 내용은 https://github.com/rtomayko/tilt 참조.

      @@ -885,67 +1146,103 @@

      필터(Filters)

      사전 필터(before filter)는 라우터와 동일한 맥락에서 매 요청 전에 평가되며 요청과 응답을 변형할 수 있다. 필터에서 설정된 인스턴스 변수들은 라우터와 템플릿 속에서 접근 가능하다:

      -
      before do
      -  @note = 'Hi!'
      -  request.path_info = '/foo/bar/baz'
      -end
      -
      -get '/foo/*' do
      -  @note #=> 'Hi!'
      -  params[:splat] #=> 'bar/baz'
      -end
      -
      + +
      +
      +
      before do
      +  @note = 'Hi!'
      +  request.path_info = '/foo/bar/baz'
      +end
      +
      +get '/foo/*' do
      +  @note #=> 'Hi!'
      +  params[:splat] #=> 'bar/baz'
      +end
      +
      +
      +
      +

      사후 필터(after filter)는 라우터와 동일한 맥락에서 매 요청 이후에 평가되며 마찬가지로 요청과 응답을 변형할 수 있다. 사전 필터와 라우터에서 설정된 인스턴스 변수들은 사후 필터에서 접근 가능하다:

      -
      after do
      -  puts response.status
      -end
      -
      + +
      +
      +
      after do
      +  puts response.status
      +end
      +
      +
      +
      +

      참고: 만약 라우터에서 body 메서드를 사용하지 않고 그냥 문자열만 반환한 경우라면, body는 나중에 생성되는 탓에, 아직 사후 필터에서 사용할 수 없을 것이다.

      필터는 선택적으로 패턴을 취할 수 있으며, 이 경우 요청 경로가 그 패턴과 매치할 경우에만 필터가 평가될 것이다.

      -
      before '/protected/*' do
      -  authenticate!
      -end
      -
      -after '/create/:slug' do |slug|
      -  session[:last_slug] = slug
      -end
      -
      + +
      +
      +
      before '/protected/*' do
      +  authenticate!
      +end
      +
      +after '/create/:slug' do |slug|
      +  session[:last_slug] = slug
      +end
      +
      +
      +
      +

      라우터와 마찬가지로, 필터 역시 조건을 갖는다:

      -
      before :agent => /Songbird/ do
      -  # ...
      -end
      -
      -after '/blog/*', :host_name => 'example.com' do
      -  # ...
      -end
      -
      + +
      +
      +
      before :agent => /Songbird/ do
      +  # ...
      +end
      +
      +after '/blog/*', :host_name => 'example.com' do
      +  # ...
      +end
      +
      +
      +
      +

      헬퍼(Helpers)

      톱-레벨의 helpers 메서드를 사용하여 라우터 핸들러와 템플릿에서 사용할 헬퍼 메서드들을 정의할 수 있다:

      -
      helpers do
      -  def bar(name)
      -    "#{name}bar"
      -  end
      -end
      -
      -get '/:name' do
      -  bar(params[:name])
      -end
      -
      + +
      +
      +
      helpers do
      +  def bar(name)
      +    "#{name}bar"
      +  end
      +end
      +
      +get '/:name' do
      +  bar(params[:name])
      +end
      +
      +
      +
      +

      또는, 헬퍼 메서드는 별도의 모듈 속에 정의할 수도 있다:

      -
      module FooUtils
      -  def foo(name) "#{name}foo" end
      -end
       
      -module BarUtils
      -  def bar(name) "#{name}bar" end
      -end
      +
      +
      +
      module FooUtils
      +  def foo(name) "#{name}foo" end
      +end
      +
      +module BarUtils
      +  def bar(name) "#{name}bar" end
      +end
      +
      +helpers FooUtils, BarUtils
      +
      +
      +
      -helpers FooUtils, BarUtils -

      이 경우 모듈을 애플리케이션 클래스에 포함(include)시킨 것과 동일한 효과를 갖는다.

      @@ -953,74 +1250,140 @@

      세션(Sessions) 사용하기

      세션은 요청 동안에 상태를 유지하기 위해 사용한다. 세션이 활성화되면, 사용자 세션 당 session 해시 하나씩을 갖게 된다:

      -
      enable :sessions
       
      -get '/' do
      -  "value = " << session[:value].inspect
      -end
      +
      +
      +
      enable :sessions
      +
      +get '/' do
      +  "value = " << session[:value].inspect
      +end
      +
      +get '/:value' do
      +  session[:value] = params[:value]
      +end
      +
      +
      +
      -get '/:value' do - session[:value] = params[:value] -end -

      enable :sessions은 실은 모든 데이터를 쿠키 속에 저장함에 유의하자. 항상 이렇게 하고 싶지 않을 수도 있을 것이다(예를 들어, 많은 양의 데이터를 저장하게 되면 트래픽이 높아진다). 이 때는 여러 가지 랙 세션 미들웨어(Rack session middleware)를 사용할 수 있을 것이다: 이렇게 할 경우라면, enable :sessions을 호출하지 말고, 대신 여러분이 선택한 미들웨어를 다른 모든 미들웨어들처럼 포함시키면 된다:

      -
      use Rack::Session::Pool, :expire_after => 2592000
       
      -get '/' do
      -  "value = " << session[:value].inspect
      -end
      +
      +
      +
      use Rack::Session::Pool, :expire_after => 2592000
      +
      +get '/' do
      +  "value = " << session[:value].inspect
      +end
      +
      +get '/:value' do
      +  session[:value] = params[:value]
      +end
      +
      +
      +
      -get '/:value' do - session[:value] = params[:value] -end -

      보안을 위해서, 쿠키 속의 세션 데이터는 세션 시크릿(secret)으로 사인(sign)된다. Sinatra는 여러분을 위해 무작위 시크릿을 생성한다. 그렇지만, 이 시크릿은 여러분 애플리케이션 시작 시마다 변경될 수 있기 때문에, 여러분은 여러분 애플리케이션의 모든 인스턴스들이 공유할 시크릿을 직접 만들고 싶을 수도 있다:

      -
      set :session_secret, 'super secret'
      -
      + +
      +
      +
      set :session_secret, 'super secret'
      +
      +
      +
      +

      조금 더 세부적인 설정이 필요하다면, sessions 설정에서 옵션이 있는 해시를 저장할 수도 있을 것이다:

      -
      set :sessions, :domain => 'foo.com'
      -
      + +
      +
      +
      set :sessions, :domain => 'foo.com'
      +
      +
      +
      +

      중단하기(Halting)

      필터나 라우터에서 요청을 즉각 중단하고 싶을 때 사용하라:

      -
      halt
      -
      + +
      +
      +
      halt
      +
      +
      +
      +

      중단할 때 상태를 지정할 수도 있다:

      -
      halt 410
      -
      + +
      +
      +
      halt 410
      +
      +
      +
      +

      또는 본문을 넣을 수도 있다:

      -
      halt 'this will be the body'
      -
      + +
      +
      +
      halt 'this will be the body'
      +
      +
      +
      +

      또는 둘 다도 가능하다:

      -
      halt 401, 'go away!'
      -
      + +
      +
      +
      halt 401, 'go away!'
      +
      +
      +
      +

      헤더를 추가할 경우에는 다음과 같이 하면 된다:

      -
      halt 402, {'Content-Type' => 'text/plain'}, 'revenge'
      -
      + +
      +
      +
      halt 402, {'Content-Type' => 'text/plain'}, 'revenge'
      +
      +
      +
      +

      물론 halt를 템플릿과 결합하는 것도 가능하다:

      -
      halt erb(:error)
      -
      + +
      +
      +
      halt erb(:error)
      +
      +
      +
      +

      넘기기(Passing)

      라우터는 pass를 사용하여 다음 번 매칭되는 라우터로 처리를 넘길 수 있다:

      -
      get '/guess/:who' do
      -  pass unless params[:who] == 'Frank'
      -  'You got me!'
      -end
      -
      -get '/guess/*' do
      -  'You missed!'
      -end
      -
      + +
      +
      +
      get '/guess/:who' do
      +  pass unless params[:who] == 'Frank'
      +  'You got me!'
      +end
      +
      +get '/guess/*' do
      +  'You missed!'
      +end
      +
      +
      +
      +

      이 떄 라우터 블록에서 즉각 빠져나오게 되고 제어는 다음 번 매칭되는 라우터로 넘어간다. 만약 매칭되는 라우터를 찾지 못하면, 404가 반환된다.

      @@ -1029,15 +1392,21 @@

      다른 라우터 부르기(Triggering Another Route)

      경우에 따라서는 pass가 아니라, 다른 라우터를 호출한 결과를 얻고 싶은 경우도 있을 것이다. 이 때는 간단하게 +call+을 사용하면 된다:

      -
      get '/foo' do
      -  status, headers, body = call env.merge("PATH_INFO" => '/bar')
      -  [status, headers, body.map(&:upcase)]
      -end
      -
      -get '/bar' do
      -"bar"
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  status, headers, body = call env.merge("PATH_INFO" => '/bar')
      +  [status, headers, body.map(&:upcase)]
      +end
      +
      +get '/bar' do
      +"bar"
      +end
      +
      +
      +
      +

      위 예제의 경우, "bar"를 헬퍼로 옮겨 /foo/bar 모두에서 사용하도록 함으로써 테스팅을 쉽게 하고 성능을 높일 수 있을 것이다.

      @@ -1052,26 +1421,38 @@

      본문, 상태 코드 및 헤더 설정하기

      라우터 블록의 반환값과 함께 상태 코드(status code)와 응답 본문(response body)을 설정하는 것은 가능하기도 하거니와 권장되는 방법이다. 그렇지만, 경우에 따라서는 본문을 실행 흐름 중의 임의 지점에서 설정하고 싶을 수도 있다. 이 때는 body 헬퍼 메서드를 사용하면 된다. 이렇게 하면, 그 순간부터 본문에 접근할 때 그 메서드를 사용할 수가 있다:

      -
      get '/foo' do
      -  body "bar"
      -end
      -
      -after do
      -  puts body
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  body "bar"
      +end
      +
      +after do
      +  puts body
      +end
      +
      +
      +
      +

      body로 블록을 전달하는 것도 가능하며, 이 블록은 랙(Rack) 핸들러에 의해 실행될 것이다. -(이 방법은 스트리밍을 구현할 때 사용할 수 있는데, "값 반환하기"를 참고).

      +(이 방법은 스트리밍을 구현할 때 사용할 수 있는데, “값 반환하기”를 참고).

      본문와 마찬가지로, 상태코드와 헤더도 설정할 수 있다:

      -
      get '/foo' do
      -  status 418
      -  headers \
      -"Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
      -"Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
      -  body "I'm a tea pot!"
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  status 418
      +  headers \
      +"Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
      +"Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
      +  body "I'm a tea pot!"
      +end
      +
      +
      +
      +

      body처럼, headerstatus도 매개변수 없이 사용하여 그것의 현재 값을 액세스하는 데 사용될 수 있다.

      @@ -1080,16 +1461,22 @@

      응답 스트리밍(Streaming Responses)

      응답 본문의 일정 부분을 계속 생성하는 가운데 데이터를 내보내기 시작하고 싶을 경우도 있을 것이다. 극단적인 예제로, 클라이언트가 접속을 끊기 전까지 계속 데이터를 내보내고 싶을 수도 있다. 여러분만의 래퍼(wrapper)를 만들기 싫다면 stream 헬퍼를 사용하면 된다:

      -
      get '/' do
      -  stream do |out|
      -out << "It's gonna be legen -\n"
      -sleep 0.5
      -out << " (wait for it) \n"
      -sleep 1
      -out << "- dary!\n"
      -  end
      -end
      -
      + +
      +
      +
      get '/' do
      +  stream do |out|
      +out << "It's gonna be legen -\n"
      +sleep 0.5
      +out << " (wait for it) \n"
      +sleep 1
      +out << "- dary!\n"
      +  end
      +end
      +
      +
      +
      +

      이렇게 하면 스트리밍 API나 서버 발송 이벤트Server Sent Events를 구현할 수 있게 해 주며, WebSockets을 위한 기반으로 사용될 수 있다. @@ -1105,42 +1492,60 @@

      응답 스트리밍(Streaming Responses)

      따라서 여러분은 나중에 실행 흐름 상의 어느 시점에서 스트림을 닫을 수 있다. 이 옵션은 Thin과 Rainbow 같은 이벤트 기반 서버에서만 작동한다. 다른 서버들은 여전히 스트림을 닫을 것이다:

      -
      set :server, :thin
      -connections = []
      -
      -get '/' do
      -  # 스트림을 열린 채 유지
      -  stream(:keep_open) { |out| connections << out }
      -end
      -
      -post '/' do
      -  # 모든 열린 스트림에 쓰기
      -  connections.each { |out| out << params[:message] << "\n" }
      -  "message sent"
      -end
      -
      + +
      +
      +
      set :server, :thin
      +connections = []
      +
      +get '/' do
      +  # 스트림을 열린 채 유지
      +  stream(:keep_open) { |out| connections << out }
      +end
      +
      +post '/' do
      +  # 모든 열린 스트림에 쓰기
      +  connections.each { |out| out << params[:message] << "\n" }
      +  "message sent"
      +end
      +
      +
      +
      +

      로깅(Logging)

      In the request scope, the logger helper exposes a Logger instance: 요청 스코프(request scope) 내에서, Logger의 인스턴스인 logger 헬퍼를 사용할 수 있다:

      -
      get '/' do
      -  logger.info "loading data"
      -  # ...
      -end
      -
      + +
      +
      +
      get '/' do
      +  logger.info "loading data"
      +  # ...
      +end
      +
      +
      +
      +

      이 로거는 여러분이 Rack 핸들러에서 설정한 로그 셋팅을 자동으로 참고한다. 만약 로깅이 비활성이라면, 이 메서드는 더미(dummy) 객체를 반환할 것이며, 따라서 여러분은 라우터나 필터에서 이 부분에 대해 걱정할 필요는 없다.

      로깅은 Sinatra::Application에서만 기본으로 활성화되어 있음에 유의하자. 만약 Sinatra::Base로부터 상속받은 경우라면 직접 활성화시켜 줘야 한다:

      -
      class MyApp < Sinatra::Base
      -  configure :production, :development do
      -enable :logging
      -  end
      -end
      -
      + +
      +
      +
      class MyApp < Sinatra::Base
      +  configure :production, :development do
      +enable :logging
      +  end
      +end
      +
      +
      +
      +

      어떠한 로깅 미들웨어도 설정되지 않게 하려면, logging 설정을 nil로 두면 된다. 그렇지만, 이럴 경우 loggernil을 반환할 것임에 유의하자. 통상적인 유스케이스는 여러분만의 로거를 사용하고자 할 경우일 것이다. @@ -1151,22 +1556,40 @@

      마임 타입(Mime Types)

      send_file이나 정적인 파일을 사용할 때에 Sinatra가 인식하지 못하는 마임 타입이 있을 수 있다. 이 경우 mime_type을 사용하여 파일 확장자를 등록하면 된다:

      -
      configure do
      -  mime_type :foo, 'text/foo'
      -end
      -
      + +
      +
      +
      configure do
      +  mime_type :foo, 'text/foo'
      +end
      +
      +
      +
      +

      또는 content_type 헬퍼와 함께 사용할 수도 있다:

      -
      get '/' do
      -  content_type :foo
      -  "foo foo foo"
      -end
      -
      + +
      +
      +
      get '/' do
      +  content_type :foo
      +  "foo foo foo"
      +end
      +
      +
      +
      +

      URL 생성하기

      URL을 생성하려면 url 헬퍼 메서드를 사용해야 한다. 예를 들어 Haml에서:

      -
      %a{:href => url('/foo')} foo
      -
      + +
      +
      +
      %a{:href => url('/foo')} foo
      +
      +
      +
      +

      이것은 리버스 프록시(reverse proxies)와 Rack 라우터를, 만약 존재한다면, 참고한다.

      This method is also aliased to to (see below for an example). @@ -1176,114 +1599,204 @@

      URL 생성하기

      브라우저 재지정(Browser Redirect)

      redirect 헬퍼 메서드를 사용하여 브라우저 리다이렉트를 촉발시킬 수 있다:

      -
      get '/foo' do
      -  redirect to('/bar')
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  redirect to('/bar')
      +end
      +
      +
      +
      +

      여타 부가적인 매개변수들은 halt에서 전달한 인자들처럼 다루어 진다:

      -
      redirect to('/bar'), 303
      -redirect 'http://google.com', 'wrong place, buddy'
      -
      + +
      +
      +
      redirect to('/bar'), 303
      +redirect 'http://google.com', 'wrong place, buddy'
      +
      +
      +
      +

      redirect back을 사용하면 사용자가 왔던 페이지로 다시 돌아가는 리다이렉트도 쉽게 할 수 있다:

      -
      get '/foo' do
      -  "<a href='/bar'>do something</a>"
      -end
      -
      -get '/bar' do
      -  do_something
      -  redirect back
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  "<a href='/bar'>do something</a>"
      +end
      +
      +get '/bar' do
      +  do_something
      +  redirect back
      +end
      +
      +
      +
      +

      리다이렉트와 함께 인자를 전달하려면, 쿼리에 붙이거나:

      -
      redirect to('/bar?sum=42')
      -
      + +
      +
      +
      redirect to('/bar?sum=42')
      +
      +
      +
      +

      또는 세션을 사용하면 된다:

      -
      enable :sessions
       
      -get '/foo' do
      -  session[:secret] = 'foo'
      -  redirect to('/bar')
      -end
      +
      +
      +
      enable :sessions
      +
      +get '/foo' do
      +  session[:secret] = 'foo'
      +  redirect to('/bar')
      +end
      +
      +get '/bar' do
      +  session[:secret]
      +end
      +
      +
      +
      -get '/bar' do - session[:secret] -end -

      캐시 컨트롤(Cache Control)

      헤더를 정확하게 설정하는 것은 적절한 HTTP 캐싱의 기본이다.

      Cache-Control 헤더를 다음과 같이 간단하게 설정할 수 있다:

      -
      get '/' do
      -  cache_control :public
      -  "cache it!"
      -end
      -
      + +
      +
      +
      get '/' do
      +  cache_control :public
      +  "cache it!"
      +end
      +
      +
      +
      +

      프로 팁: 캐싱은 사전 필터에서 설정하라:

      -
      before do
      -  cache_control :public, :must_revalidate, :max_age => 60
      -end
      -
      + +
      +
      +
      before do
      +  cache_control :public, :must_revalidate, :max_age => 60
      +end
      +
      +
      +
      +

      expires 헬퍼를 사용하여 그에 상응하는 헤더를 설정한다면, Cache-Control이 자동으로 설정될 것이다:

      -
      before do
      -  expires 500, :public, :must_revalidate
      -end
      -
      + +
      +
      +
      before do
      +  expires 500, :public, :must_revalidate
      +end
      +
      +
      +
      +

      캐시를 잘 사용하려면, etag 또는 last_modified의 사용을 고려해야 할 것이다. 무거운 작업을 하기 에 이들 헬퍼를 호출할 것을 권장하는데, 이러면 만약 클라이언트 캐시에 현재 버전이 이미 들어 있을 경우엔 즉각 응답을 반환(flush)하게 될 것이다:

      -
      get '/article/:id' do
      -  @article = Article.find params[:id]
      -  last_modified @article.updated_at
      -  etag @article.sha1
      -  erb :article
      -end
      -
      + +
      +
      +
      get '/article/:id' do
      +  @article = Article.find params[:id]
      +  last_modified @article.updated_at
      +  etag @article.sha1
      +  erb :article
      +end
      +
      +
      +
      +

      약한 ETag를 사용하는 것도 가능하다:

      -
      etag @article.sha1, :weak
      -
      + +
      +
      +
      etag @article.sha1, :weak
      +
      +
      +
      +

      이들 헬퍼는 어떠한 캐싱도 하지 않으며, 대신 필요한 정보를 캐시에 제공한다. 여러분이 만약 손쉬운 리버스 프록시(reverse-proxy) 캐싱 솔루션을 찾고 있다면, rack-cache를 써보라:

      -
      require "rack/cache"
      -require "sinatra"
       
      -use Rack::Cache
      +
      +
      +
      require "rack/cache"
      +require "sinatra"
      +
      +use Rack::Cache
      +
      +get '/' do
      +  cache_control :public, :max_age => 36000
      +  sleep 5
      +  "hello"
      +end
      +
      +
      +
      -get '/' do - cache_control :public, :max_age => 36000 - sleep 5 - "hello" -end -

      정적 파일에 Cache-Control 헤더 정보를 추가하려면 :static_cache_control 설정(아래 참조)을 사용하라:

      RFC 2616에 따르면 If-Match 또는 If-None-Match 헤더가 *로 설정된 경우 요청한 리소스(resource)가 이미 존재하느냐 여부에 따라 다르게 취급해야 한다고 되어 있다. Sinatra는 (get 처럼) 안전하거나 (put 처럼) 멱등인 요청에 대한 리소스는 이미 존재한다고 가정하며, 반면 다른 리소스(예를 들면 post 요청 같은)의 경우는 새 리소스로 취급한다. 이런 설정은 :new_resource 옵션으로 전달하여 변경할 수 있다:

      -
      get '/create' do
      -  etag '', :new_resource => true
      -  Article.create
      -  erb :new_article
      -end
      -
      + +
      +
      +
      get '/create' do
      +  etag '', :new_resource => true
      +  Article.create
      +  erb :new_article
      +end
      +
      +
      +
      +

      여전히 약한 ETag를 사용하고자 한다면, :kind으로 전달하자:

      -
      etag '', :new_resource => true, :kind => :weak
      -
      + +
      +
      +
      etag '', :new_resource => true, :kind => :weak
      +
      +
      +
      +

      파일 전송하기(Sending Files)

      파일을 전송하려면, send_file 헬퍼 메서드를 사용하면 된다:

      -
      get '/' do
      -  send_file 'foo.png'
      -end
      -
      + +
      +
      +
      get '/' do
      +  send_file 'foo.png'
      +end
      +
      +
      +
      +

      이 메서드는 몇 가지 옵션을 받는다:

      -
      send_file 'foo.png', :type => :jpg
      -
      + +
      +
      +
      send_file 'foo.png', :type => :jpg
      +
      +
      +
      +

      옵션들:

      @@ -1314,128 +1827,188 @@

      파일 전송하기(Sending Files)

      요청 객체에 접근하기(Accessing the Request Object)

      인입되는 요청 객에는 요청 레벨(필터, 라우터, 오류 핸들러)에서 request 메서드를 통해 접근 가능하다:

      -
      # http://example.com/example 상에서 실행 중인 앱
      -get '/foo' do
      -  t = %w[text/css text/html application/javascript]
      -  request.accept              # ['text/html', '*/*']
      -  request.accept? 'text/xml'  # true
      -  request.preferred_type(t)   # 'text/html'
      -  request.body                # 클라이언트로부터 전송된 요청 본문 (아래 참조)
      -  request.scheme              # "http"
      -  request.script_name         # "/example"
      -  request.path_info           # "/foo"
      -  request.port                # 80
      -  request.request_method      # "GET"
      -  request.query_string        # ""
      -  request.content_length      # request.body의 길이
      -  request.media_type          # request.body의 미디어 유형
      -  request.host                # "example.com"
      -  request.get?                # true (다른 동사에 대해 유사한 메서드 있음)
      -  request.form_data?          # false
      -  request["SOME_HEADER"]      # SOME_HEADER 헤더의 값
      -  request.referrer            # 클라이언트의 리퍼러 또는 '/'
      -  request.user_agent          # 사용자 에이전트 (:agent 조건에서 사용됨)
      -  request.cookies             # 브라우저 쿠키의 해시
      -  request.xhr?                # 이게 ajax 요청인가요?
      -  request.url                 # "http://example.com/example/foo"
      -  request.path                # "/example/foo"
      -  request.ip                  # 클라이언트 IP 주소
      -  request.secure?             # false (ssl 접속인 경우 true)
      -  request.forwarded?          # true (리버스 프록시 하에서 작동 중이라면)
      -  request.env                 # Rack에 의해 처리되는 로우(raw) env 해시
      -end
      -
      + +
      +
      +
      # http://example.com/example 상에서 실행 중인 앱
      +get '/foo' do
      +  t = %w[text/css text/html application/javascript]
      +  request.accept              # ['text/html', '*/*']
      +  request.accept? 'text/xml'  # true
      +  request.preferred_type(t)   # 'text/html'
      +  request.body                # 클라이언트로부터 전송된 요청 본문 (아래 참조)
      +  request.scheme              # "http"
      +  request.script_name         # "/example"
      +  request.path_info           # "/foo"
      +  request.port                # 80
      +  request.request_method      # "GET"
      +  request.query_string        # ""
      +  request.content_length      # request.body의 길이
      +  request.media_type          # request.body의 미디어 유형
      +  request.host                # "example.com"
      +  request.get?                # true (다른 동사에 대해 유사한 메서드 있음)
      +  request.form_data?          # false
      +  request["SOME_HEADER"]      # SOME_HEADER 헤더의 값
      +  request.referrer            # 클라이언트의 리퍼러 또는 '/'
      +  request.user_agent          # 사용자 에이전트 (:agent 조건에서 사용됨)
      +  request.cookies             # 브라우저 쿠키의 해시
      +  request.xhr?                # 이게 ajax 요청인가요?
      +  request.url                 # "http://example.com/example/foo"
      +  request.path                # "/example/foo"
      +  request.ip                  # 클라이언트 IP 주소
      +  request.secure?             # false (ssl 접속인 경우 true)
      +  request.forwarded?          # true (리버스 프록시 하에서 작동 중이라면)
      +  request.env                 # Rack에 의해 처리되는 로우(raw) env 해시
      +end
      +
      +
      +
      +

      일부 옵션들, script_name 또는 path_info와 같은 일부 옵션은 쓸 수도 있다:

      -
      before { request.path_info = "/" }
       
      -get "/" do
      -  "all requests end up here"
      -end
      -
      +
      +
      +
      before { request.path_info = "/" }
      +
      +get "/" do
      +  "all requests end up here"
      +end
      +
      +
      +
      +

      request.body는 IO 또는 StringIO 객체이다:

      -
      post "/api" do
      -  request.body.rewind  # 누군가 이미 읽은 경우
      -  data = JSON.parse request.body.read
      -  "Hello #{data['name']}!"
      -end
      -
      + +
      +
      +
      post "/api" do
      +  request.body.rewind  # 누군가 이미 읽은 경우
      +  data = JSON.parse request.body.read
      +  "Hello #{data['name']}!"
      +end
      +
      +
      +
      +

      첨부(Attachments)

      attachment 헬퍼를 사용하여 브라우저에게 응답이 브라우저에 표시되는 게 아니라 디스크에 저장되어야 함을 알릴 수 있다:

      -
      get '/' do
      -  attachment
      -  "store it!"
      -end
      -
      + +
      +
      +
      get '/' do
      +  attachment
      +  "store it!"
      +end
      +
      +
      +
      +

      이 때 파일명을 전달할 수도 있다:

      -
      get '/' do
      -  attachment "info.txt"
      -  "store it!"
      -end
      -
      + +
      +
      +
      get '/' do
      +  attachment "info.txt"
      +  "store it!"
      +end
      +
      +
      +
      +

      날짜와 시간 다루기

      Sinatra는 time_for_ 헬퍼 메서드를 제공하는데, 이 메서드는 주어진 값으로부터 Time 객체를 생성한다. DateTime 이나 Date 또는 유사한 클래스들도 변환 가능하다:

      -
      get '/' do
      -  pass if Time.now > time_for('Dec 23, 2012')
      -  "still time"
      -end
      -
      + +
      +
      +
      get '/' do
      +  pass if Time.now > time_for('Dec 23, 2012')
      +  "still time"
      +end
      +
      +
      +
      +

      이 메서드는 내부적으로 expireslast_modified 같은 곳에서 사용된다. 따라서 여러분은 애플리케이션에서 time_for를 오버라이딩하여 이들 메서드의 동작을 쉽게 확장할 수 있다:

      -
      helpers do
      -  def time_for(value)
      -    case value
      -    when :yesterday then Time.now - 24*60*60
      -    when :tomorrow  then Time.now + 24*60*60
      -    else super
      -    end
      -  end
      -end
      -
      -get '/' do
      -  last_modified :yesterday
      -  expires :tomorrow
      -  "hello"
      -end
      -
      + +
      +
      +
      helpers do
      +  def time_for(value)
      +    case value
      +    when :yesterday then Time.now - 24*60*60
      +    when :tomorrow  then Time.now + 24*60*60
      +    else super
      +    end
      +  end
      +end
      +
      +get '/' do
      +  last_modified :yesterday
      +  expires :tomorrow
      +  "hello"
      +end
      +
      +
      +
      +

      템플릿 파일 참조하기

      find_template는 렌더링할 템플릿 파일을 찾는데 사용된다:

      -
      find_template settings.views, 'foo', Tilt[:haml] do |file|
      -  puts "could be #{file}"
      -end
      -
      + +
      +
      +
      find_template settings.views, 'foo', Tilt[:haml] do |file|
      +  puts "could be #{file}"
      +end
      +
      +
      +
      +

      This is not really useful. But it is useful that you can actually override this method to hook in your own lookup mechanism. For instance, if you want to be able to use more than one view directory: 이건 별로 유용하지 않다. 그렇지만 이 메서드를 오버라이드하여 여러분만의 참조 메커니즘에서 가로채는 것은 유용하다. 예를 들어, 하나 이상의 뷰 디렉터리를 사용하고자 한다면:

      -
      set :views, ['views', 'templates']
      -
      -helpers do
      -  def find_template(views, name, engine, &block)
      -    Array(views).each { |v| super(v, name, engine, &block) }
      -  end
      -end
      -
      + +
      +
      +
      set :views, ['views', 'templates']
      +
      +helpers do
      +  def find_template(views, name, engine, &block)
      +    Array(views).each { |v| super(v, name, engine, &block) }
      +  end
      +end
      +
      +
      +
      +

      또다른 예제는 각각의 엔진마다 다른 디렉터리를 사용할 경우다:

      -
      set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
      -
      -helpers do
      -  def find_template(views, name, engine, &block)
      -    _, folder = views.detect { |k,v| engine == Tilt[k] }
      -    folder ||= views[:default]
      -    super(folder, name, engine, &block)
      -  end
      -end
      -
      + +
      +
      +
      set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
      +
      +helpers do
      +  def find_template(views, name, engine, &block)
      +    _, folder = views.detect { |k,v| engine == Tilt[k] }
      +    folder ||= views[:default]
      +    super(folder, name, engine, &block)
      +  end
      +end
      +
      +
      +
      +

      여러분은 이것을 간단하게 확장(extension)으로 만들어 다른 사람들과 공유할 수 있다!

      find_template은 그 파일이 실제 존재하는지 검사하지 않음에 유의하자. @@ -1448,58 +2021,100 @@

      템플릿 파일 참조하기

      설정(Configuration)

      모든 환경에서, 시작될 때, 한번만 실행:

      -
      configure do
      -  # 옵션 하나 설정
      -  set :option, 'value'
       
      -  # 여러 옵션 설정
      -  set :a => 1, :b => 2
      +
      +
      +
      configure do
      +  # 옵션 하나 설정
      +  set :option, 'value'
      +  
      +  # 여러 옵션 설정
      +  set :a => 1, :b => 2
      +  
      +  # `set :option, true`와 동일
      +  enable :option
      +  
      +  # `set :option, false`와 동일
      +  disable :option
      +  
      +  # 블록으로 동적인 설정을 할 수도 있음
      +  set(:css_dir) { File.join(views, 'css') }
      +end
      +
      +
      +
      - # `set :option, true`와 동일 - enable :option +

      환경(RACK_ENV 환경 변수)이 :production일 때만 실행:

      - # `set :option, false`와 동일 - disable :option +
      +
      +
      configure :production do
      +  ...
      +end
      +
      +
      +
      - # 블록으로 동적인 설정을 할 수도 있음 - set(:css_dir) { File.join(views, 'css') } -end -
      -

      환경(RACK_ENV 환경 변수)이 :production일 때만 실행:

      -
      configure :production do
      -  ...
      -end
      -

      환경이 :production 또는 :test일 때 실행:

      -
      configure :production, :test do
      -  ...
      -end
      -
      + +
      +
      +
      configure :production, :test do
      +  ...
      +end
      +
      +
      +
      +

      이들 옵션은 settings를 통해 접근 가능하다:

      -
      configure do
      -  set :foo, 'bar'
      -end
      -
      -get '/' do
      -  settings.foo? # => true
      -  settings.foo  # => 'bar'
      -  ...
      -end
      -
      + +
      +
      +
      configure do
      +  set :foo, 'bar'
      +end
      +
      +get '/' do
      +  settings.foo? # => true
      +  settings.foo  # => 'bar'
      +  ...
      +end
      +
      +
      +
      +

      공격 방어 설정하기(Configuring attack protection)

      Sinatra는 Rack::Protection을 사용하여 일반적인, 일어날 수 있는 공격에 대비한다. 이 부분은 간단하게 비활성시킬 수 있다(성능 향상 효과를 가져올 것이다):

      -
      disable :protection
      -
      + +
      +
      +
      disable :protection
      +
      +
      +
      +

      하나의 방어층만 스킵하려면, 옵션 해시에 protection을 설정하면 된다:

      -
      set :protection, :except => :path_traversal
      -
      + +
      +
      +
      set :protection, :except => :path_traversal
      +
      +
      +
      +

      방어막 여러 개를 비활성하려면, 배열로 주면 된다:

      -
      set :protection, :except => [:path_traversal, :session_hijacking]
      -
      + +
      +
      +
      set :protection, :except => [:path_traversal, :session_hijacking]
      +
      +
      +
      +

      가능한 설정들(Available Settings)

      @@ -1523,28 +2138,28 @@

      가능한 설정들(Available Settings)

      이 옵션은 오버라이딩하지 말고 추가해야 한다: - settings.add_charsets - -
      + settings.add_charsets << "application/foobar" + -
      app_file
      -
      - -
      메인 애플리케이션 파일의 경로. 프로젝트 루트와 뷰, 그리고 public 폴더, 인라인 템플릿을 - 파악할 때 사용됨.
      +
      app_file
      +
      메인 애플리케이션 파일의 경로. 프로젝트 루트와 뷰, 그리고 public 폴더, 인라인 템플릿을 + 파악할 때 사용됨. +
      -
      bind
      -
      - -
      바인드할 IP 주소(기본값: 0.0.0.0). - 오직 빌트인(built-in) 서버에서만 사용됨.
      +
      bind
      +
      바인드할 IP 주소(기본값: 0.0.0.0). + 오직 빌트인(built-in) 서버에서만 사용됨. +
      default_encoding
      모를 때 가정할 인코딩 - (기본값은 "utf-8").
      + (기본값은 "utf-8"). +
      dump_errors
      -
      로그로 에러 출력.
      +
      + 로그로 에러 출력. +
      environment
      현재 환경, 기본값은 ENV['RACK_ENV'] 또는 알 수 없을 경우 "development".
      @@ -1637,15 +2252,21 @@

      가능한 설정들(Available Settings)

      환경(Environments)

      -

      환경은 RACK_ENV 환경 변수를 통해서도 설정할 수 있다. 기본값은 "development"다. +

      환경은 RACK_ENV 환경 변수를 통해서도 설정할 수 있다. 기본값은 “development”다. 이 모드에서, 모든 템플릿들은 요청 간에 리로드된다. 특별한 not_founderror 핸들러가 이 환경에 설치되기 때문에 브라우저에서 스택 추적을 볼 수 있을 것이다. "production""test"에서는 템플릿은 캐시되는 게 기본값이다.

      다른 환경으로 실행시키려면 -e옵션을 사용하면 된다:

      -
      ruby my_app.rb -e [ENVIRONMENT]
      -
      + +
      +
      +
      ruby my_app.rb -e [ENVIRONMENT]
      +
      +
      +
      +

      현재 설정된 환경이 무엇인지 검사하기 위해 사전 정의된 development?, test?production? 메서드를 사용할 수 있다.

      @@ -1661,46 +2282,88 @@

      찾을 수 없음(Not Found)

      Sinatra::NotFound 예외가 발생하거나 또는 응답의 상태 코드가 404라면, not_found 핸들러가 호출된다:

      -
      not_found do
      -  '아무 곳에도 찾을 수 없습니다.'
      -end
      -
      + +
      +
      +
      not_found do
      +  '아무 곳에도 찾을 수 없습니다.'
      +end
      +
      +
      +
      +

      오류(Error)

      error 핸들러는 라우터 또는 필터에서 뭐든 오류가 발생할 경우에 호출된다. 예외 객체는 Rack 변수 sinatra.error로부터 얻을 수 있다:

      -
      error do
      -  '고약한 오류가 발생했군요 - ' + env['sinatra.error'].name
      -end
      -
      + +
      +
      +
      error do
      +  '고약한 오류가 발생했군요 - ' + env['sinatra.error'].name
      +end
      +
      +
      +
      +

      사용자 정의 오류:

      -
      error MyCustomError do
      -  '무슨 일이 생겼나면요...' + env['sinatra.error'].message
      -end
      -
      + +
      +
      +
      error MyCustomError do
      +  '무슨 일이 생겼나면요...' + env['sinatra.error'].message
      +end
      +
      +
      +
      +

      그런 다음, 이 오류가 발생하면:

      -
      get '/' do
      -  raise MyCustomError, '안좋은 일'
      -end
      -
      + +
      +
      +
      get '/' do
      +  raise MyCustomError, '안좋은 일'
      +end
      +
      +
      +
      +

      다음을 얻는다:

      -
      무슨 일이 생겼냐면요... 안좋은 
      -
      + +
      +
      +
      무슨 일이 생겼냐면요... 안좋은 일
      +
      +
      +
      +

      또는, 상태 코드에 대해 오류 핸들러를 설치할 수 있다:

      -
      error 403 do
      -  '액세스가 금지됨'
      -end
      -
      -get '/secret' do
      -  403
      -end
      -
      + +
      +
      +
      error 403 do
      +  '액세스가 금지됨'
      +end
      +
      +get '/secret' do
      +  403
      +end
      +
      +
      +
      +

      Or a range:

      -
      error 400..510 do
      -  '어이쿠'
      -end
      -
      + +
      +
      +
      error 400..510 do
      +  '어이쿠'
      +end
      +
      +
      +
      +

      Sinatra는 개발 환경에서 동작할 경우에 특별한 not_founderror 핸들러를 설치한다.

      @@ -1710,28 +2373,40 @@

      Rack 미들웨어(Rack Middleware)

      Sinatra는 Rack 위에서 동작하며, Rack은 루비 웹 프레임워크를 위한 최소한의 표준 인터페이스이다. Rack이 애플리케이션 개발자들에게 제공하는 가장 흥미로운 기능 중 하나가 바로 -"미들웨어(middleware)"에 대한 지원이며, 여기서 미들웨어란 서버와 여러분의 애플리케이션 사이에 +“미들웨어(middleware)”에 대한 지원이며, 여기서 미들웨어란 서버와 여러분의 애플리케이션 사이에 위치하면서 HTTP 요청/응답을 모니터링하거나/또는 조작함으로써 다양한 유형의 공통 기능을 제공하는 컴포넌트(component)다.

      Sinatra는 톱레벨의 use 메서드를 사용하여 Rack 미들웨어의 파이프라인을 만드는 일을 식은 죽 먹기로 만든다:

      -
      require 'sinatra'
      -require 'my_custom_middleware'
       
      -use Rack::Lint
      -use MyCustomMiddleware
      +
      +
      +
      require 'sinatra'
      +require 'my_custom_middleware'
      +
      +use Rack::Lint
      +use MyCustomMiddleware
      +
      +get '/hello' do
      +  'Hello World'
      +end
      +
      +
      +
      -get '/hello' do - 'Hello World' -end -

      use의 의미는 Rack::Builder DSL (rackup 파일에서 가장 많이 사용된다)에서 정의한 것들과 동일하다. 예를 들어, use 메서드는 블록 뿐 아니라 여러 개의/가변적인 인자도 받는다:

      -
      use Rack::Auth::Basic do |username, password|
      -  username == 'admin' && password == 'secret'
      -end
      -
      + +
      +
      +
      use Rack::Auth::Basic do |username, password|
      +  username == 'admin' && password == 'secret'
      +end
      +
      +
      +
      +

      Rack은 로깅, 디버깅, URL 라우팅, 인증, 그리고 세센 핸들링을 위한 다양한 표준 미들웨어로 분산되어 있다. Sinatra는 설정에 기반하여 이들 컴포넌트들 중 많은 것들을 자동으로 사용하며, 따라서 여러분은 일반적으로는 use를 명시적으로 사용할 필요가 없을 것이다.

      @@ -1748,33 +2423,39 @@

      테스팅(Testing)

      Sinatra 테스트는 Rack 기반 어떠한 테스팅 라이브러리 또는 프레임워크를 사용하여도 작성할 수 있다. Rack::Test를 권장한다:

      -
      require 'my_sinatra_app'
      -require 'test/unit'
      -require 'rack/test'
      -
      -class MyAppTest < Test::Unit::TestCase
      -  include Rack::Test::Methods
      -
      -  def app
      -    Sinatra::Application
      -  end
      -
      -  def test_my_default
      -    get '/'
      -    assert_equal 'Hello World!', last_response.body
      -  end
      -
      -  def test_with_params
      -    get '/meet', :name => 'Frank'
      -    assert_equal 'Hello Frank!', last_response.body
      -  end
      -
      -  def test_with_rack_env
      -    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      -    assert_equal "You're using Songbird!", last_response.body
      -  end
      -end
      -
      + +
      +
      +
      require 'my_sinatra_app'
      +require 'test/unit'
      +require 'rack/test'
      +
      +class MyAppTest < Test::Unit::TestCase
      +  include Rack::Test::Methods
      +  
      +  def app
      +    Sinatra::Application
      +  end
      +  
      +  def test_my_default
      +    get '/'
      +    assert_equal 'Hello World!', last_response.body
      +  end
      +  
      +  def test_with_params
      +    get '/meet', :name => 'Frank'
      +    assert_equal 'Hello Frank!', last_response.body
      +  end
      +  
      +  def test_with_rack_env
      +    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      +    assert_equal "You're using Songbird!", last_response.body
      +  end
      +end
      +
      +
      +
      +

      Sinatra::Base - 미들웨어(Middleware), 라이브러리(Libraries), 그리고 모듈 앱(Modular Apps)

      @@ -1784,24 +2465,30 @@

      Sinatra::Base - 미들웨어(Middleware), 라이브러리(Libraries), 그리 톱레벨은 마이크로 앱 스타일의 설정을 가정한다(즉, 하나의 단일 애플리케이션 파일과 ./public./views 디렉터리, 로깅, 예외 상세 페이지 등등). 이게 바로 Sinatra::Base가 필요한 부분이다:

      -
      require 'sinatra/base'
       
      -class MyApp < Sinatra::Base
      -  set :sessions, true
      -  set :foo, 'bar'
      +
      +
      +
      require 'sinatra/base'
      +
      +class MyApp < Sinatra::Base
      +  set :sessions, true
      +  set :foo, 'bar'
      +  
      +  get '/' do
      +    'Hello world!'
      +  end
      +end
      +
      +
      +
      - get '/' do - 'Hello world!' - end -end -

      Sinatra::Base 서브클래스에서 사용가능한 메서드들은 톱레벨 DSL로 접근 가능한 것들과 동일하다. 대부분의 톱레벨 앱들이 다음 두 가지만 수정하면 Sinatra::Base 컴포넌트로 변환 가능하다:

      • 파일은 sinatra가 아닌 sinatra/base를 require해야 하며, 그렇지 않으면 모든 Sinatra의 DSL 메서드들이 메인 네임스페이스에 불러지게 된다.
      • -
      • 앱의 라우터, 예외 핸들러, 필터, 그리고 옵션들을 Sinatra::Base의 서브클래스에 둘 것.
      • +
      • 앱의 라우터, 예외 핸들러, 필터, 그리고 옵션들을 Sinatra::Base의 서브클래스에 둘 것.

      Sinatra::Base는 빈서판(blank slate)이다. 빌트인 서버를 비롯한 대부분의 옵션들이 기본값으로 꺼져 있다. @@ -1864,42 +2551,78 @@

      모듈 애플리케이션(Modular Application) 제공하기

      모듈 앱을 시작하는 두 가지 일반적인 옵션이 있는데, 공격적으로 run!으로 시작하거나:

      -
      # my_app.rb
      -require 'sinatra/base'
       
      -class MyApp < Sinatra::Base
      -  # ... 여기에 앱 코드가 온다 ...
      +
      +
      +
      # my_app.rb
      +require 'sinatra/base'
      +
      +class MyApp < Sinatra::Base
      +  # ... 여기에 앱 코드가 온다 ...
      +
      +  # 루비 파일이 직접 실행될 경우에 서버를 시작
      +  run! if app_file == $0
      +end
      +
      +
      +
      - # 루비 파일이 직접 실행될 경우에 서버를 시작 - run! if app_file == $0 -end -

      다음과 같이 시작:

      -
      ruby my_app.rb
      -
      + +
      +
      +
      ruby my_app.rb
      +
      +
      +
      +

      또는 config.ru와 함께 사용하며, 이 경우는 어떠한 Rack 핸들러라도 사용할 수 있다:

      -
      # config.ru
      -require './my_app'
      -run MyApp
      -
      + +
      +
      +
      # config.ru
      +require './my_app'
      +run MyApp
      +
      +
      +
      +

      실행:

      -
      rackup -p 4567
      -
      + +
      +
      +
      rackup -p 4567
      +
      +
      +
      +

      config.ru로 전통적 방식의 애플리케이션 사용하기

      앱 파일을 다음과 같이 작성하고:

      -
      # app.rb
      -require 'sinatra'
       
      -get '/' do
      -  'Hello world!'
      -end
      -
      +
      +
      +
      # app.rb
      +require 'sinatra'
      +
      +get '/' do
      +  'Hello world!'
      +end
      +
      +
      +
      +

      대응하는 config.ru는 다음과 같이 작성:

      -
      require './app'
      -run Sinatra::Application
      -
      + +
      +
      +
      require './app'
      +run Sinatra::Application
      +
      +
      +
      +

      언제 config.ru를 사용할까?

      @@ -1907,9 +2630,9 @@

      언제 config.ru를 사용할까?

      다음은 config.ru를 사용하게 될 징후들이다:

        -
      • 다른 Rack 핸들러(Passenger, Unicorn, Heroku, ...)로 배포하고자 할 때.
      • -
      • 하나 이상의 Sinatra::Base 서브클래스를 사용하고자 할 때.
      • -
      • Sinatra를 최종점(endpoint)이 아니라, 오로지 미들웨어로만 사용하고자 할 때.
      • +
      • 다른 Rack 핸들러(Passenger, Unicorn, Heroku, …)로 배포하고자 할 때.
      • +
      • 하나 이상의 Sinatra::Base 서브클래스를 사용하고자 할 때.
      • +
      • Sinatra를 최종점(endpoint)이 아니라, 오로지 미들웨어로만 사용하고자 할 때.

      모듈 방식으로 전환했다는 이유만으로 config.ru로 전환할 필요는 없으며, 또한 config.ru를 사용한다고 해서 모듈 방식을 사용해야 하는 것도 아니다.

      @@ -1920,73 +2643,97 @@

      Sinatra를 미들웨어로 사용하기

      Sinatra에서 다른 Rack 미들웨어를 사용할 수 있을 뿐 아니라, 모든 Sinatra 애플리케이션은 순차로 어떠한 Rack 종착점 앞에 미들웨어로 추가될 수 있다. 이 종착점은 다른 Sinatra 애플리케이션이 될 수도 있고, -또는 Rack 기반의 어떠한 애플리케이션(Rails/Ramaze/Camping/...)이라도 가능하다:

      -
      require 'sinatra/base'
      -
      -class LoginScreen < Sinatra::Base
      -  enable :sessions
      -
      -  get('/login') { haml :login }
      -
      -  post('/login') do
      -if params[:name] == 'admin' && params[:password] == 'admin'
      -  session['user_name'] = params[:name]
      -else
      -  redirect '/login'
      -end
      -  end
      -end
      -
      -class MyApp < Sinatra::Base
      -  # 미들웨어는 사전 필터보다 앞서 실행됨
      -  use LoginScreen
      -
      -  before do
      -unless session['user_name']
      -  halt "접근 거부됨, <a href='/login'>로그인</a> 하세요."
      -end
      -  end
      -
      -  get('/') { "Hello #{session['user_name']}." }
      -end
      -
      +또는 Rack 기반의 어떠한 애플리케이션(Rails/Ramaze/Camping/…)이라도 가능하다:

      + +
      +
      +
      require 'sinatra/base'
      +
      +class LoginScreen < Sinatra::Base
      +  enable :sessions
      +  
      +  get('/login') { haml :login }
      +  
      +  post('/login') do
      +if params[:name] == 'admin' && params[:password] == 'admin'
      +  session['user_name'] = params[:name]
      +else
      +  redirect '/login'
      +end
      +  end
      +end
      +
      +class MyApp < Sinatra::Base
      +  # 미들웨어는 사전 필터보다 앞서 실행됨
      +  use LoginScreen
      +  
      +  before do
      +unless session['user_name']
      +  halt "접근 거부됨, <a href='/login'>로그인</a> 하세요."
      +end
      +  end
      +  
      +  get('/') { "Hello #{session['user_name']}." }
      +end
      +
      +
      +
      +

      동적인 애플리케이션 생성(Dynamic Application Creation)

      경우에 따라선 어떤 상수에 할당하지 않고 런타임에서 새 애플리케이션들을 생성하고 싶을 수도 있을 것인데, 이 때는 Sinatra.new를 쓰면 된다:

      -
      require 'sinatra/base'
      -my_app = Sinatra.new { get('/') { "hi" } }
      -my_app.run!
      -
      + +
      +
      +
      require 'sinatra/base'
      +my_app = Sinatra.new { get('/') { "hi" } }
      +my_app.run!
      +
      +
      +
      +

      이것은 선택적 인자로 상속할 애플리케이션을 받는다:

      -
      # config.ru
      -require 'sinatra/base'
      -
      -controller = Sinatra.new do
      -  enable :logging
      -  helpers MyHelpers
      -end
      -
      -map('/a') do
      -  run Sinatra.new(controller) { get('/') { 'a' } }
      -end
      -
      -map('/b') do
      -  run Sinatra.new(controller) { get('/') { 'b' } }
      -end
      -
      + +
      +
      +
      # config.ru
      +require 'sinatra/base'
      +
      +controller = Sinatra.new do
      +  enable :logging
      +  helpers MyHelpers
      +end
      +
      +map('/a') do
      +  run Sinatra.new(controller) { get('/') { 'a' } }
      +end
      +
      +map('/b') do
      +  run Sinatra.new(controller) { get('/') { 'b' } }
      +end
      +
      +
      +
      +

      이것은 Sintra 익스텐션을 테스팅하거나 또는 여러분의 라이브러리에서 Sinatra를 사용할 경우에 특히 유용하다.

      또한 이 방법은 Sinatra를 미들웨어로 사용하는 것을 아주 쉽게 만들어 준다:

      -
      require 'sinatra/base'
       
      -use Sinatra do
      -  get('/') { ... }
      -end
      +
      +
      +
      require 'sinatra/base'
      +
      +use Sinatra do
      +  get('/') { ... }
      +end
      +
      +run RailsProject::Application
      +
      +
      +
      -run RailsProject::Application -

      범위(Scopes)와 바인딩(Binding)

      @@ -2003,33 +2750,39 @@

      애플리케이션/클래스 범위

      애플리케이션 클래스는 오직 하나이기 때문이다.

      set으로 생성한 옵션들은 클래스 레벨의 메서드들이다:

      -
      class MyApp < Sinatra::Base
      -  # 이봐요, 저는 애플리케이션 범위에 있다구요!
      -  set :foo, 42
      -  foo # => 42
      -
      -  get '/foo' do
      -# 저기요, 전 이제 더 이상 애플리케이션 범위 속에 있지 않아요!
      -  end
      -end
      -
      + +
      +
      +
      class MyApp < Sinatra::Base
      +  # 이봐요, 저는 애플리케이션 범위에 있다구요!
      +  set :foo, 42
      +  foo # => 42
      +
      +  get '/foo' do
      +# 저기요, 전 이제 더 이상 애플리케이션 범위 속에 있지 않아요!
      +  end
      +end
      +
      +
      +
      +

      다음 속에 있을 때 애플리케이션 범위가 된다:

      • 애플리케이션 클래스 본문
      • -
      • 확장으로 정의된 메서드
      • -
      • +
      • 확장으로 정의된 메서드
      • +
      • helpers로 전달된 블록
      • -
      • +
      • set의 값으로 사용된 Procs/blocks
      • -
      • +
      • Sinatra.new로 전달된 블록

      범위 객체 (클래스)는 다음과 같이 접근할 수 있다:

      • configure 블록으로 전달된 객체를 통해(configure { |c| ... })
      • -
      • 요청 범위 내에서 settings +
      • 요청 범위 내에서 settings
      @@ -2039,28 +2792,34 @@

      요청/인스턴스 범위

      이 범위 내에서 여러분은 requestsession 객체에 접근하거나 erbhaml 같은 렌더링 메서드를 호출할 수 있다. 요청 범위 내에서 애플리케이션 범위는 settings 헬퍼를 통해 접근 가능하다:

      -
      class MyApp < Sinatra::Base
      -  # 이봐요, 전 애플리케이션 범위에 있다구요!
      -  get '/define_route/:name' do
      -# '/define_route/:name'의 요청 범위
      -@value = 42
      -
      -settings.get("/#{params[:name]}") do
      -  # "/#{params[:name]}"의 요청 범위
      -  @value # => nil (동일한 요청이 아님)
      -end
      -
      -"라우터가 정의됨!"
      -  end
      -end
      -
      + +
      +
      +
      class MyApp < Sinatra::Base
      +  # 이봐요, 전 애플리케이션 범위에 있다구요!
      +  get '/define_route/:name' do
      +# '/define_route/:name'의 요청 범위
      +@value = 42
      +  
      +settings.get("/#{params[:name]}") do
      +  # "/#{params[:name]}"의 요청 범위
      +  @value # => nil (동일한 요청이 아님)
      +end
      +  
      +"라우터가 정의됨!"
      +  end
      +end
      +
      +
      +
      +

      다음 속에 있을 때 요청 범위 바인딩이 된다:

      • get/head/post/put/delete/options 블록
      • -
      • before/after 필터
      • -
      • 헬퍼(helper) 메서드
      • -
      • 템플릿/뷰
      • +
      • before/after 필터
      • +
      • 헬퍼(helper) 메서드
      • +
      • 템플릿/뷰

      위임 범위(Delegation Scope)

      @@ -2075,7 +2834,7 @@

      위임 범위(Delegation Scope)

      • 톱레벨 바인딩, require "sinatra"를 한 경우
      • -
      • +
      • Sinatra::Delegator 믹스인으로 확장된 객체

      직접 코드를 살펴보길 바란다: @@ -2086,16 +2845,24 @@

      위임 범위(Delegation Scope)

      명령행(Command Line)

      Sinatra 애플리케이션은 직접 실행할 수 있다:

      -
      ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
      -
      + +
      +
      +
      ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
      +
      +
      +
      +

      옵션들:

      -
      -h # 도움말
      +
      +
      -h # 도움말
       -p # 포트 설정 (기본값은 4567)
       -o # 호스트 설정 (기본값은 0.0.0.0)
       -e # 환경 설정 (기본값은 development)
       -s # rack 서버/핸들러 지정 (기본값은 thin)
       -x # mutex 잠금 켜기 (기본값은 off)
      -
      +
      +

      요구사항(Requirement)

      @@ -2145,9 +2912,9 @@

      요구사항(Requirement)

      • JRuby와 Rubinius 예전 버전
      • -
      • Ruby Enterprise Edition
      • -
      • MacRuby, Maglev, IronRuby
      • -
      • Ruby 1.9.0 및 1.9.1 (그러나 이 버전들은 사용하지 말 것을 권함)
      • +
      • Ruby Enterprise Edition
      • +
      • MacRuby, Maglev, IronRuby
      • +
      • Ruby 1.9.0 및 1.9.1 (그러나 이 버전들은 사용하지 말 것을 권함)

      공식적으로 지원하지 않는다는 것의 의미는 무언가가 그쪽에서만 잘못되고 지원되는 플랫폼에서는 그러지 않을 경우, 우리의 문제가 아니라 그쪽의 문제로 간주한다는 뜻이다.

      @@ -2168,8 +2935,14 @@

      최신(The Bleeding Edge)

      여러분 애플리케이션을 마스터 브랜치에 맞춰 실행하면 되지만, 덜 안정적일 것임에 분명하다.

      또한 우리는 가끔 사전배포(prerelease) 젬을 푸시하기 때문에, 다음과 같이 할 수 있다

      -
      gem install sinatra --pre
      -
      + +
      +
      +
      gem install sinatra --pre
      +
      +
      +
      +

      최신 기능들을 얻기 위해선

      @@ -2179,48 +2952,90 @@

      Bundler를 사용하여

      Bundler를 사용할 것을 권장한다.

      우선, 아직 설치하지 않았다면 bundler를 설치한다:

      -
      gem install bundler
      -
      + +
      +
      +
      gem install bundler
      +
      +
      +
      +

      그런 다음, 프로젝트 디렉터리에서, Gemfile을 하나 만든다:

      -
      source :rubygems
      -gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
       
      -# 다른 의존관계들
      -gem 'haml'# 예를 들어, haml을 사용한다면
      -gem 'activerecord', '~> 3.0'  # 아마도 ActiveRecord 3.x도 필요할 것
      -
      +
      +
      +
      source :rubygems
      +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
      +
      +# 다른 의존관계들
      +gem 'haml'# 예를 들어, haml을 사용한다면
      +gem 'activerecord', '~> 3.0'  # 아마도 ActiveRecord 3.x도 필요할 것
      +
      +
      +
      +

      이 속에 애플리케이션의 모든 의존관계를 나열해야 함에 유의하자. 그렇지만, Sinatra가 직접적인 의존관계에 있는 것들 (Rack과 Tilt)은 Bundler가 자동으로 추출하여 추가할 것이다.

      이제 여러분은 다음과 같이 앱을 실행할 수 있다:

      -
      bundle exec ruby myapp.rb
      -
      + +
      +
      +
      bundle exec ruby myapp.rb
      +
      +
      +
      +

      직접 하기(Roll Your Own)

      로컬 클론(clone)을 생성한 다음 $LOAD_PATHsinatra/lib 디렉터리를 주고 여러분 앱을 실행한다:

      -
      cd myapp
      -git clone git://github.com/sinatra/sinatra.git
      -ruby -Isinatra/lib myapp.rb
      -
      + +
      +
      +
      cd myapp
      +git clone git://github.com/sinatra/sinatra.git
      +ruby -Isinatra/lib myapp.rb
      +
      +
      +
      +

      이후에 Sinatra 소스를 업데이트하려면:

      -
      cd myapp/sinatra
      -git pull
      -
      + +
      +
      +
      cd myapp/sinatra
      +git pull
      +
      +
      +
      +

      전역으로 설치(Install Globally)

      젬을 직접 빌드할 수 있다:

      -
      git clone git://github.com/sinatra/sinatra.git
      -cd sinatra
      -rake sinatra.gemspec
      -rake install
      -
      + +
      +
      +
      git clone git://github.com/sinatra/sinatra.git
      +cd sinatra
      +rake sinatra.gemspec
      +rake install
      +
      +
      +
      +

      만약 젬을 루트로 설치한다면, 마지막 단계는 다음과 같이 해야 한다

      -
      sudo rake install
      -
      + +
      +
      +
      sudo rake install
      +
      +
      +
      +

      버저닝(Versioning)

      @@ -2233,18 +3048,19 @@

      더 읽을 거리(Further Reading)

      diff --git a/_includes/README.pt-br.html b/_includes/README.pt-br.html index ec722440..8a79e1a3 100644 --- a/_includes/README.pt-br.html +++ b/_includes/README.pt-br.html @@ -48,17 +48,29 @@

      Sinatra é uma DSL para criar aplicações web em Ruby com o mínimo de esforço e rapidez:

      -
      # minhaapp.rb
      -require 'sinatra'
       
      -get '/' do
      -  'Olá Mundo!'
      -end
      -
      +
      +
      +
      # minhaapp.rb
      +require 'sinatra'
      +
      +get '/' do
      +  'Olá Mundo!'
      +end
      +
      +
      +
      +

      Instale a gem e execute como:

      -
      gem install sinatra
      +
      +
      +
      +
      gem install sinatra
       ruby minhaapp.rb
      -
      +
      + + +

      Acesse em: localhost:4567

      Recomendamos a execução de gem install thin. Caso esteja disponível, o @@ -69,79 +81,127 @@

      Rotas

      No Sinatra, uma rota é um método HTTP emparelhado com um padrão de URL. Cada rota possui um bloco de execução:

      -
      get '/' do
      -  .. mostrando alguma coisa ..
      -end
      -
      -post '/' do
      -  .. criando alguma coisa ..
      -end
      -
      -put '/' do
      -  .. atualizando alguma coisa ..
      -end
      -
      -patch '/' do
      -  .. modificando alguma coisa ..
      -end
      -
      -delete '/' do
      -  .. removendo alguma coisa ..
      -end
      -
      -options '/' do
      -  .. estabelecendo alguma coisa ..
      -end
      -
      + +
      +
      +
      get '/' do
      +  .. mostrando alguma coisa ..
      +end
      +
      +post '/' do
      +  .. criando alguma coisa ..
      +end
      +
      +put '/' do
      +  .. atualizando alguma coisa ..
      +end
      +
      +patch '/' do
      +  .. modificando alguma coisa ..
      +end
      +
      +delete '/' do
      +  .. removendo alguma coisa ..
      +end
      +
      +options '/' do
      +  .. estabelecendo alguma coisa ..
      +end
      +
      +
      +
      +

      As rotas são interpretadas na ordem em que são definidos. A primeira rota encontrada responde ao pedido.

      Padrões de rota podem conter parâmetros nomeados, acessível através do hash params:

      -
      get '/ola/:nome' do
      -  # corresponde a "GET /ola/foo" e "GET /ola/bar"
      -  # params[:nome] é 'foo' ou 'bar'
      -  "Olá #{params[:nome]}!"
      -end
      -
      + +
      +
      +
      get '/ola/:nome' do
      +  # corresponde a "GET /ola/foo" e "GET /ola/bar"
      +  # params[:nome] é 'foo' ou 'bar'
      +  "Olá #{params[:nome]}!"
      +end
      +
      +
      +
      +

      Você também pode acessar parâmetros nomeados através dos parâmetros de um bloco:

      -
      get '/ola/:nome' do |n|
      -  "Olá #{n}!"
      -end
      -
      + +
      +
      +
      get '/ola/:nome' do |n|
      +  "Olá #{n}!"
      +end
      +
      +
      +
      +

      Padrões de rota também podem conter parâmetros splat (wildcard), acessível através do array params[: splat]:

      -
      get '/diga/*/para/*' do
      -  # corresponde a /diga/ola/para/mundo
      -  params[:splat] # => ["ola", "mundo"]
      -end
      -
      -get '/download/*.*' do
      -  # corresponde a /download/pasta/do/arquivo.xml
      -  params[:splat] # => ["pasta/do/arquivo", "xml"]
      -end
      -
      + +
      +
      +
      get '/diga/*/para/*' do
      +  # corresponde a /diga/ola/para/mundo
      +  params[:splat] # => ["ola", "mundo"]
      +end
      +
      +get '/download/*.*' do
      +  # corresponde a /download/pasta/do/arquivo.xml
      +  params[:splat] # => ["pasta/do/arquivo", "xml"]
      +end
      +
      +
      +
      +

      Ou com parâmetros de um bloco:

      -
      get '/download/*.*' do |pasta, ext|
      -  [pasta, ext] # => ["pasta/do/arquivo", "xml"]
      -end
      -
      + +
      +
      +
      get '/download/*.*' do |pasta, ext|
      +  [pasta, ext] # => ["pasta/do/arquivo", "xml"]
      +end
      +
      +
      +
      +

      Rotas podem corresponder com expressões regulares:

      -
      get %r{/ola/([\w]+)} do
      -  "Olá, #{params[:captures].first}!"
      -end
      -
      + +
      +
      +
      get %r{/ola/([\w]+)} do
      +  "Olá, #{params[:captures].first}!"
      +end
      +
      +
      +
      +

      Ou com parâmetros de um bloco:

      -
      get %r{/ola/([\w]+)} do |c|
      -  "Olá, #{c}!"
      -end
      -
      + +
      +
      +
      get %r{/ola/([\w]+)} do |c|
      +  "Olá, #{c}!"
      +end
      +
      +
      +
      +

      Padrões de rota podem contar com parâmetros opcionais:

      -
      get '/posts.?:formato?' do
      -  # corresponde a "GET /posts" e qualquer extensão "GET /posts.json", "GET /posts.xml", etc.
      -end
      -
      + +
      +
      +
      get '/posts.?:formato?' do
      +  # corresponde a "GET /posts" e qualquer extensão "GET /posts.json", "GET /posts.xml", etc.
      +end
      +
      +
      +
      +

      A propósito, a menos que você desative a proteção contra ataques (veja abaixo), o caminho solicitado pode ser alterado antes de concluir a comparação com as suas rotas.

      @@ -150,55 +210,79 @@

      Rotas

      Condições

      Rotas podem incluir uma variedade de condições, tal como o user agent:

      -
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      -  "Você está usando o Songbird versão #{params[:agent][0]}"
      -end
      -
      -get '/foo' do
      -  # Correspondente a navegadores que não sejam Songbird
      -end
      -
      + +
      +
      +
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      +  "Você está usando o Songbird versão #{params[:agent][0]}"
      +end
      +
      +get '/foo' do
      +  # Correspondente a navegadores que não sejam Songbird
      +end
      +
      +
      +
      +

      Outras condições disponíveis são host_name e provides:

      -
      get '/', :host_name => /^admin\./ do
      -  "Área administrativa. Acesso negado!"
      -end
      -
      -get '/', :provides => 'html' do
      -  haml :index
      -end
      -
      -get '/', :provides => ['rss', 'atom', 'xml'] do
      -  builder :feed
      -end
      -
      + +
      +
      +
      get '/', :host_name => /^admin\./ do
      +  "Área administrativa. Acesso negado!"
      +end
      +
      +get '/', :provides => 'html' do
      +  haml :index
      +end
      +
      +get '/', :provides => ['rss', 'atom', 'xml'] do
      +  builder :feed
      +end
      +
      +
      +
      +

      Você pode facilmente definir suas próprias condições:

      -
      set(:probabilidade) { |valor| condition { rand <= valor } }
       
      -get '/ganha_um_carro', :probabilidade => 0.1 do
      -  "Você ganhou!"
      -end
      +
      +
      +
      set(:probabilidade) { |valor| condition { rand <= valor } }
      +
      +get '/ganha_um_carro', :probabilidade => 0.1 do
      +  "Você ganhou!"
      +end
      +
      +get '/ganha_um_carro' do
      +  "Sinto muito, você perdeu."
      +end
      +
      +
      +
      -get '/ganha_um_carro' do - "Sinto muito, você perdeu." -end -

      Use splat, para uma condição que levam vários valores:

      -
      set(:auth) do |*roles|   # <- observe o splat aqui
      -  condition do
      -    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      -      redirect "/login/", 303
      -    end
      -  end
      -end
      -
      -get "/minha/conta/", :auth => [:usuario, :administrador] do
      -  "Detalhes da sua conta"
      -end
      -
      -get "/apenas/administrador/", :auth => :administrador do
      -  "Apenas administradores são permitidos aqui!"
      -end
      -
      + +
      +
      +
      set(:auth) do |*roles|   # <- observe o splat aqui
      +  condition do
      +    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      +      redirect "/login/", 303
      +    end
      +  end
      +end
      +
      +get "/minha/conta/", :auth => [:usuario, :administrador] do
      +  "Detalhes da sua conta"
      +end
      +
      +get "/apenas/administrador/", :auth => :administrador do
      +  "Apenas administradores são permitidos aqui!"
      +end
      +
      +
      +
      +

      Retorno de valores

      @@ -212,14 +296,20 @@

      Retorno de valores

      retornar um código de status HTTP.

      Dessa forma, podemos implementar facilmente um exemplo de streaming:

      -
      class Stream
      -  def each
      -    100.times { |i| yield "#{i}\n" }
      -  end
      -end
      -
      -get('/') { Stream.new }
      -
      + +
      +
      +
      class Stream
      +  def each
      +    100.times { |i| yield "#{i}\n" }
      +  end
      +end
      +
      +get('/') { Stream.new }
      +
      +
      +
      +

      Você também pode usar o método auxiliar stream (descrito abaixo) para incorporar a lógica de streaming na rota.

      @@ -230,47 +320,71 @@

      Custom Route Matchers

      embutido para uso de padrões de String e expressões regulares como validadores de rota. No entanto, ele não pára por aí. Você pode facilmente definir os seus próprios validadores:

      -
      class AllButPattern
      -  Match = Struct.new(:captures)
      -
      -  def initialize(except)
      -    @except   = except
      -    @captures = Match.new([])
      -  end
      -
      -  def match(str)
      -    @captures unless @except === str
      -  end
      -end
      -
      -def all_but(pattern)
      -  AllButPattern.new(pattern)
      -end
      -
      -get all_but("/index") do
      -  # ...
      -end
      -
      + +
      +
      +
      class AllButPattern
      +  Match = Struct.new(:captures)
      +
      +  def initialize(except)
      +    @except   = except
      +    @captures = Match.new([])
      +  end
      +
      +  def match(str)
      +    @captures unless @except === str
      +  end
      +end
      +
      +def all_but(pattern)
      +  AllButPattern.new(pattern)
      +end
      +
      +get all_but("/index") do
      +  # ...
      +end
      +
      +
      +
      +

      Note que o exemplo acima pode ser robusto e complicado em excesso. Pode também ser implementado como:

      -
      get // do
      -  pass if request.path_info == "/index"
      -  # ...
      -end
      -
      + +
      +
      +
      get // do
      +  pass if request.path_info == "/index"
      +  # ...
      +end
      +
      +
      +
      +

      Ou, usando algo mais denso à frente:

      -
      get %r{^(?!/index$)} do
      -  # ...
      -end
      -
      + +
      +
      +
      get %r{^(?!/index$)} do
      +  # ...
      +end
      +
      +
      +
      +

      Arquivos estáticos

      Arquivos estáticos são disponibilizados a partir do diretório ./public. Você pode especificar um local diferente pela opção :public_folder

      -
      set :public_folder, File.dirname(__FILE__) + '/estatico'
      -
      + +
      +
      +
      set :public_folder, File.dirname(__FILE__) + '/estatico'
      +
      +
      +
      +

      Note que o nome do diretório público não é incluido na URL. Um arquivo ./public/css/style.css é disponibilizado como http://example.com/css/style.css.

      @@ -280,8 +394,14 @@

      Views / Templates

      Templates presumem-se estar localizados sob o diretório ./views. Para utilizar um diretório view diferente:

      -
      set :views, File.dirname(__FILE__) + '/modelo'
      -
      + +
      +
      +
      set :views, File.dirname(__FILE__) + '/modelo'
      +
      +
      +
      +

      Uma coisa importante a ser lembrada é que você sempre tem as referências dos templates como símbolos, mesmo se eles estiverem em um sub-diretório (nesse caso utilize :'subdir/template'). Métodos de renderização irão @@ -291,13 +411,19 @@

      Views / Templates

      Haml Templates

      A gem/biblioteca haml é necessária para renderizar templates HAML:

      -
      # Você precisa do 'require haml' em sua aplicação.
      -require 'haml'
       
      -get '/' do
      -  haml :index
      -end
      -
      +
      +
      +
      # Você precisa do 'require haml' em sua aplicação.
      +require 'haml'
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      +

      Renderiza ./views/index.haml.

      Opções @@ -305,62 +431,92 @@

      Haml Templates

      podem ser setadas globalmente através das configurações do sinatra, veja Opções e Configurações, e substitua em uma requisição individual.

      -
      set :haml, {:format => :html5 } # o formato padrão do Haml é :xhtml
       
      -get '/' do
      -  haml :index, :haml_options => {:format => :html4 } # substituido
      -end
      -
      +
      +
      +
      set :haml, {:format => :html5 } # o formato padrão do Haml é :xhtml
      +
      +get '/' do
      +  haml :index, :haml_options => {:format => :html4 } # substituido
      +end
      +
      +
      +
      +

      Erb Templates

      -
      # Você precisa do 'require erb' em sua aplicação
      -require 'erb'
       
      -get '/' do
      -  erb :index
      -end
      -
      +
      +
      +
      # Você precisa do 'require erb' em sua aplicação
      +require 'erb'
      +
      +get '/' do
      +  erb :index
      +end
      +
      +
      +
      +

      Renderiza ./views/index.erb

      Erubis

      A gem/biblioteca erubis é necessária para renderizar templates erubis:

      -
      # Você precisa do 'require erubis' em sua aplicação.
      -require 'erubis'
       
      -get '/' do
      -  erubis :index
      -end
      -
      +
      +
      +
      # Você precisa do 'require erubis' em sua aplicação.
      +require 'erubis'
      +
      +get '/' do
      +  erubis :index
      +end
      +
      +
      +
      +

      Renderiza ./views/index.erubis

      Builder Templates

      A gem/biblioteca builder é necessária para renderizar templates builder:

      -
      # Você precisa do 'require builder' em sua aplicação.
      -require 'builder'
      -
      -get '/' do
      -  content_type 'application/xml', :charset => 'utf-8'
      -  builder :index
      -end
      -
      + +
      +
      +
      # Você precisa do 'require builder' em sua aplicação.
      +require 'builder'
      +
      +get '/' do
      +  content_type 'application/xml', :charset => 'utf-8'
      +  builder :index
      +end
      +
      +
      +
      +

      Renderiza ./views/index.builder.

      Sass Templates

      A gem/biblioteca sass é necessária para renderizar templates sass:

      -
      # Você precisa do 'require haml' ou 'require sass' em sua aplicação.
      -require 'sass'
      -
      -get '/stylesheet.css' do
      -  content_type 'text/css', :charset => 'utf-8'
      -  sass :stylesheet
      -end
      -
      + +
      +
      +
      # Você precisa do 'require haml' ou 'require sass' em sua aplicação.
      +require 'sass'
      +
      +get '/stylesheet.css' do
      +  content_type 'text/css', :charset => 'utf-8'
      +  sass :stylesheet
      +end
      +
      +
      +
      +

      Renderiza ./views/stylesheet.sass.

      Opções @@ -368,33 +524,51 @@

      Sass Templates

      podem ser setadas globalmente através das configurações do sinatra, veja Opções e Configurações, e substitua em uma requisição individual.

      -
      set :sass, {:style => :compact } # o estilo padrão do Sass é :nested
       
      -get '/stylesheet.css' do
      -  content_type 'text/css', :charset => 'utf-8'
      -  sass :stylesheet, :style => :expanded # substituido
      -end
      -
      +
      +
      +
      set :sass, {:style => :compact } # o estilo padrão do Sass é :nested
      +
      +get '/stylesheet.css' do
      +  content_type 'text/css', :charset => 'utf-8'
      +  sass :stylesheet, :style => :expanded # substituido
      +end
      +
      +
      +
      +

      Less Templates

      A gem/biblioteca less é necessária para renderizar templates Less:

      -
      # Você precisa do 'require less' em sua aplicação.
      -require 'less'
      -
      -get '/stylesheet.css' do
      -  content_type 'text/css', :charset => 'utf-8'
      -  less :stylesheet
      -end
      -
      + +
      +
      +
      # Você precisa do 'require less' em sua aplicação.
      +require 'less'
      +
      +get '/stylesheet.css' do
      +  content_type 'text/css', :charset => 'utf-8'
      +  less :stylesheet
      +end
      +
      +
      +
      +

      Renderiza ./views/stylesheet.less.

      Inline Templates

      -
      get '/' do
      -  haml '%div.title Olá Mundo'
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml '%div.title Olá Mundo'
      +end
      +
      +
      +
      +

      Renderiza a string, em uma linha, no template.

      @@ -403,17 +577,29 @@

      Acessando Variáveis nos Templates

      Templates são avaliados dentro do mesmo contexto como manipuladores de rota. Variáveis de instância setadas em rotas manipuladas são diretamente acessadas por templates:

      -
      get '/:id' do
      -  @foo = Foo.find(params[:id])
      -  haml '%h1= @foo.nome'
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  @foo = Foo.find(params[:id])
      +  haml '%h1= @foo.nome'
      +end
      +
      +
      +
      +

      Ou, especifique um hash explícito para variáveis locais:

      -
      get '/:id' do
      -  foo = Foo.find(params[:id])
      -  haml '%h1= foo.nome', :locals => { :foo => foo }
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  foo = Foo.find(params[:id])
      +  haml '%h1= foo.nome', :locals => { :foo => foo }
      +end
      +
      +
      +
      +

      Isso é tipicamente utilizando quando renderizamos templates como partials dentro de outros templates.

      @@ -421,22 +607,28 @@

      Acessando Variáveis nos Templates

      Templates Inline

      Templates podem ser definidos no final do arquivo fonte(.rb):

      -
      require 'rubygems'
      -require 'sinatra'
       
      -get '/' do
      -  haml :index
      -end
      +
      +
      +
      require 'rubygems'
      +require 'sinatra'
      +
      +get '/' do
      +  haml :index
      +end
       
      -__END__
      +__END__
       
       @@ layout
       %html
         = yield
       
       @@ index
      -%div.title Olá Mundo!!!!!
      -
      +%div.title Olá Mundo!!!!! +
      + + +

      NOTA: Templates inline definidos no arquivo fonte são automaticamente carregados pelo sinatra. Digite `enable :inline_templates` se você tem templates inline no outro arquivo fonte.

      @@ -446,40 +638,58 @@

      Templates nomeados

      Templates também podem ser definidos utilizando o método top-level template:

      -
      template :layout do
      -  "%html\n  =yield\n"
      -end
      -
      -template :index do
      -  '%div.title Olá Mundo!'
      -end
      -
      -get '/' do
      -  haml :index
      -end
      -
      + +
      +
      +
      template :layout do
      +  "%html\n  =yield\n"
      +end
      +
      +template :index do
      +  '%div.title Olá Mundo!'
      +end
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      +

      Se existir um template com nome “layout”, ele será utilizado toda vez que um template for renderizado. Você pode desabilitar layouts passando :layout => false.

      -
      get '/' do
      -  haml :index, :layout => !request.xhr?
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml :index, :layout => !request.xhr?
      +end
      +
      +
      +
      +

      Helpers

      Use o método de alto nível helpers para definir métodos auxiliares para utilizar em manipuladores de rotas e modelos:

      -
      helpers do
      -  def bar(nome)
      -    "#{nome}bar"
      -  end
      -end
      -
      -get '/:nome' do
      -  bar(params[:nome])
      -end
      -
      + +
      +
      +
      helpers do
      +  def bar(nome)
      +    "#{nome}bar"
      +  end
      +end
      +
      +get '/:nome' do
      +  bar(params[:nome])
      +end
      +
      +
      +
      +

      Filtros

      @@ -487,66 +697,120 @@

      Filtros

      da requisição e pode modificar a requisição e a reposta. Variáveis de instância setadas nos filtros são acessadas através de rotas e templates:

      -
      before do
      -  @nota = 'Oi!'
      -  request.path_info = '/foo/bar/baz'
      -end
      -
      -get '/foo/*' do
      -  @nota #=> 'Oi!'
      -  params[:splat] #=> 'bar/baz'
      -end
      -
      + +
      +
      +
      before do
      +  @nota = 'Oi!'
      +  request.path_info = '/foo/bar/baz'
      +end
      +
      +get '/foo/*' do
      +  @nota #=> 'Oi!'
      +  params[:splat] #=> 'bar/baz'
      +end
      +
      +
      +
      +

      Filtros After são avaliados após cada requisição dentro do contexto da requisição e também podem modificar o pedido e a resposta. Variáveis de instância definidas nos filtros before e rotas são acessadas através dos filtros after:

      -
      after do
      -  puts response.status
      -end
      -
      + +
      +
      +
      after do
      +  puts response.status
      +end
      +
      +
      +
      +

      Filtros opcionalmente tem um padrão, fazendo com que sejam avaliados somente se o caminho do pedido coincidir com esse padrão:

      -
      before '/protected/*' do
      -  authenticate!
      -end
      -
      -after '/create/:slug' do |slug|
      -  session[:last_slug] = slug
      -end
      -
      + +
      +
      +
      before '/protected/*' do
      +  authenticate!
      +end
      +
      +after '/create/:slug' do |slug|
      +  session[:last_slug] = slug
      +end
      +
      +
      +
      +

      Halting

      Para parar imediatamente uma requisição com um filtro ou rota utilize:

      -
      halt
      -
      + +
      +
      +
      halt
      +
      +
      +
      +

      Você também pode especificar o status quando parar…

      -
      halt 410
      -
      + +
      +
      +
      halt 410
      +
      +
      +
      +

      Ou com corpo de texto…

      -
      halt 'isso será o corpo do texto'
      -
      + +
      +
      +
      halt 'isso será o corpo do texto'
      +
      +
      +
      +

      Ou também…

      -
      halt 401, 'vamos embora!'
      -
      + +
      +
      +
      halt 401, 'vamos embora!'
      +
      +
      +
      +

      Com cabeçalhos…

      -
      halt 402, {'Content-Type' => 'text/plain'}, 'revanche'
      -
      + +
      +
      +
      halt 402, {'Content-Type' => 'text/plain'}, 'revanche'
      +
      +
      +
      +

      Passing

      Uma rota pode processar aposta para a próxima rota correspondente usando pass:

      -
      get '/adivinhar/:quem' do
      -  pass unless params[:quem] == 'Frank'
      -  'Você me pegou!'
      -end
      -
      -get '/adivinhar/*' do
      -  'Você falhou!'
      -end
      -
      + +
      +
      +
      get '/adivinhar/:quem' do
      +  pass unless params[:quem] == 'Frank'
      +  'Você me pegou!'
      +end
      +
      +get '/adivinhar/*' do
      +  'Você falhou!'
      +end
      +
      +
      +
      +

      O bloqueio da rota é imediatamente encerrado e o controle continua com a próxima rota de parâmetro. Se o parâmetro da rota não for encontrado, um 404 é retornado.

      @@ -555,21 +819,39 @@

      Passing

      Configuração

      Rodando uma vez, na inicialização, em qualquer ambiente:

      -
      configure do
      -  ...
      -end
      -
      + +
      +
      +
      configure do
      +  ...
      +end
      +
      +
      +
      +

      Rodando somente quando o ambiente (RACK_ENV environment variável) é setado para :production:

      -
      configure :production do
      -  ...
      -end
      -
      + +
      +
      +
      configure :production do
      +  ...
      +end
      +
      +
      +
      +

      Rodando quando o ambiente é setado para :production ou :test:

      -
      configure :production, :test do
      -  ...
      -end
      -
      + +
      +
      +
      configure :production, :test do
      +  ...
      +end
      +
      +
      +
      +

      Tratamento de Erros

      @@ -582,48 +864,86 @@

      Não Encontrado

      Quando um Sinatra::NotFound exception é levantado, ou o código de status da reposta é 404, o not_found manipulador é invocado:

      -
      not_found do
      -  'Isto está longe de ser encontrado'
      -end
      -
      + +
      +
      +
      not_found do
      +  'Isto está longe de ser encontrado'
      +end
      +
      +
      +
      +

      Erro

      O manipulador error é invocado toda a vez que uma exceção é lançada a partir de um bloco de rota ou um filtro. O objeto da exceção pode ser obtido a partir da variável Rack sinatra.error:

      -
      error do
      -  'Desculpe, houve um erro desagradável - ' + env['sinatra.error'].name
      -end
      -
      + +
      +
      +
      error do
      +  'Desculpe, houve um erro desagradável - ' + env['sinatra.error'].name
      +end
      +
      +
      +
      +

      Erros customizados:

      -
      error MeuErroCustomizado do
      -  'Então que aconteceu foi...' + env['sinatra.error'].message
      -end
      -
      + +
      +
      +
      error MeuErroCustomizado do
      +  'Então que aconteceu foi...' + env['sinatra.error'].message
      +end
      +
      +
      +
      +

      Então, se isso acontecer:

      -
      get '/' do
      -  raise MeuErroCustomizado, 'alguma coisa ruim'
      -end
      -
      + +
      +
      +
      get '/' do
      +  raise MeuErroCustomizado, 'alguma coisa ruim'
      +end
      +
      +
      +
      +

      Você receberá isso:

      -
      Então que aconteceu foi... alguma coisa ruim
      -
      + +
      Então que aconteceu foi... alguma coisa ruim
      +
      +

      Alternativamente, você pode instalar manipulador de erro para um código de status:

      -
      error 403 do
      -  'Accesso negado'
      -end
      -
      -get '/secreto' do
      -  403
      -end
      -
      + +
      +
      +
      error 403 do
      +  'Accesso negado'
      +end
      +
      +get '/secreto' do
      +  403
      +end
      +
      +
      +
      +

      Ou um range:

      -
      error 400..510 do
      -  'Boom'
      -end
      -
      + +
      +
      +
      error 400..510 do
      +  'Boom'
      +end
      +
      +
      +
      +

      O Sinatra instala os manipuladores especiais not_found e error quando roda sobre o ambiente de desenvolvimento.

      @@ -633,11 +953,23 @@

      Mime Types

      Quando utilizamos send_file ou arquivos estáticos você pode ter mime types Sinatra não entendidos. Use mime_type para registrar eles por extensão de arquivos:

      -
      mime_type :foo, 'text/foo'
      -
      + +
      +
      +
      mime_type :foo, 'text/foo'
      +
      +
      +
      +

      Você também pode utilizar isto com o helper content_type:

      -
      content_type :foo
      -
      + +
      +
      +
      content_type :foo
      +
      +
      +
      +

      Middleware Rack

      @@ -650,24 +982,36 @@

      Middleware Rack

      O Sinatra faz construtores pipelines do middleware Rack facilmente em um nível superior utilizando o método use:

      -
      require 'sinatra'
      -require 'meu_middleware_customizado'
       
      -use Rack::Lint
      -use MeuMiddlewareCustomizado
      +
      +
      +
      require 'sinatra'
      +require 'meu_middleware_customizado'
      +
      +use Rack::Lint
      +use MeuMiddlewareCustomizado
      +
      +get '/ola' do
      +  'Olá mundo'
      +end
      +
      +
      +
      -get '/ola' do - 'Olá mundo' -end -

      A semântica de use é idêntica aquela definida para a DSL Rack::Builder (mais frequentemente utilizada para arquivos rackup). Por exemplo, o método use aceita múltiplos argumentos/variáveis bem como blocos:

      -
      use Rack::Auth::Basic do |usuario, senha|
      -  usuario == 'admin' && senha == 'secreto'
      -end
      -
      + +
      +
      +
      use Rack::Auth::Basic do |usuario, senha|
      +  usuario == 'admin' && senha == 'secreto'
      +end
      +
      +
      +
      +

      O Rack é distribuido com uma variedade de middleware padrões para logs, debugs, rotas de URL, autenticação, e manipuladores de sessão. Sinatra utilizada muitos desses componentes automaticamente baseando sobre @@ -679,32 +1023,38 @@

      Testando

      Testes no Sinatra podem ser escritos utilizando qualquer biblioteca ou framework de teste baseados no Rack. Rack::Test é recomendado:

      -
      require 'minha_aplicacao_sinatra'
      -require 'rack/test'
      -
      -class MinhaAplicacaoTeste < Test::Unit::TestCase
      -  include Rack::Test::Methods
      -
      -  def app
      -    Sinatra::Application
      -  end
      -
      -  def meu_test_default
      -    get '/'
      -    assert_equal 'Ola Mundo!', last_response.body
      -  end
      -
      -  def teste_com_parametros
      -    get '/atender', :name => 'Frank'
      -    assert_equal 'Olá Frank!', last_response.bodymeet
      -  end
      -
      -  def test_com_ambiente_rack
      -    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      -    assert_equal "Você está utilizando o Songbird!", last_response.body
      -  end
      -end
      -
      + +
      +
      +
      require 'minha_aplicacao_sinatra'
      +require 'rack/test'
      +
      +class MinhaAplicacaoTeste < Test::Unit::TestCase
      +  include Rack::Test::Methods
      +
      +  def app
      +    Sinatra::Application
      +  end
      +
      +  def meu_test_default
      +    get '/'
      +    assert_equal 'Ola Mundo!', last_response.body
      +  end
      +
      +  def teste_com_parametros
      +    get '/atender', :name => 'Frank'
      +    assert_equal 'Olá Frank!', last_response.bodymeet
      +  end
      +
      +  def test_com_ambiente_rack
      +    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      +    assert_equal "Você está utilizando o Songbird!", last_response.body
      +  end
      +end
      +
      +
      +
      +

      NOTA: Os módulos de classe embutidos Sinatra::Test e Sinatra::TestHarness são depreciados na versão 0.9.2.

      @@ -719,34 +1069,50 @@

      Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares

      estilo de configuração de micro aplicativos (exemplo: uma simples arquivo de aplicação, diretórios ./public e ./views, logs, página de detalhes de exceção, etc.). É onde o Sinatra::Base entra em jogo:

      -
      require 'sinatra/base'
       
      -class MinhaApp < Sinatra::Base
      -  set :sessions, true
      -  set :foo, 'bar'
      +
      +
      +
      require 'sinatra/base'
      +
      +class MinhaApp < Sinatra::Base
      +  set :sessions, true
      +  set :foo, 'bar'
      +
      +  get '/' do
      +    'Ola mundo!'
      +  end
      +end
      +
      +
      +
      - get '/' do - 'Ola mundo!' - end -end -

      A classe MinhaApp é um componente Rack independente que pode agir como um middleware Rack, uma aplicação Rack, ou metal Rails. Você pode utilizar ou executar esta classe com um arquivo rackup config.ru; ou, controlar um componente de servidor fornecendo como biblioteca:

      -
      MinhaApp.run! :host => 'localhost', :port => 9090
      -
      + +
      +
      +
      MinhaApp.run! :host => 'localhost', :port => 9090
      +
      +
      +
      +

      Os métodos disponíveis para subclasses Sinatra::Base são exatamente como aqueles disponíveis via a DSL de nível superior. Aplicações de nível mais alto podem ser convertidas para componentes Sinatra::Base com duas modificações:

        -
      • Seu arquivo deve requerer sinatra/base ao invés de sinatra; +

      • +

        Seu arquivo deve requerer sinatra/base ao invés de sinatra; outra coisa, todos os métodos DSL do Sinatra são importados para o -espaço principal.

      • -
      • Coloque as rotas da sua aplicação, manipuladores de erro, filtros e -opções na subclasse de um Sinatra::Base.

      • +espaço principal.

        + +
      • +

        Coloque as rotas da sua aplicação, manipuladores de erro, filtros e +opções na subclasse de um Sinatra::Base.

        +

      Sinatra::Base é um quadro branco. Muitas opções são desabilitadas por padrão, incluindo o servidor embutido. Veja Opções e @@ -767,52 +1133,90 @@

      Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares

      Linha de Comando

      Aplicações Sinatra podem ser executadas diretamente:

      -
      ruby minhaapp.rb [-h] [-x] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s SERVIDOR]
      -
      + +
      +
      +
      ruby minhaapp.rb [-h] [-x] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s SERVIDOR]
      +
      +
      +
      +

      As opções são:

      -
      -h # ajuda
      +
      +
      -h # ajuda
       -p # define a porta (padrão é 4567)
       -o # define o host (padrão é 0.0.0.0)
       -e # define o ambiente (padrão é development)
       -s # especifica o servidor/manipulador rack (padrão é thin)
       -x # ativa o bloqueio (padrão é desligado)
      -
      +
      +

      A última versão

      Se você gostaria de utilizar o código da última versão do Sinatra, crie um clone local e execute sua aplicação com o diretório sinatra/lib no LOAD_PATH:

      -
      cd minhaapp
      +
      +
      +
      +
      cd minhaapp
       git clone git://github.com/sinatra/sinatra.git
       ruby -I sinatra/lib minhaapp.rb
      -
      +
      + + +

      Alternativamente, você pode adicionar o diretório do sinatra/lib no LOAD_PATH do seu aplicativo:

      -
      $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
      -require 'rubygems'
      -require 'sinatra'
      -
      -get '/sobre' do
      -  "Estou rodando a versão" + Sinatra::VERSION
      -end
      -
      + +
      +
      +
      $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
      +require 'rubygems'
      +require 'sinatra'
      +
      +get '/sobre' do
      +  "Estou rodando a versão" + Sinatra::VERSION
      +end
      +
      +
      +
      +

      Para atualizar o código do Sinatra no futuro:

      -
      cd meuprojeto/sinatra
      +
      +
      +
      +
      cd meuprojeto/sinatra
       git pull
      -
      +
      + + +

      Mais

      diff --git a/_includes/README.pt-pt.html b/_includes/README.pt-pt.html index 7602a8ef..98d27217 100644 --- a/_includes/README.pt-pt.html +++ b/_includes/README.pt-pt.html @@ -43,17 +43,29 @@

      Sinatra é uma DSL para criar rapidamente aplicações web em Ruby com o mínimo de esforço:

      -
      # minhaapp.rb
      -require 'rubygems'
      -require 'sinatra'
      -get '/' do
      -  'Olá Mundo!'
      -end
      -
      + +
      +
      +
      # minhaapp.rb
      +require 'rubygems'
      +require 'sinatra'
      +get '/' do
      +  'Olá Mundo!'
      +end
      +
      +
      +
      +

      Instale a gem e execute com:

      -
      sudo gem install sinatra
      +
      +
      +
      +
      sudo gem install sinatra
       ruby minhaapp.rb
      -
      +
      + + +

      Aceda em: localhost:4567

      @@ -61,78 +73,126 @@

      Rotas

      No Sinatra, uma rota é um metodo HTTP associado a uma URL correspondente padrão. Cada rota é associada a um bloco:

      -
      get '/' do
      -  .. mostrar algo ..
      -end
      -
      -post '/' do
      -  .. criar algo ..
      -end
      -
      -put '/' do
      -  .. atualizar algo ..
      -end
      -
      -delete '/' do
      -  .. apagar algo ..
      -end
      -
      + +
      +
      +
      get '/' do
      +  .. mostrar algo ..
      +end
      +
      +post '/' do
      +  .. criar algo ..
      +end
      +
      +put '/' do
      +  .. atualizar algo ..
      +end
      +
      +delete '/' do
      +  .. apagar algo ..
      +end
      +
      +
      +
      +

      Rotas são encontradas na ordem em que são definidas. A primeira rota que é encontrada invoca o pedido.

      Padrões de rota podem incluir parâmetros nomeados, acessíveis através da hash params:

      -
      get '/ola/:nome' do
      -  # corresponde a "GET /ola/foo" e "GET /ola/bar"
      -  # params[:nome] é 'foo' ou 'bar'
      -  "Olá #{params[:nome]}!"
      -end
      -
      + +
      +
      +
      get '/ola/:nome' do
      +  # corresponde a "GET /ola/foo" e "GET /ola/bar"
      +  # params[:nome] é 'foo' ou 'bar'
      +  "Olá #{params[:nome]}!"
      +end
      +
      +
      +
      +

      Pode também aceder a parâmetros nomeados através do bloco de parâmetros:

      -
      get '/ola/:nome' do |n|
      -  "Olá #{n}!"
      -end
      -
      + +
      +
      +
      get '/ola/:nome' do |n|
      +  "Olá #{n}!"
      +end
      +
      +
      +
      +

      Padrões de rota podem também incluir parâmetros splat (asteriscos), acessíveis através do array params[:splat].

      -
      get '/diga/*/ao/*' do
      -  # corresponde a /diga/ola/ao/mundo
      -  params[:splat] # => ["ola", "mundo"]
      -end
      -
      -get '/download/*.*' do
      -  # corresponde a /download/pasta/do/arquivo.xml
      -  params[:splat] # => ["pasta/do/arquivo", "xml"]
      -end
      -
      + +
      +
      +
      get '/diga/*/ao/*' do
      +  # corresponde a /diga/ola/ao/mundo
      +  params[:splat] # => ["ola", "mundo"]
      +end
      +
      +get '/download/*.*' do
      +  # corresponde a /download/pasta/do/arquivo.xml
      +  params[:splat] # => ["pasta/do/arquivo", "xml"]
      +end
      +
      +
      +
      +

      Rotas correspondem-se com expressões regulares:

      -
      get %r{/ola/([\w]+)} do
      -  "Olá, #{params[:captures].first}!"
      -end
      -
      + +
      +
      +
      get %r{/ola/([\w]+)} do
      +  "Olá, #{params[:captures].first}!"
      +end
      +
      +
      +
      +

      Ou com um bloco de parâmetro:

      -
      get %r{/ola/([\w]+)} do |c|
      -  "Olá, #{c}!"
      -end
      -
      + +
      +
      +
      get %r{/ola/([\w]+)} do |c|
      +  "Olá, #{c}!"
      +end
      +
      +
      +
      +

      Rotas podem incluir uma variedade de condições correspondentes, por exemplo, o agente usuário:

      -
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      -  "Você está a utilizar a versão #{params[:agent][0]} do Songbird."
      -end
      -
      -get '/foo' do
      -  # Corresponde a um navegador não Songbird
      -end
      -
      + +
      +
      +
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      +  "Você está a utilizar a versão #{params[:agent][0]} do Songbird."
      +end
      +
      +get '/foo' do
      +  # Corresponde a um navegador não Songbird
      +end
      +
      +
      +
      +

      Arquivos estáticos

      Arquivos estáticos são disponibilizados a partir do directório ./public. Você pode especificar um local diferente através da opção :public_folder

      -
      set :public_folder, File.dirname(__FILE__) + '/estatico'
      -
      + +
      +
      +
      set :public_folder, File.dirname(__FILE__) + '/estatico'
      +
      +
      +
      +

      Note que o nome do directório público não é incluido no URL. Um arquivo ./public/css/style.css é disponibilizado como http://example.com/css/style.css.

      @@ -142,8 +202,14 @@

      Views / Templates

      Templates presumem-se estar localizados sob o directório ./views. Para utilizar um directório de views diferente:

      -
      set :views, File.dirname(__FILE__) + '/modelo'
      -
      + +
      +
      +
      set :views, File.dirname(__FILE__) + '/modelo'
      +
      +
      +
      +

      Uma coisa importante a ser lembrada é que você sempre tem as referências dos templates como símbolos, mesmo se eles estiverem num sub-directório (nesse caso utilize :'subdir/template'). Métodos de renderização irão @@ -153,13 +219,19 @@

      Views / Templates

      Haml Templates

      A gem/biblioteca haml é necessária para renderizar templates HAML:

      -
      # É necessário requerir 'haml' na aplicação.
      -require 'haml'
       
      -get '/' do
      -  haml :index
      -end
      -
      +
      +
      +
      # É necessário requerir 'haml' na aplicação.
      +require 'haml'
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      +

      Renderiza ./views/index.haml.

      Opções @@ -168,62 +240,92 @@

      Haml Templates

      veja Opções e Configurações, e substitua em uma requisição individual.

      -
      set :haml, {:format => :html5 } # o formato padrão do Haml é :xhtml
       
      -get '/' do
      -  haml :index, :haml_options => {:format => :html4 } # substituido
      -end
      -
      +
      +
      +
      set :haml, {:format => :html5 } # o formato padrão do Haml é :xhtml
      +
      +get '/' do
      +  haml :index, :haml_options => {:format => :html4 } # substituido
      +end
      +
      +
      +
      +

      Erb Templates

      -
      # É necessário requerir 'erb' na aplicação.
      -require 'erb'
       
      -get '/' do
      -  erb :index
      -end
      -
      +
      +
      +
      # É necessário requerir 'erb' na aplicação.
      +require 'erb'
      +
      +get '/' do
      +  erb :index
      +end
      +
      +
      +
      +

      Renderiza ./views/index.erb

      Erubis

      A gem/biblioteca erubis é necessária para renderizar templates erubis:

      -
      # É necessário requerir 'erubis' na aplicação.
      -require 'erubis'
       
      -get '/' do
      -  erubis :index
      -end
      -
      +
      +
      +
      # É necessário requerir 'erubis' na aplicação.
      +require 'erubis'
      +
      +get '/' do
      +  erubis :index
      +end
      +
      +
      +
      +

      Renderiza ./views/index.erubis

      Builder Templates

      A gem/biblioteca builder é necessária para renderizar templates builder:

      -
      # É necessário requerir 'builder' na aplicação.
      -require 'builder'
      -
      -get '/' do
      -  content_type 'application/xml', :charset => 'utf-8'
      -  builder :index
      -end
      -
      + +
      +
      +
      # É necessário requerir 'builder' na aplicação.
      +require 'builder'
      +
      +get '/' do
      +  content_type 'application/xml', :charset => 'utf-8'
      +  builder :index
      +end
      +
      +
      +
      +

      Renderiza ./views/index.builder.

      Sass Templates

      A gem/biblioteca sass é necessária para renderizar templates sass:

      -
      # É necessário requerir 'haml' ou 'sass' na aplicação.
      -require 'sass'
      -
      -get '/stylesheet.css' do
      -  content_type 'text/css', :charset => 'utf-8'
      -  sass :stylesheet
      -end
      -
      + +
      +
      +
      # É necessário requerir 'haml' ou 'sass' na aplicação.
      +require 'sass'
      +
      +get '/stylesheet.css' do
      +  content_type 'text/css', :charset => 'utf-8'
      +  sass :stylesheet
      +end
      +
      +
      +
      +

      Renderiza ./views/stylesheet.sass.

      Opções @@ -232,33 +334,51 @@

      Sass Templates

      veja Opções e Configurações, e substitua em uma requisição individual.

      -
      set :sass, {:style => :compact } # o estilo padrão do Sass é :nested
       
      -get '/stylesheet.css' do
      -  content_type 'text/css', :charset => 'utf-8'
      -  sass :stylesheet, :style => :expanded # substituido
      -end
      -
      +
      +
      +
      set :sass, {:style => :compact } # o estilo padrão do Sass é :nested
      +
      +get '/stylesheet.css' do
      +  content_type 'text/css', :charset => 'utf-8'
      +  sass :stylesheet, :style => :expanded # substituido
      +end
      +
      +
      +
      +

      Less Templates

      A gem/biblioteca less é necessária para renderizar templates Less:

      -
      # É necessário requerir 'less' na aplicação.
      -require 'less'
      -
      -get '/stylesheet.css' do
      -  content_type 'text/css', :charset => 'utf-8'
      -  less :stylesheet
      -end
      -
      + +
      +
      +
      # É necessário requerir 'less' na aplicação.
      +require 'less'
      +
      +get '/stylesheet.css' do
      +  content_type 'text/css', :charset => 'utf-8'
      +  less :stylesheet
      +end
      +
      +
      +
      +

      Renderiza ./views/stylesheet.less.

      Templates Inline

      -
      get '/' do
      -  haml '%div.title Olá Mundo'
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml '%div.title Olá Mundo'
      +end
      +
      +
      +
      +

      Renderiza a string, em uma linha, no template.

      @@ -267,17 +387,29 @@

      Acedendo a Variáveis nos Templates

      Templates são avaliados dentro do mesmo contexto que os manipuladores de rota. Variáveis de instância definidas em rotas manipuladas são directamente acedidas por templates:

      -
      get '/:id' do
      -  @foo = Foo.find(params[:id])
      -  haml '%h1= @foo.nome'
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  @foo = Foo.find(params[:id])
      +  haml '%h1= @foo.nome'
      +end
      +
      +
      +
      +

      Ou, especifique um hash explícito para variáveis locais:

      -
      get '/:id' do
      -  foo = Foo.find(params[:id])
      -  haml '%h1= foo.nome', :locals => { :foo => foo }
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  foo = Foo.find(params[:id])
      +  haml '%h1= foo.nome', :locals => { :foo => foo }
      +end
      +
      +
      +
      +

      Isso é tipicamente utilizado quando renderizamos templates parciais (partials) dentro de outros templates.

      @@ -285,22 +417,28 @@

      Acedendo a Variáveis nos Templates

      Templates Inline

      Templates podem ser definidos no final do arquivo fonte(.rb):

      -
      require 'rubygems'
      -require 'sinatra'
       
      -get '/' do
      -  haml :index
      -end
      +
      +
      +
      require 'rubygems'
      +require 'sinatra'
       
      -__END__
      +get '/' do
      +  haml :index
      +end
      +
      +__END__
       
       @@ layout
       %html
         = yield
       
       @@ index
      -%div.title Olá Mundo!!!!!
      -
      +%div.title Olá Mundo!!!!! +
      + + +

      NOTA: Templates inline definidos no arquivo fonte são automaticamente carregados pelo sinatra. Digite `enable :inline_templates` se tem templates inline no outro arquivo fonte.

      @@ -310,40 +448,58 @@

      Templates nomeados

      Templates também podem ser definidos utilizando o método top-level template:

      -
      template :layout do
      -  "%html\n  =yield\n"
      -end
      -
      -template :index do
      -  '%div.title Olá Mundo!'
      -end
      -
      -get '/' do
      -  haml :index
      -end
      -
      + +
      +
      +
      template :layout do
      +  "%html\n  =yield\n"
      +end
      +
      +template :index do
      +  '%div.title Olá Mundo!'
      +end
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      +

      Se existir um template com nome “layout”, ele será utilizado sempre que um template for renderizado. Pode desactivar layouts usando :layout => false.

      -
      get '/' do
      -  haml :index, :layout => !request.xhr?
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml :index, :layout => !request.xhr?
      +end
      +
      +
      +
      +

      Helpers

      Use o método de alto nível helpers para definir métodos auxiliares para utilizar em manipuladores de rotas e modelos:

      -
      helpers do
      -  def bar(nome)
      -    "#{nome}bar"
      -  end
      -end
      -
      -get '/:nome' do
      -  bar(params[:nome])
      -end
      -
      + +
      +
      +
      helpers do
      +  def bar(nome)
      +    "#{nome}bar"
      +  end
      +end
      +
      +get '/:nome' do
      +  bar(params[:nome])
      +end
      +
      +
      +
      +

      Filtros

      @@ -351,67 +507,121 @@

      Filtros

      da requisição e podem modificar a requisição e a reposta. Variáveis de instância definidas nos filtros são acedidas através de rotas e templates:

      -
      before do
      -  @nota = 'Olá!'
      -  request.path_info = '/foo/bar/baz'
      -end
      -
      -get '/foo/*' do
      -  @nota #=> 'Olá!'
      -  params[:splat] #=> 'bar/baz'
      -end
      -
      + +
      +
      +
      before do
      +  @nota = 'Olá!'
      +  request.path_info = '/foo/bar/baz'
      +end
      +
      +get '/foo/*' do
      +  @nota #=> 'Olá!'
      +  params[:splat] #=> 'bar/baz'
      +end
      +
      +
      +
      +

      Filtros After são avaliados após cada requisição dentro do contexto da requisição e também podem modificar o pedido e a resposta. Variáveis de instância definidas nos filtros before e rotas são acedidas através dos filtros after:

      -
      after do
      -  puts response.status
      -end
      -
      + +
      +
      +
      after do
      +  puts response.status
      +end
      +
      +
      +
      +

      Filtros opcionalmente têm um padrão, fazendo com que sejam avaliados somente se o caminho do pedido coincidir com esse padrão:

      -
      before '/protected/*' do
      -  autenticar!
      -end
      -
      -after '/create/:slug' do |slug|
      -  session[:last_slug] = slug
      -end
      -
      + +
      +
      +
      before '/protected/*' do
      +  autenticar!
      +end
      +
      +after '/create/:slug' do |slug|
      +  session[:last_slug] = slug
      +end
      +
      +
      +
      +

      Halting

      Para parar imediatamente uma requisição dentro de um filtro ou rota utilize:

      -
      halt
      -
      + +
      +
      +
      halt
      +
      +
      +
      +

      Pode também especificar o status ao parar…

      -
      halt 410
      -
      + +
      +
      +
      halt 410
      +
      +
      +
      +

      Ou com um corpo de texto…

      -
      halt 'isto será o corpo de texto'
      -
      + +
      +
      +
      halt 'isto será o corpo de texto'
      +
      +
      +
      +

      Ou também…

      -
      halt 401, 'vamos embora!'
      -
      + +
      +
      +
      halt 401, 'vamos embora!'
      +
      +
      +
      +

      Com cabeçalhos…

      -
      halt 402, {'Content-Type' => 'text/plain'}, 'revanche'
      -
      + +
      +
      +
      halt 402, {'Content-Type' => 'text/plain'}, 'revanche'
      +
      +
      +
      +

      Passing

      Dentro de uma rota, pode passar para a próxima rota correspondente usando pass:

      -
      get '/adivinhar/:quem' do
      -  pass unless params[:quem] == 'Frank'
      -  'Apanhaste-me!'
      -end
      -
      -get '/adivinhar/*' do
      -  'Falhaste!'
      -end
      -
      + +
      +
      +
      get '/adivinhar/:quem' do
      +  pass unless params[:quem] == 'Frank'
      +  'Apanhaste-me!'
      +end
      +
      +get '/adivinhar/*' do
      +  'Falhaste!'
      +end
      +
      +
      +
      +

      O bloqueio da rota é imediatamente encerrado e o controle continua com a próxima rota de parâmetro. Se o parâmetro da rota não for encontrado, um 404 é retornado.

      @@ -420,21 +630,39 @@

      Passing

      Configuração

      Correndo uma vez, na inicialização, em qualquer ambiente:

      -
      configure do
      -  ...
      -end
      -
      + +
      +
      +
      configure do
      +  ...
      +end
      +
      +
      +
      +

      Correndo somente quando o ambiente (RACK_ENV environment variável) é definido para :production:

      -
      configure :production do
      -  ...
      -end
      -
      + +
      +
      +
      configure :production do
      +  ...
      +end
      +
      +
      +
      +

      Correndo quando o ambiente é definido para :production ou :test:

      -
      configure :production, :test do
      -  ...
      -end
      -
      + +
      +
      +
      configure :production, :test do
      +  ...
      +end
      +
      +
      +
      +

      Lidar com Erros

      @@ -446,48 +674,86 @@

      Não Encontrado

      Quando um Sinatra::NotFound exception é levantado, ou o código de status da reposta é 404, o manipulador not_found é invocado:

      -
      not_found do
      -  'Isto está longe de ser encontrado'
      -end
      -
      + +
      +
      +
      not_found do
      +  'Isto está longe de ser encontrado'
      +end
      +
      +
      +
      +

      Erro

      O manipulador error é invocado sempre que uma exceção é lançada a partir de um bloco de rota ou um filtro. O objecto da exceção pode ser obtido a partir da variável Rack sinatra.error:

      -
      error do
      -  'Peço desculpa, houve um erro desagradável - ' + env['sinatra.error'].name
      -end
      -
      + +
      +
      +
      error do
      +  'Peço desculpa, houve um erro desagradável - ' + env['sinatra.error'].name
      +end
      +
      +
      +
      +

      Erros personalizados:

      -
      error MeuErroPersonalizado do
      -  'O que aconteceu foi...' + env['sinatra.error'].message
      -end
      -
      + +
      +
      +
      error MeuErroPersonalizado do
      +  'O que aconteceu foi...' + env['sinatra.error'].message
      +end
      +
      +
      +
      +

      Então, se isso acontecer:

      -
      get '/' do
      -  raise MeuErroPersonalizado, 'alguma coisa desagradável'
      -end
      -
      + +
      +
      +
      get '/' do
      +  raise MeuErroPersonalizado, 'alguma coisa desagradável'
      +end
      +
      +
      +
      +

      O resultado será:

      -
      O que aconteceu foi...alguma coisa desagradável
      -
      + +
      O que aconteceu foi...alguma coisa desagradável
      +
      +

      Alternativamente, pode definir um manipulador de erro para um código de status:

      -
      error 403 do
      -  'Accesso negado'
      -end
      -
      -get '/secreto' do
      -  403
      -end
      -
      + +
      +
      +
      error 403 do
      +  'Accesso negado'
      +end
      +
      +get '/secreto' do
      +  403
      +end
      +
      +
      +
      +

      Ou um range (alcance):

      -
      error 400..510 do
      -  'Boom'
      -end
      -
      + +
      +
      +
      error 400..510 do
      +  'Boom'
      +end
      +
      +
      +
      +

      O Sinatra define os manipuladores especiais not_found e error quando corre no ambiente de desenvolvimento.

      @@ -497,11 +763,23 @@

      Mime Types

      Quando utilizamos send_file ou arquivos estáticos pode ter mime types Sinatra não entendidos. Use mime_type para os registar por extensão de arquivos:

      -
      mime_type :foo, 'text/foo'
      -
      + +
      +
      +
      mime_type :foo, 'text/foo'
      +
      +
      +
      +

      Pode também utilizar isto com o helper content_type:

      -
      content_type :foo
      -
      + +
      +
      +
      content_type :foo
      +
      +
      +
      +

      Middleware Rack

      @@ -514,24 +792,36 @@

      Middleware Rack

      O Sinatra torna a construção de pipelines do middleware Rack fácil a um nível superior utilizando o método use:

      -
      require 'sinatra'
      -require 'meu_middleware_personalizado'
       
      -use Rack::Lint
      -use MeuMiddlewarePersonalizado
      +
      +
      +
      require 'sinatra'
      +require 'meu_middleware_personalizado'
      +
      +use Rack::Lint
      +use MeuMiddlewarePersonalizado
      +
      +get '/ola' do
      +  'Olá mundo'
      +end
      +
      +
      +
      -get '/ola' do - 'Olá mundo' -end -

      A semântica de use é idêntica aquela definida para a DSL Rack::Builder (mais frequentemente utilizada para arquivos rackup). Por exemplo, o método use aceita múltiplos argumentos/variáveis, bem como blocos:

      -
      use Rack::Auth::Basic do |utilizador, senha|
      -  utilizador == 'admin' && senha == 'secreto'
      -end
      -
      + +
      +
      +
      use Rack::Auth::Basic do |utilizador, senha|
      +  utilizador == 'admin' && senha == 'secreto'
      +end
      +
      +
      +
      +

      O Rack é distribuido com uma variedade de middleware padrões para logs, debugs, rotas de URL, autenticação, e manipuladores de sessão.Sinatra utiliza muitos desses componentes automaticamente dependendo da @@ -544,32 +834,38 @@

      Testando

      Testes no Sinatra podem ser escritos utilizando qualquer biblioteca ou framework de teste baseados no Rack. Rack::Test é recomendado:

      -
      require 'minha_aplicacao_sinatra'
      -require 'rack/test'
      -
      -class MinhaAplicacaoTeste < Test::Unit::TestCase
      -  include Rack::Test::Methods
      -
      -  def app
      -    Sinatra::Application
      -  end
      -
      -  def meu_test_default
      -    get '/'
      -    assert_equal 'Ola Mundo!', last_response.body
      -  end
      -
      -  def teste_com_parametros
      -    get '/atender', :name => 'Frank'
      -    assert_equal 'Olá Frank!', last_response.bodymeet
      -  end
      -
      -  def test_com_ambiente_rack
      -    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      -    assert_equal "Você está utilizando o Songbird!", last_response.body
      -  end
      -end
      -
      + +
      +
      +
      require 'minha_aplicacao_sinatra'
      +require 'rack/test'
      +
      +class MinhaAplicacaoTeste < Test::Unit::TestCase
      +  include Rack::Test::Methods
      +
      +  def app
      +    Sinatra::Application
      +  end
      +
      +  def meu_test_default
      +    get '/'
      +    assert_equal 'Ola Mundo!', last_response.body
      +  end
      +
      +  def teste_com_parametros
      +    get '/atender', :name => 'Frank'
      +    assert_equal 'Olá Frank!', last_response.bodymeet
      +  end
      +
      +  def test_com_ambiente_rack
      +    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      +    assert_equal "Você está utilizando o Songbird!", last_response.body
      +  end
      +end
      +
      +
      +
      +

      NOTA: Os módulos de classe embutidos Sinatra::Test e Sinatra::TestHarness são depreciados na versão 0.9.2.

      @@ -584,23 +880,35 @@

      Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares

      estilo de configuração de micro aplicativos (exemplo: um simples arquivo de aplicação, directórios ./public e ./views, logs, página de detalhes de excepção, etc.). É onde o Sinatra::Base entra em jogo:

      -
      require 'sinatra/base'
       
      -class MinhaApp < Sinatra::Base
      -  set :sessions, true
      -  set :foo, 'bar'
      +
      +
      +
      require 'sinatra/base'
      +
      +class MinhaApp < Sinatra::Base
      +  set :sessions, true
      +  set :foo, 'bar'
      +
      +  get '/' do
      +    'Olá mundo!'
      +  end
      +end
      +
      +
      +
      - get '/' do - 'Olá mundo!' - end -end -

      A classe MinhaApp é um componente Rack independente que pode utilizar como um middleware Rack, uma aplicação Rack, ou metal Rails. Pode utilizar ou executar esta classe com um arquivo rackup config.ru; ou, controlar um componente de servidor fornecendo como biblioteca:

      -
      MinhaApp.run! :host => 'localhost', :port => 9090
      -
      + +
      +
      +
      MinhaApp.run! :host => 'localhost', :port => 9090
      +
      +
      +
      +

      Os métodos disponíveis para subclasses Sinatra::Base são exatamente como aqueles disponíveis via a DSL de nível superior. Aplicações de nível mais alto podem ser convertidas para componentes Sinatra::Base com duas @@ -625,52 +933,90 @@

      Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares

      Linha de Comandos

      As aplicações Sinatra podem ser executadas directamente:

      -
      ruby minhaapp.rb [-h] [-x] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s SERVIDOR]
      -
      + +
      +
      +
      ruby minhaapp.rb [-h] [-x] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s SERVIDOR]
      +
      +
      +
      +

      As opções são:

      -
      -h # ajuda
      +
      +
      -h # ajuda
       -p # define a porta (padrão é 4567)
       -o # define o host (padrão é 0.0.0.0)
       -e # define o ambiente (padrão é development)
       -s # especifica o servidor/manipulador rack (padrão é thin)
       -x # activa o bloqueio (padrão é desligado)
      -
      +
      +

      A última versão

      Se gostaria de utilizar o código da última versão do Sinatra, crie um clone local e execute sua aplicação com o directório sinatra/lib no LOAD_PATH:

      -
      cd minhaapp
      +
      +
      +
      +
      cd minhaapp
       git clone git://github.com/sinatra/sinatra.git
       ruby -I sinatra/lib minhaapp.rb
      -
      +
      + + +

      Alternativamente, pode adicionar o directório do sinatra/lib no LOAD_PATH do seu aplicativo:

      -
      $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
      -require 'rubygems'
      -require 'sinatra'
      -
      -get '/sobre' do
      -  "Estou correndo a versão" + Sinatra::VERSION
      -end
      -
      + +
      +
      +
      $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
      +require 'rubygems'
      +require 'sinatra'
      +
      +get '/sobre' do
      +  "Estou correndo a versão" + Sinatra::VERSION
      +end
      +
      +
      +
      +

      Para actualizar o código do Sinatra no futuro:

      -
      cd meuprojeto/sinatra
      +
      +
      +
      +
      cd meuprojeto/sinatra
       git pull
      -
      +
      + + +

      Mais

      diff --git a/_includes/README.ru.html b/_includes/README.ru.html index 707a6a53..4a501745 100644 --- a/_includes/README.ru.html +++ b/_includes/README.ru.html @@ -69,16 +69,16 @@
    95. Not Found
    96. Ошибки
    -
  • Rack "прослойки"
  • +
  • Rack “прослойки”
  • Тестирование
  • -
  • Sinatra::Base — "прослойки", библиотеки и модульные приложения
  • +
  • Sinatra::Base — “прослойки”, библиотеки и модульные приложения
    1. Модульные приложения против классических
    2. Запуск модульных приложений
    3. Запуск классических приложений с config.ru
    4. Когда использовать config.ru?
    5. -
    6. Использование Sinatra в качестве "прослойки"
    7. -
    8. Создание приложений "на лету"
    9. +
    10. Использование Sinatra в качестве “прослойки”
    11. +
    12. Создание приложений “на лету”
  • Области видимости и привязка
    1. @@ -107,19 +107,29 @@

      Sinatra — это предметно-ориентированный каркас (DSL) для быстрого создания функциональных веб-приложений на Ruby с минимумом усилий:

      -
      # myapp.rb
      -require 'sinatra'
       
      -get '/' do
      -  'Hello world!'
      -end
      -
      +
      +
      +
      # myapp.rb
      +require 'sinatra'
      +
      +get '/' do
      +  'Hello world!'
      +end
      +
      +
      +
      +

      Установите gem:

      -
      gem install sinatra
      -
      + +
      gem install sinatra
      +
      +

      и запустите приложение с помощью:

      -
      ruby myapp.rb
      -
      + +
      ruby myapp.rb
      +
      +

      Оцените результат: http://localhost:4567

      Рекомендуется также установить Thin, сделать это можно командой: gem install @@ -131,78 +141,126 @@

      Маршруты

      В Sinatra маршрут — это пара: <HTTP метод> и <шаблон URL>. Каждый маршрут связан с блоком кода:

      -
      get '/' do
      -  # .. что-то показать ..
      -end
      -
      -post '/' do
      -  # .. что-то создать ..
      -end
      -
      -put '/' do
      -  # .. что-то заменить ..
      -end
      -
      -patch '/' do
      -  # .. что-то изменить ..
      -end
      -
      -delete '/' do
      -  # .. что-то удалить ..
      -end
      -
      -options '/' do
      -  # .. что-то ответить ..
      -end
      -
      + +
      +
      +
      get '/' do
      +  # .. что-то показать ..
      +end
      +
      +post '/' do
      +  # .. что-то создать ..
      +end
      +
      +put '/' do
      +  # .. что-то заменить ..
      +end
      +
      +patch '/' do
      +  # .. что-то изменить ..
      +end
      +
      +delete '/' do
      +  # .. что-то удалить ..
      +end
      +
      +options '/' do
      +  # .. что-то ответить ..
      +end
      +
      +
      +
      +

      Маршруты сверяются с запросом в порядке очередности их записи в файле приложения. Первый же совпавший с запросом маршрут и будет вызван.

      Шаблоны маршрутов могут включать в себя именованные параметры, доступные в xэше params:

      -
      get '/hello/:name' do
      -  # соответствует "GET /hello/foo" и "GET /hello/bar",
      -  # где params[:name] 'foo' или 'bar'
      -  "Hello #{params[:name]}!"
      -end
      -
      + +
      +
      +
      get '/hello/:name' do
      +  # соответствует "GET /hello/foo" и "GET /hello/bar",
      +  # где params[:name] 'foo' или 'bar'
      +  "Hello #{params[:name]}!"
      +end
      +
      +
      +
      +

      Также можно использовать именованные параметры в качестве переменных блока:

      -
      get '/hello/:name' do |n|
      -  "Hello #{n}!"
      -end
      -
      -

      Шаблоны маршрутов также могут включать в себя splat (или '*' маску, + +

      +
      +
      get '/hello/:name' do |n|
      +  "Hello #{n}!"
      +end
      +
      +
      +
      + +

      Шаблоны маршрутов также могут включать в себя splat (или ‘*’ маску, обозначающую любой символ) параметры, доступные в массиве params[:splat]:

      -
      get '/say/*/to/*' do
      -  # соответствует /say/hello/to/world
      -  params[:splat] # => ["hello", "world"]
      -end
      -
      -get '/download/*.*' do
      -  # соответствует /download/path/to/file.xml
      -  params[:splat] # => ["path/to/file", "xml"]
      -end
      -
      + +
      +
      +
      get '/say/*/to/*' do
      +  # соответствует /say/hello/to/world
      +  params[:splat] # => ["hello", "world"]
      +end
      +
      +get '/download/*.*' do
      +  # соответствует /download/path/to/file.xml
      +  params[:splat] # => ["path/to/file", "xml"]
      +end
      +
      +
      +
      +

      Или с параметрами блока:

      -
      get '/download/*.*' do |path, ext|
      -  [path, ext] # => ["path/to/file", "xml"]
      -end
      -
      + +
      +
      +
      get '/download/*.*' do |path, ext|
      +  [path, ext] # => ["path/to/file", "xml"]
      +end
      +
      +
      +
      +

      Регулярные выражения в качестве шаблонов маршрутов:

      -
      get %r{/hello/([\w]+)} do
      -  "Hello, #{params[:captures].first}!"
      -end
      -
      + +
      +
      +
      get %r{/hello/([\w]+)} do
      +  "Hello, #{params[:captures].first}!"
      +end
      +
      +
      +
      +

      Или с параметром блока:

      -
      get %r{/hello/([\w]+)} do |c|
      -  "Hello, #{c}!"
      -end
      -
      + +
      +
      +
      get %r{/hello/([\w]+)} do |c|
      +  "Hello, #{c}!"
      +end
      +
      +
      +
      +

      Шаблоны маршрутов могут иметь необязательные параметры:

      -
      get '/posts.?:format?' do
      -  # соответствует "GET /posts", "GET /posts.json", "GET /posts.xml" и т.д.
      -end
      -
      + +
      +
      +
      get '/posts.?:format?' do
      +  # соответствует "GET /posts", "GET /posts.json", "GET /posts.xml" и т.д.
      +end
      +
      +
      +
      +

      Кстати, если вы не отключите защиту от обратного пути в директориях (path traversal, см. ниже), путь запроса может быть изменен до начала поиска подходящего маршрута.

      @@ -212,60 +270,84 @@

      Условия

      Маршруты могут включать различные условия совпадений, например, клиентское приложение (user agent):

      -
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      -  "You're using Songbird version #{params[:agent][0]}"
      -end
      -
      -get '/foo' do
      -  # соответствует не-songbird браузерам
      -end
      -
      + +
      +
      +
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      +  "You're using Songbird version #{params[:agent][0]}"
      +end
      +
      +get '/foo' do
      +  # соответствует не-songbird браузерам
      +end
      +
      +
      +
      +

      Другими доступными условиями являются host_name и provides:

      -
      get '/', :host_name => /^admin\./ do
      -  "Admin Area, Access denied!"
      -end
      -
      -get '/', :provides => 'html' do
      -  haml :index
      -end
      -
      -get '/', :provides => ['rss', 'atom', 'xml'] do
      -  builder :feed
      -end
      -
      + +
      +
      +
      get '/', :host_name => /^admin\./ do
      +  "Admin Area, Access denied!"
      +end
      +
      +get '/', :provides => 'html' do
      +  haml :index
      +end
      +
      +get '/', :provides => ['rss', 'atom', 'xml'] do
      +  builder :feed
      +end
      +
      +
      +
      +

      Вы можете задать собственные условия:

      -
      set(:probability) { |value| condition { rand <= value } }
       
      -get '/win_a_car', :probability => 0.1 do
      -  "You won!"
      -end
      +
      +
      +
      set(:probability) { |value| condition { rand <= value } }
      +
      +get '/win_a_car', :probability => 0.1 do
      +  "You won!"
      +end
      +
      +get '/win_a_car' do
      +  "Sorry, you lost."
      +end
      +
      +
      +
      -get '/win_a_car' do - "Sorry, you lost." -end -

      Для условия, которое принимает несколько параметров, используйте звездочку:

      -
      set(:auth) do |*roles|   # <- обратите внимание на звездочку
      -  condition do
      -    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      -      redirect "/login/", 303
      -    end
      -  end
      -end
      -
      -get "/my/account/", :auth => [:user, :admin] do
      -  "Your Account Details"
      -end
      -
      -get "/only/admin/", :auth => :admin do
      -  "Only admins are allowed here!"
      -end
      -
      + +
      +
      +
      set(:auth) do |*roles|   # <- обратите внимание на звездочку
      +  condition do
      +    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      +      redirect "/login/", 303
      +    end
      +  end
      +end
      +
      +get "/my/account/", :auth => [:user, :admin] do
      +  "Your Account Details"
      +end
      +
      +get "/only/admin/", :auth => :admin do
      +  "Only admins are allowed here!"
      +end
      +
      +
      +
      +

      Возвращаемые значения

      Возвращаемое значение блока маршрута ограничивается телом ответа, которое -будет передано HTTP клиенту, или следующей "прослойкой" (middleware) в Rack +будет передано HTTP клиенту, или следующей “прослойкой” (middleware) в Rack стеке. Чаще всего это строка, как в примерах выше. Но также приемлемы и другие значения.

      @@ -273,14 +355,20 @@

      Возвращаемые значения

      объектом Rack body, либо кодом состояния HTTP:

      Таким образом, легко можно реализовать, например, поточный пример:

      -
      class Stream
      -  def each
      -    100.times { |i| yield "#{i}\n" }
      -  end
      -end
      -
      -get('/') { Stream.new }
      -
      + +
      +
      +
      class Stream
      +  def each
      +    100.times { |i| yield "#{i}\n" }
      +  end
      +end
      +
      +get('/') { Stream.new }
      +
      +
      +
      +

      Вы также можете использовать метод stream (описываемый ниже), чтобы уменьшить количество дублируемого кода и держать логику стриминга прямо в маршруте.

      @@ -292,46 +380,70 @@

      Собственные детекторы совпадений для мар регулярных выражений в качестве шаблонов URL. Но и это еще не все. Вы можете легко определить свои собственные детекторы совпадений (matchers) для маршрутов:

      -
      class AllButPattern
      -  Match = Struct.new(:captures)
      -
      -  def initialize(except)
      -    @except   = except
      -    @captures = Match.new([])
      -  end
      -
      -  def match(str)
      -    @captures unless @except === str
      -  end
      -end
      -
      -def all_but(pattern)
      -  AllButPattern.new(pattern)
      -end
      -
      -get all_but("/index") do
      -  # ...
      -end
      -
      + +
      +
      +
      class AllButPattern
      +  Match = Struct.new(:captures)
      +
      +  def initialize(except)
      +    @except   = except
      +    @captures = Match.new([])
      +  end
      +
      +  def match(str)
      +    @captures unless @except === str
      +  end
      +end
      +
      +def all_but(pattern)
      +  AllButPattern.new(pattern)
      +end
      +
      +get all_but("/index") do
      +  # ...
      +end
      +
      +
      +
      +

      Заметьте, что предыдущий пример, возможно, чересчур усложнен, потому что он может быть реализован так:

      -
      get // do
      -  pass if request.path_info == "/index"
      -  # ...
      -end
      -
      + +
      +
      +
      get // do
      +  pass if request.path_info == "/index"
      +  # ...
      +end
      +
      +
      +
      +

      Или с использованием негативного просмотра вперед:

      -
      get %r{^(?!/index$)} do
      -  # ...
      -end
      -
      + +
      +
      +
      get %r{^(?!/index$)} do
      +  # ...
      +end
      +
      +
      +
      +

      Статические файлы

      Статические файлы отдаются из ./public директории. Вы можете указать другое место, используя опцию :public_folder:

      -
      set :public_folder, File.dirname(__FILE__) + '/static'
      -
      + +
      +
      +
      set :public_folder, File.dirname(__FILE__) + '/static'
      +
      +
      +
      +

      Учтите, что имя директории со статическими файлами не включено в URL. Например, файл ./public/css/style.css будет доступен как http://example.com/css/style.css.

      @@ -344,39 +456,69 @@

      Представления / Шаблоны

      Каждый шаблонизатор представлен своим собственным методом. Эти методы попросту возвращают строку:

      -
      get '/' do
      -  erb :index
      -end
      -
      + +
      +
      +
      get '/' do
      +  erb :index
      +end
      +
      +
      +
      +

      Отобразит views/index.erb.

      Вместо имени шаблона вы так же можете передавать непосредственно само содержимое шаблона:

      -
      get '/' do
      -  code = "<%= Time.now %>"
      -  erb code
      -end
      -
      + +
      +
      +
      get '/' do
      +  code = "<%= Time.now %>"
      +  erb code
      +end
      +
      +
      +
      +

      Эти методы принимают второй аргумент, хеш с опциями:

      -
      get '/' do
      -  erb :index, :layout => :post
      -end
      -
      + +
      +
      +
      get '/' do
      +  erb :index, :layout => :post
      +end
      +
      +
      +
      +

      Отобразит views/index.erb, вложенным в views/post.erb (по умолчанию: views/layout.erb, если существует).

      Любые опции, не понимаемые Sinatra, будут переданы в шаблонизатор:

      -
      get '/' do
      -  haml :index, :format => :html5
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml :index, :format => :html5
      +end
      +
      +
      +
      +

      Вы также можете задавать опции для шаблонизаторов в общем:

      -
      set :haml, :format => :html5
       
      -get '/' do
      -  haml :index
      -end
      -
      +
      +
      +
      set :haml, :format => :html5
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      +

      Опции, переданные в метод, переопределяют опции, заданные с помощью set.

      Доступные опции:

      @@ -385,7 +527,7 @@

      Представления / Шаблоны

      locals
      Список локальных переменных, передаваемых в документ. - Например: erb "", :locals => {:foo => "bar"} + Например: erb "<%= foo %>", :locals => {:foo => "bar"}
      default_encoding
      @@ -428,8 +570,14 @@

      Представления / Шаблоны

      По умолчанию считается, что шаблоны находятся в директории ./views. Чтобы использовать другую директорию с шаблонами:

      -
      set :views, settings.root + '/templates'
      -
      + +
      +
      +
      set :views, settings.root + '/templates'
      +
      +
      +
      +

      Важное замечание: вы всегда должны ссылаться на шаблоны с помощью символов (Symbol), даже когда они в поддиректории (в этом случае используйте :'subdir/template'). Вы должны использовать символы, потому что иначе @@ -437,10 +585,16 @@

      Представления / Шаблоны

      Буквальные шаблоны

      -
      get '/' do
      -  haml '%div.title Hello World'
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml '%div.title Hello World'
      +end
      +
      +
      +
      +

      Отобразит шаблон, переданный строкой.

      @@ -448,9 +602,15 @@

      Доступные шаблонизаторы

      Некоторые языки шаблонов имеют несколько реализаций. Чтобы указать, какую реализацию использовать, вам следует просто подключить нужную библиотеку:

      -
      require 'rdiscount' # или require 'bluecloth'
      -get('/') { markdown :index }
      -
      + +
      +
      +
      require 'rdiscount' # или require 'bluecloth'
      +get('/') { markdown :index }
      +
      +
      +
      +

      Haml шаблоны

      @@ -629,12 +789,24 @@

      Markdown шаблоны

      В Markdown невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:

      -
      erb :overview, :locals => { :text => markdown(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => markdown(:introduction) }
      +
      +
      +
      +

      Заметьте, что вы можете вызывать метод markdown из других шаблонов:

      -
      %h1 Hello From Haml!
      -%p= markdown(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= markdown(:greetings)
      +
      +
      +
      +

      Вы не можете вызывать Ruby из Markdown, соответственно, вы не можете использовать лэйауты на Markdown. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью @@ -660,12 +832,24 @@

      Textile шаблоны

      В Textile невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:

      -
      erb :overview, :locals => { :text => textile(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => textile(:introduction) }
      +
      +
      +
      +

      Заметьте, что вы можете вызывать метод textile из других шаблонов:

      -
      %h1 Hello From Haml!
      -%p= textile(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= textile(:greetings)
      +
      +
      +
      +

      Вы не можете вызывать Ruby из Textile, соответственно, вы не можете использовать лэйауты на Textile. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью @@ -691,12 +875,24 @@

      RDoc шаблоны

      В RDoc невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:

      -
      erb :overview, :locals => { :text => rdoc(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => rdoc(:introduction) }
      +
      +
      +
      +

      Заметьте, что вы можете вызывать метод rdoc из других шаблонов:

      -
      %h1 Hello From Haml!
      -%p= rdoc(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= rdoc(:greetings)
      +
      +
      +
      +

      Вы не можете вызывать Ruby из RDoc, соответственно, вы не можете использовать лэйауты на RDoc. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции @@ -795,12 +991,24 @@

      Creole шаблоны

      В Creole невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:

      -
      erb :overview, :locals => { :text => creole(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => creole(:introduction) }
      +
      +
      +
      +

      Заметьте, что вы можете вызывать метод creole из других шаблонов:

      -
      %h1 Hello From Haml!
      -%p= creole(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= creole(:greetings)
      +
      +
      +
      +

      Вы не можете вызывать Ruby из Creole, соответственно, вы не можете использовать лэйауты на Creole. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью @@ -856,13 +1064,25 @@

      Yajl шаблоны

      Содержимое шаблона интерпретируется как код на Ruby, а результирующая переменная json затем конвертируется с помощью #to_json.

      -
      json = { :foo => 'bar' }
      -json[:baz] = key
      -
      -

      Опции :callback и :variable используются для "декорирования" итогового + +

      +
      +
      json = { :foo => 'bar' }
      +json[:baz] = key
      +
      +
      +
      + +

      Опции :callback и :variable используются для “декорирования” итогового объекта.

      -
      var resource = {"foo":"bar","baz":"qux"}; present(resource);
      -
      + +
      +
      +
      var resource = {"foo":"bar","baz":"qux"}; present(resource);
      +
      +
      +
      +

      WLang шаблоны

      @@ -890,17 +1110,29 @@

      Доступ к переменным в шаблонах

      Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов. Переменные экземпляра, установленные в процессе обработки маршрутов, будут доступны напрямую в шаблонах:

      -
      get '/:id' do
      -  @foo = Foo.find(params[:id])
      -  haml '%h1= @foo.name'
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  @foo = Foo.find(params[:id])
      +  haml '%h1= @foo.name'
      +end
      +
      +
      +
      +

      Либо установите их через хеш локальных переменных:

      -
      get '/:id' do
      -  foo = Foo.find(params[:id])
      -  haml '%h1= bar.name', :locals => { :bar => foo }
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  foo = Foo.find(params[:id])
      +  haml '%h1= bar.name', :locals => { :bar => foo }
      +end
      +
      +
      +
      +

      Это обычный подход, когда шаблоны рендерятся как части других шаблонов.

      @@ -910,49 +1142,73 @@

      Шаблоны с yield и вложенные раскладк yield. Такой шаблон может быть либо использован с помощью опции :template, как описано выше, либо он может быть дополнен блоком:

      -
          erb :post, :layout => false do
      -      erb :index
      -    end
      -
      + +
      +
      +
          erb :post, :layout => false do
      +      erb :index
      +    end
      +
      +
      +
      +

      Эти инструкции в основном эквивалентны erb :index, :layout => :post.

      Передача блоков интерпретирующим шаблоны методам наиболее полезна для создания вложенных раскладок:

      -
          erb :main_layout, :layout => false do
      -      erb :admin_layout do
      -        erb :user
      -      end
      -    end
      -
      + +
      +
      +
          erb :main_layout, :layout => false do
      +      erb :admin_layout do
      +        erb :user
      +      end
      +    end
      +
      +
      +
      +

      Это же самое может быть сделано короче:

      -
          erb :admin_layout, :layout => :main_layout do
      -      erb :user
      -    end
      -
      + +
      +
      +
          erb :admin_layout, :layout => :main_layout do
      +      erb :user
      +    end
      +
      +
      +
      +

      В настоящее время, следующие интерпретирубщие шаблоны методы принимают блок: -erb, haml, liquid, slim, wlang. +erb, haml, liquid, slim , wlang. Общий метод заполнения шаблонов render также принимает блок.

      Включённые шаблоны

      Шаблоны также могут быть определены в конце исходного файла:

      -
      require 'sinatra'
       
      -get '/' do
      -  haml :index
      -end
      +
      +
      +
      require 'sinatra'
      +
      +get '/' do
      +  haml :index
      +end
       
      -__END__
      +__END__
       
       @@ layout
       %html
         = yield
       
       @@ index
      -%div.title Hello world.
      -
      +%div.title Hello world. +
      + + +

      Заметьте: включённые шаблоны, определенные в исходном файле, который подключил Sinatra, будут загружены автоматически. Вызовите enable :inline_templates напрямую, если используете включённые шаблоны в других файлах.

      @@ -961,49 +1217,73 @@

      Включённые шаблоны

      Именованные шаблоны

      Шаблоны также могут быть определены при помощи template метода:

      -
      template :layout do
      -  "%html\n  =yield\n"
      -end
      -
      -template :index do
      -  '%div.title Hello World!'
      -end
      -
      -get '/' do
      -  haml :index
      -end
      -
      -

      Если шаблон с именем "layout" существует, то он будет использоваться каждый + +

      +
      +
      template :layout do
      +  "%html\n  =yield\n"
      +end
      +
      +template :index do
      +  '%div.title Hello World!'
      +end
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      + +

      Если шаблон с именем “layout” существует, то он будет использоваться каждый раз при рендеринге. Вы можете отключать лэйаут в каждом конкретном случае с помощью :layout => false или отключить его для всего приложения: set :haml, :layout => false:

      -
      get '/' do
      -  haml :index, :layout => !request.xhr?
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml :index, :layout => !request.xhr?
      +end
      +
      +
      +
      +

      Привязка файловых расширений

      Чтобы связать расширение файла с движком рендеринга, используйте Tilt.register. Например, если вы хотите использовать расширение tt для шаблонов Textile:

      -
      Tilt.register :tt, Tilt[:textile]
      -
      + +
      +
      +
      Tilt.register :tt, Tilt[:textile]
      +
      +
      +
      +

      Добавление собственного движка рендеринга

      Сначала зарегистрируйте свой движок в Tilt, а затем создайте метод, отвечающий за рендеринг:

      -
      Tilt.register :myat, MyAwesomeTemplateEngine
       
      -helpers do
      -  def myat(*args) render(:myat, *args) end
      -end
      +
      +
      +
      Tilt.register :myat, MyAwesomeTemplateEngine
      +
      +helpers do
      +  def myat(*args) render(:myat, *args) end
      +end
      +
      +get '/' do
      +  myat :index
      +end
      +
      +
      +
      -get '/' do - myat :index -end -

      Отобразит ./views/index.myat. Чтобы узнать больше о Tilt, смотрите https://github.com/rtomayko/tilt

      @@ -1013,73 +1293,109 @@

      Фильтры

      before-фильтры выполняются перед каждым запросом в том же контексте, что и маршруты, и могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах:

      -
      before do
      -  @note = 'Hi!'
      -  request.path_info = '/foo/bar/baz'
      -end
      -
      -get '/foo/*' do
      -  @note #=> 'Hi!'
      -  params[:splat] #=> 'bar/baz'
      -end
      -
      + +
      +
      +
      before do
      +  @note = 'Hi!'
      +  request.path_info = '/foo/bar/baz'
      +end
      +
      +get '/foo/*' do
      +  @note #=> 'Hi!'
      +  params[:splat] #=> 'bar/baz'
      +end
      +
      +
      +
      +

      after-фильтры выполняются после каждого запроса в том же контексте и могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в before-фильтрах и маршрутах, будут доступны в after-фильтрах:

      -
      after do
      -  puts response.status
      -end
      -
      + +
      +
      +
      after do
      +  puts response.status
      +end
      +
      +
      +
      +

      Заметьте: если вы используете метод body, а не просто возвращаете строку из маршрута, то тело ответа не будет доступно в after-фильтрах, так как оно будет сгенерировано позднее.

      Фильтры могут использовать шаблоны URL и будут интерпретированы, только если путь запроса совпадет с этим шаблоном:

      -
      before '/protected/*' do
      -  authenticate!
      -end
      -
      -after '/create/:slug' do |slug|
      -  session[:last_slug] = slug
      -end
      -
      + +
      +
      +
      before '/protected/*' do
      +  authenticate!
      +end
      +
      +after '/create/:slug' do |slug|
      +  session[:last_slug] = slug
      +end
      +
      +
      +
      +

      Как и маршруты, фильтры могут использовать условия:

      -
      before :agent => /Songbird/ do
      -  # ...
      -end
      -
      -after '/blog/*', :host_name => 'example.com' do
      -  # ...
      -end
      -
      + +
      +
      +
      before :agent => /Songbird/ do
      +  # ...
      +end
      +
      +after '/blog/*', :host_name => 'example.com' do
      +  # ...
      +end
      +
      +
      +
      +

      Методы-помощники

      Используйте метод helpers, чтобы определить методы-помощники, которые в дальнейшем можно будет использовать в обработчиках маршрутов и шаблонах:

      -
      helpers do
      -  def bar(name)
      -    "#{name}bar"
      -  end
      -end
      -
      -get '/:name' do
      -  bar(params[:name])
      -end
      -
      + +
      +
      +
      helpers do
      +  def bar(name)
      +    "#{name}bar"
      +  end
      +end
      +
      +get '/:name' do
      +  bar(params[:name])
      +end
      +
      +
      +
      +

      Также методы-помощники могут быть заданы в отдельных модулях:

      -
      module FooUtils
      -  def foo(name) "#{name}foo" end
      -end
       
      -module BarUtils
      -  def bar(name) "#{name}bar" end
      -end
      +
      +
      +
      module FooUtils
      +  def foo(name) "#{name}foo" end
      +end
      +
      +module BarUtils
      +  def bar(name) "#{name}bar" end
      +end
      +
      +helpers FooUtils, BarUtils
      +
      +
      +
      -helpers FooUtils, BarUtils -

      Эффект равносилен включению модулей в класс приложения.

      @@ -1087,79 +1403,145 @@

      Использование сессий

      Сессия используется, чтобы сохранять состояние между запросами. Если эта опция включена, то у вас будет один хеш сессии на одну пользовательскую сессию:

      -
      enable :sessions
       
      -get '/' do
      -  "value = " << session[:value].inspect
      -end
      +
      +
      +
      enable :sessions
      +
      +get '/' do
      +  "value = " << session[:value].inspect
      +end
      +
      +get '/:value' do
      +  session[:value] = params[:value]
      +end
      +
      +
      +
      -get '/:value' do - session[:value] = params[:value] -end -

      Заметьте, что при использовании enable :sessions все данные сохраняются в куках (cookies). Это может быть не совсем то, что вы хотите (например, сохранение больших объемов данных увеличит ваш трафик). В таком случае вы -можете использовать альтернативную Rack "прослойку" (middleware), реализующую +можете использовать альтернативную Rack “прослойку” (middleware), реализующую механизм сессий. Для этого не надо вызывать enable :sessions, вместо этого -следует подключить ее так же, как и любую другую "прослойку":

      -
      use Rack::Session::Pool, :expire_after => 2592000
      +следует подключить ее так же, как и любую другую “прослойку”:

      + +
      +
      +
      use Rack::Session::Pool, :expire_after => 2592000
       
      -get '/' do
      -  "value = " << session[:value].inspect
      -end
      +get '/' do
      +  "value = " << session[:value].inspect
      +end
      +
      +get '/:value' do
      +  session[:value] = params[:value]
      +end
      +
      +
      +
      -get '/:value' do - session[:value] = params[:value] -end -

      Для повышения безопасности данные сессии в куках подписываются секретным ключом. Секретный ключ генерируется Sinatra. Тем не менее, так как этот ключ будет меняться с каждым запуском приложения, вы, возможно, захотите установить ключ вручную, чтобы у всех экземпляров вашего приложения был один и тот же ключ:

      -
      set :session_secret, 'super secret'
      -
      + +
      +
      +
      set :session_secret, 'super secret'
      +
      +
      +
      +

      Если вы хотите больше настроек для сессий, вы можете задать их, передав хеш опций в параметр sessions:

      -
      set :sessions, :domain => 'foo.com'
      -
      + +
      +
      +
      set :sessions, :domain => 'foo.com'
      +
      +
      +
      +

      Прерывание

      Чтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута, используйте:

      -
      halt
      -
      + +
      +
      +
      halt
      +
      +
      +
      +

      Можно также указать статус при прерывании:

      -
      halt 410
      -
      + +
      +
      +
      halt 410
      +
      +
      +
      +

      Тело:

      -
      halt 'this will be the body'
      -
      -

      И то, и другое:

      -
      halt 401, 'go away!'
      -
      -

      Можно указать заголовки:

      -
      halt 402, {'Content-Type' => 'text/plain'}, 'revenge'
      -
      -

      И, конечно, можно использовать шаблоны с halt:

      -
      halt erb(:error)
      -
      - -

      Передача

      -

      Маршрут может передать обработку запроса следующему совпадающему маршруту, -используя pass:

      -
      get '/guess/:who' do
      -  pass unless params[:who] == 'Frank'
      -  'You got me!'
      -end
      -
      -get '/guess/*' do
      -  'You missed!'
      -end
      -
      +
      +
      +
      halt 'this will be the body'
      +
      +
      +
      + +

      И то, и другое:

      + +
      +
      +
      halt 401, 'go away!'
      +
      +
      +
      + +

      Можно указать заголовки:

      + +
      +
      +
      halt 402, {'Content-Type' => 'text/plain'}, 'revenge'
      +
      +
      +
      + +

      И, конечно, можно использовать шаблоны с halt:

      + +
      +
      +
      halt erb(:error)
      +
      +
      +
      + + +

      Передача

      + +

      Маршрут может передать обработку запроса следующему совпадающему маршруту, +используя pass:

      + +
      +
      +
      get '/guess/:who' do
      +  pass unless params[:who] == 'Frank'
      +  'You got me!'
      +end
      +
      +get '/guess/*' do
      +  'You missed!'
      +end
      +
      +
      +
      +

      Блок маршрута сразу же прерывается, и контроль переходит к следующему совпадающему маршруту. Если соответствующий маршрут не найден, то ответом на запрос будет 404.

      @@ -1169,15 +1551,21 @@

      Вызов другого маршрута

      Иногда pass не подходит, например, если вы хотите получить результат вызова другого обработчика маршрута. В таком случае просто используйте call:

      -
      get '/foo' do
      -  status, headers, body = call env.merge("PATH_INFO" => '/bar')
      -  [status, headers, body.map(&:upcase)]
      -end
      -
      -get '/bar' do
      -  "bar"
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  status, headers, body = call env.merge("PATH_INFO" => '/bar')
      +  [status, headers, body.map(&:upcase)]
      +end
      +
      +get '/bar' do
      +  "bar"
      +end
      +
      +
      +
      +

      Заметьте, что в предыдущем примере можно облегчить тестирование и повысить производительность, перенеся "bar" в метод-помощник, используемый и в /foo, и в /bar.

      @@ -1196,27 +1584,39 @@

      Задание тела, кода и заголовков ответа

      потока исполнения. Вы можете сделать это с помощью метода-помощника body. Если вы задействуете метод body, то вы можете использовать его и в дальнейшем, чтобы получить доступ к телу ответа.

      -
      get '/foo' do
      -  body "bar"
      -end
      -
      -after do
      -  puts body
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  body "bar"
      +end
      +
      +after do
      +  puts body
      +end
      +
      +
      +
      +

      Также можно передать блок в метод body, который затем будет вызван обработчиком Rack (такой подход может быть использован для реализации -поточного ответа, см. "Возвращаемые значения").

      +поточного ответа, см. “Возвращаемые значения”).

      Аналогично вы можете установить код ответа и его заголовки:

      -
      get '/foo' do
      -  status 418
      -  headers \
      -    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
      -    "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
      -  body "I'm a tea pot!"
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  status 418
      +  headers \
      +    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
      +    "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
      +  body "I'm a tea pot!"
      +end
      +
      +
      +
      +

      Как и body, методы headers и status, вызванные без аргументов, возвращают свои текущие значения.

      @@ -1226,17 +1626,23 @@

      Стриминг ответов

      Иногда требуется начать отправлять данные клиенту прямо в процессе генерирования частей этих данных. В особых случаях требуется постоянно отправлять данные до тех пор, пока клиент не закроет соединение. Вы можете -использовать метод stream вместо написания собственных "оберток".

      -
      get '/' do
      -  stream do |out|
      -    out << "It's gonna be legen -\n"
      -    sleep 0.5
      -    out << " (wait for it) \n"
      -    sleep 1
      -    out << "- dary!\n"
      -  end
      -end
      -
      +использовать метод stream вместо написания собственных “оберток”.

      + +
      +
      +
      get '/' do
      +  stream do |out|
      +    out << "It's gonna be legen -\n"
      +    sleep 0.5
      +    out << " (wait for it) \n"
      +    sleep 1
      +    out << "- dary!\n"
      +  end
      +end
      +
      +
      +
      +

      Что позволяет вам реализовать стриминговые API, Server Sent Events, и может служить основой для WebSockets. @@ -1254,45 +1660,57 @@

      Стриминг ответов

      close у объекта потока, что позволит вам закрыть его позже в любом другом месте. Это работает только с событийными серверами, например, с Thin и Rainbows. Другие же серверы все равно будут закрывать поток:

      -
      # long polling
       
      -set :server, :thin
      -connections = []
      +
      +
      +
      # long polling
      +
      +set :server, :thin
      +connections = []
      +
      +get '/subscribe' do
      +  # регистрация клиента
      +  stream(:keep_open) { |out| connections << out }
       
      -get '/subscribe' do
      -  # регистрация клиента
      -  stream(:keep_open) { |out| connections << out }
      +  # удаление "мертвых клиентов"
      +  connections.reject!(&:closed?)
       
      -  # удаление "мертвых клиентов"
      -  connections.reject!(&:closed?)
      +  # допуск
      +  "subscribed"
      +end
       
      -  # допуск
      -  "subscribed"
      -end
      +post '/message' do
      +  connections.each do |out|
      +    # уведомить клиента о новом сообщении
      +    out << params[:message] << "\n"
       
      -post '/message' do
      -  connections.each do |out|
      -    # уведомить клиента о новом сообщении
      -    out << params[:message] << "\n"
      +    # указать клиенту на необходимость снова соединиться
      +    out.close
      +  end
       
      -    # указать клиенту на необходимость снова соединиться
      -    out.close
      -  end
      +  # допуск
      +  "message received"
      +end
      +
      +
      +
      - # допуск - "message received" -end -

      Логирование

      В области видимости запроса метод logger предоставляет доступ к экземпляру Logger:

      -
      get '/' do
      -  logger.info "loading data"
      -  # ...
      -end
      -
      + +
      +
      +
      get '/' do
      +  logger.info "loading data"
      +  # ...
      +end
      +
      +
      +
      +

      Этот логер автоматически учитывает ваши настройки логирования в Rack. Если логирование выключено, то этот метод вернет пустой (dummy) объект, поэтому вы можете смело использовать его в маршрутах и фильтрах.

      @@ -1300,13 +1718,19 @@

      Логирование

      Заметьте, что логирование включено по умолчанию только для Sinatra::Application, а если ваше приложение — подкласс Sinatra::Base, то вы, наверное, захотите включить его вручную:

      -
      class MyApp < Sinatra::Base
      -  configure :production, :development do
      -    enable :logging
      -  end
      -end
      -
      -

      Чтобы избежать использования любой логирующей "прослойки", задайте опции + +

      +
      +
      class MyApp < Sinatra::Base
      +  configure :production, :development do
      +    enable :logging
      +  end
      +end
      +
      +
      +
      + +

      Чтобы избежать использования любой логирующей “прослойки”, задайте опции logging значение nil. Тем не менее, не забывайте, что в такой ситуации logger вернет nil. Чаще всего так делают, когда задают свой собственный логер. Sinatra будет использовать то, что находится в env['rack.logger'].

      @@ -1317,22 +1741,40 @@

      Mime-типы

      Когда вы используете send_file или статические файлы, у вас могут быть mime-типы, которые Sinatra не понимает по умолчанию. Используйте mime_type для их регистрации по расширению файла:

      -
      configure do
      -  mime_type :foo, 'text/foo'
      -end
      -
      + +
      +
      +
      configure do
      +  mime_type :foo, 'text/foo'
      +end
      +
      +
      +
      +

      Вы также можете использовать это в content_type методе-помощнике:

      -
      get '/' do
      -  content_type :foo
      -  "foo foo foo"
      -end
      -
      + +
      +
      +
      get '/' do
      +  content_type :foo
      +  "foo foo foo"
      +end
      +
      +
      +
      +

      Генерирование URL

      Чтобы сформировать URL, вам следует использовать метод url, например, в Haml:

      -
      %a{:href => url('/foo')} foo
      -
      + +
      +
      +
      %a{:href => url('/foo')} foo
      +
      +
      +
      +

      Этот метод учитывает обратные прокси и маршрутизаторы Rack, если они присутствуют.

      @@ -1342,93 +1784,159 @@

      Генерирование URL

      Перенаправление (редирект)

      Вы можете перенаправить браузер пользователя с помощью метода redirect:

      -
      get '/foo' do
      -  redirect to('/bar')
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  redirect to('/bar')
      +end
      +
      +
      +
      +

      Любые дополнительные параметры используются по аналогии с аргументами метода halt:

      -
      redirect to('/bar'), 303
      -redirect 'http://google.com', 'wrong place, buddy'
      -
      + +
      +
      +
      redirect to('/bar'), 303
      +redirect 'http://google.com', 'wrong place, buddy'
      +
      +
      +
      +

      Вы также можете перенаправить пользователя обратно, на страницу, с которой он пришел, с помощью redirect back:

      -
      get '/foo' do
      -  "<a href='/bar'>do something</a>"
      -end
      -
      -get '/bar' do
      -  do_something
      -  redirect back
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  "<a href='/bar'>do something</a>"
      +end
      +
      +get '/bar' do
      +  do_something
      +  redirect back
      +end
      +
      +
      +
      +

      Чтобы передать какие-либо параметры вместе с перенаправлением, либо добавьте их в строку запроса:

      -
      redirect to('/bar?sum=42')
      -
      + +
      +
      +
      redirect to('/bar?sum=42')
      +
      +
      +
      +

      либо используйте сессию:

      -
      enable :sessions
       
      -get '/foo' do
      -  session[:secret] = 'foo'
      -  redirect to('/bar')
      -end
      +
      +
      +
      enable :sessions
      +
      +get '/foo' do
      +  session[:secret] = 'foo'
      +  redirect to('/bar')
      +end
      +
      +get '/bar' do
      +  session[:secret]
      +end
      +
      +
      +
      -get '/bar' do - session[:secret] -end -

      Управление кэшированием

      Установка корректных заголовков — основа правильного HTTP кэширования.

      Вы можете легко выставить заголовок Cache-Control таким образом:

      -
      get '/' do
      -  cache_control :public
      -  "cache it!"
      -end
      -
      + +
      +
      +
      get '/' do
      +  cache_control :public
      +  "cache it!"
      +end
      +
      +
      +
      +

      Совет: задавайте кэширование в before-фильтре:

      -
      before do
      -  cache_control :public, :must_revalidate, :max_age => 60
      -end
      -
      + +
      +
      +
      before do
      +  cache_control :public, :must_revalidate, :max_age => 60
      +end
      +
      +
      +
      +

      Если вы используете метод expires для задания соответствующего заголовка, то Cache-Control будет выставлен автоматически:

      -
      before do
      -  expires 500, :public, :must_revalidate
      -end
      -
      + +
      +
      +
      before do
      +  expires 500, :public, :must_revalidate
      +end
      +
      +
      +
      +

      Чтобы как следует использовать кэширование, вам следует подумать об использовании etag или last_modified. Рекомендуется использовать эти методы-помощники до выполнения ресурсоемких вычислений, так как они немедленно отправят ответ клиенту, если текущая версия уже есть в их кэше:

      -
      get '/article/:id' do
      -  @article = Article.find params[:id]
      -  last_modified @article.updated_at
      -  etag @article.sha1
      -  erb :article
      -end
      -
      + +
      +
      +
      get '/article/:id' do
      +  @article = Article.find params[:id]
      +  last_modified @article.updated_at
      +  etag @article.sha1
      +  erb :article
      +end
      +
      +
      +
      +

      Также вы можете использовать weak ETag:

      -
      etag @article.sha1, :weak
      -
      + +
      +
      +
      etag @article.sha1, :weak
      +
      +
      +
      +

      Эти методы-помощники не станут ничего кэшировать для вас, но они дадут необходимую информацию для вашего кэша. Если вы ищете легкое решение для кэширования, попробуйте rack-cache:

      -
      require 'rack/cache'
      -require 'sinatra'
       
      -use Rack::Cache
      +
      +
      +
      require 'rack/cache'
      +require 'sinatra'
      +
      +use Rack::Cache
      +
      +get '/' do
      +  cache_control :public, :max_age => 36000
      +  sleep 5
      +  "hello"
      +end
      +
      +
      +
      -get '/' do - cache_control :public, :max_age => 36000 - sleep 5 - "hello" -end -

      Используйте опцию :static_cache_control (см. ниже), чтобы добавить заголовок Cache-Control к статическим файлам.

      @@ -1439,26 +1947,50 @@

      Управление кэшированием

      методов, уже существуют, а остальные ресурсы (к которым обращаются, например, с помощью POST) считает новыми. Вы можете изменить данное поведение с помощью опции :new_resource:

      -
      get '/create' do
      -  etag '', :new_resource => true
      -  Article.create
      -  erb :new_article
      -end
      -
      + +
      +
      +
      get '/create' do
      +  etag '', :new_resource => true
      +  Article.create
      +  erb :new_article
      +end
      +
      +
      +
      +

      Если вы хотите использовать weak ETag, задайте опцию :kind:

      -
      etag '', :new_resource => true, :kind => :weak
      -
      + +
      +
      +
      etag '', :new_resource => true, :kind => :weak
      +
      +
      +
      +

      Отправка файлов

      Для отправки файлов пользователю вы можете использовать метод send_file:

      -
      get '/' do
      -  send_file 'foo.png'
      -end
      -
      + +
      +
      +
      get '/' do
      +  send_file 'foo.png'
      +end
      +
      +
      +
      +

      Этот метод имеет несколько опций:

      -
      send_file 'foo.png', :type => :jpg
      -
      + +
      +
      +
      send_file 'foo.png', :type => :jpg
      +
      +
      +
      +

      Возможные опции:

      @@ -1493,131 +2025,191 @@

      Доступ к объекту запроса

      Объект входящего запроса доступен на уровне обработки запроса (в фильтрах, маршрутах, обработчиках ошибок) с помощью request метода:

      -
      # приложение запущено на http://example.com/example
      -get '/foo' do
      -  t = %w[text/css text/html application/javascript]
      -  request.accept              # ['text/html', '*/*']
      -  request.accept? 'text/xml'  # true
      -  request.preferred_type(t)   # 'text/html'
      -  request.body                # тело запроса, посланное клиентом (см. ниже)
      -  request.scheme              # "http"
      -  request.script_name         # "/example"
      -  request.path_info           # "/foo"
      -  request.port                # 80
      -  request.request_method      # "GET"
      -  request.query_string        # ""
      -  request.content_length      # длина тела запроса
      -  request.media_type          # медиатип тела запроса
      -  request.host                # "example.com"
      -  request.get?                # true (есть аналоги для других методов HTTP)
      -  request.form_data?          # false
      -  request["some_param"]       # значение параметра some_param. Шорткат для хеша params
      -  request.referrer            # источник запроса клиента либо '/'
      -  request.user_agent          # user agent (используется для :agent условия)
      -  request.cookies             # хеш, содержащий cookies браузера
      -  request.xhr?                # является ли запрос ajax запросом?
      -  request.url                 # "http://example.com/example/foo"
      -  request.path                # "/example/foo"
      -  request.ip                  # IP-адрес клиента
      -  request.secure?             # false (true, если запрос сделан через SSL)
      -  request.forwarded?          # true (если сервер работает за обратным прокси)
      -  request.env                 # "сырой" env хеш, полученный Rack
      -end
      -
      + +
      +
      +
      # приложение запущено на http://example.com/example
      +get '/foo' do
      +  t = %w[text/css text/html application/javascript]
      +  request.accept              # ['text/html', '*/*']
      +  request.accept? 'text/xml'  # true
      +  request.preferred_type(t)   # 'text/html'
      +  request.body                # тело запроса, посланное клиентом (см. ниже)
      +  request.scheme              # "http"
      +  request.script_name         # "/example"
      +  request.path_info           # "/foo"
      +  request.port                # 80
      +  request.request_method      # "GET"
      +  request.query_string        # ""
      +  request.content_length      # длина тела запроса
      +  request.media_type          # медиатип тела запроса
      +  request.host                # "example.com"
      +  request.get?                # true (есть аналоги для других методов HTTP)
      +  request.form_data?          # false
      +  request["some_param"]       # значение параметра some_param. Шорткат для хеша params
      +  request.referrer            # источник запроса клиента либо '/'
      +  request.user_agent          # user agent (используется для :agent условия)
      +  request.cookies             # хеш, содержащий cookies браузера
      +  request.xhr?                # является ли запрос ajax запросом?
      +  request.url                 # "http://example.com/example/foo"
      +  request.path                # "/example/foo"
      +  request.ip                  # IP-адрес клиента
      +  request.secure?             # false (true, если запрос сделан через SSL)
      +  request.forwarded?          # true (если сервер работает за обратным прокси)
      +  request.env                 # "сырой" env хеш, полученный Rack
      +end
      +
      +
      +
      +

      Некоторые опции, такие как script_name или path_info, доступны для изменения:

      -
      before { request.path_info = "/" }
       
      -get "/" do
      -  "all requests end up here"
      -end
      -
      +
      +
      +
      before { request.path_info = "/" }
      +
      +get "/" do
      +  "all requests end up here"
      +end
      +
      +
      +
      +

      request.body является IO или StringIO объектом:

      -
      post "/api" do
      -  request.body.rewind  # в случае, если кто-то уже прочитал тело запроса
      -  data = JSON.parse request.body.read
      -  "Hello #{data['name']}!"
      -end
      -
      + +
      +
      +
      post "/api" do
      +  request.body.rewind  # в случае, если кто-то уже прочитал тело запроса
      +  data = JSON.parse request.body.read
      +  "Hello #{data['name']}!"
      +end
      +
      +
      +
      +

      Вложения

      Вы можете использовать метод attachment, чтобы сказать браузеру, что ответ сервера должен быть сохранен на диск, а не отображен:

      -
      get '/' do
      -  attachment
      -  "store it!"
      -end
      -
      + +
      +
      +
      get '/' do
      +  attachment
      +  "store it!"
      +end
      +
      +
      +
      +

      Вы также можете указать имя файла:

      -
      get '/' do
      -  attachment "info.txt"
      -  "store it!"
      -end
      -
      + +
      +
      +
      get '/' do
      +  attachment "info.txt"
      +  "store it!"
      +end
      +
      +
      +
      +

      Работа со временем и датами

      Sinatra предлагает метод-помощник time_for, который из заданного значения создает объект Time. Он также может конвертировать DateTime, Date и подобные классы:

      -
      get '/' do
      -  pass if Time.now > time_for('Dec 23, 2012')
      -  "still time"
      -end
      -
      + +
      +
      +
      get '/' do
      +  pass if Time.now > time_for('Dec 23, 2012')
      +  "still time"
      +end
      +
      +
      +
      +

      Этот метод используется внутри Sinatra методами expires, last_modified и им подобными. Поэтому вы легко можете расширить функционал этих методов, переопределив time_for в своем приложении:

      -
      helpers do
      -  def time_for(value)
      -    case value
      -    when :yesterday then Time.now - 24*60*60
      -    when :tomorrow  then Time.now + 24*60*60
      -    else super
      -    end
      -  end
      -end
      -
      -get '/' do
      -  last_modified :yesterday
      -  expires :tomorrow
      -  "hello"
      -end
      -
      + +
      +
      +
      helpers do
      +  def time_for(value)
      +    case value
      +    when :yesterday then Time.now - 24*60*60
      +    when :tomorrow  then Time.now + 24*60*60
      +    else super
      +    end
      +  end
      +end
      +
      +get '/' do
      +  last_modified :yesterday
      +  expires :tomorrow
      +  "hello"
      +end
      +
      +
      +
      +

      Поиск шаблонов

      Для поиска шаблонов и их последующего рендеринга используется метод find_template:

      -
      find_template settings.views, 'foo', Tilt[:haml] do |file|
      -  puts "could be #{file}"
      -end
      -
      + +
      +
      +
      find_template settings.views, 'foo', Tilt[:haml] do |file|
      +  puts "could be #{file}"
      +end
      +
      +
      +
      +

      Это не слишком полезный пример. Зато полезен тот факт, что вы можете переопределить этот метод, чтобы использовать свой собственный механизм поиска. Например, если вы хотите, чтобы можно было использовать несколько директорий с шаблонами:

      -
      set :views, ['views', 'templates']
      -
      -helpers do
      -  def find_template(views, name, engine, &block)
      -    Array(views).each { |v| super(v, name, engine, &block) }
      -  end
      -end
      -
      + +
      +
      +
      set :views, ['views', 'templates']
      +
      +helpers do
      +  def find_template(views, name, engine, &block)
      +    Array(views).each { |v| super(v, name, engine, &block) }
      +  end
      +end
      +
      +
      +
      +

      Другой пример, в котором используются разные директории для движков рендеринга:

      -
      set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
      -
      -helpers do
      -  def find_template(views, name, engine, &block)
      -    _, folder = views.detect { |k,v| engine == Tilt[k] }
      -    folder ||= views[:default]
      -    super(folder, name, engine, &block)
      -  end
      -end
      -
      + +
      +
      +
      set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
      +
      +helpers do
      +  def find_template(views, name, engine, &block)
      +    _, folder = views.detect { |k,v| engine == Tilt[k] }
      +    folder ||= views[:default]
      +    super(folder, name, engine, &block)
      +  end
      +end
      +
      +
      +
      +

      Вы можете легко вынести этот код в расширение и поделиться им с остальными!

      Заметьте, что find_template не проверяет, существует ли файл на самом деле, @@ -1626,51 +2218,75 @@

      Поиск шаблонов

      не будет найден. Содержимое и местонахождение шаблонов будет закэшировано, если приложение запущено не в режиме разработки (set :environment, :development). Вы должны помнить об этих нюансах, если пишите по-настоящему -"сумасшедший" метод.

      +“сумасшедший” метод.

      Конфигурация

      Этот блок исполняется один раз при старте в любом окружении, режиме (environment):

      -
      configure do
      -  # задание одной опции
      -  set :option, 'value'
       
      -  # устанавливаем несколько опций
      -  set :a => 1, :b => 2
      +
      +
      +
      configure do
      +  # задание одной опции
      +  set :option, 'value'
       
      -  # то же самое, что и `set :option, true`
      -  enable :option
      +  # устанавливаем несколько опций
      +  set :a => 1, :b => 2
       
      -  # то же самое, что и `set :option, false`
      -  disable :option
      +  # то же самое, что и `set :option, true`
      +  enable :option
      +
      +  # то же самое, что и `set :option, false`
      +  disable :option
      +
      +  # у вас могут быть "динамические" опции с блоками
      +  set(:css_dir) { File.join(views, 'css') }
      +end
      +
      +
      +
      - # у вас могут быть "динамические" опции с блоками - set(:css_dir) { File.join(views, 'css') } -end -

      Будет запущено, когда окружение (RACK_ENV переменная) :production:

      -
      configure :production do
      -  ...
      -end
      -
      + +
      +
      +
      configure :production do
      +  ...
      +end
      +
      +
      +
      +

      Будет запущено, когда окружение :production или :test:

      -
      configure :production, :test do
      -  ...
      -end
      -
      + +
      +
      +
      configure :production, :test do
      +  ...
      +end
      +
      +
      +
      +

      Вы можете получить доступ к этим опциям с помощью settings:

      -
      configure do
      -  set :foo, 'bar'
      -end
      -
      -get '/' do
      -  settings.foo? # => true
      -  settings.foo  # => 'bar'
      -  ...
      -end
      -
      + +
      +
      +
      configure do
      +  set :foo, 'bar'
      +end
      +
      +get '/' do
      +  settings.foo? # => true
      +  settings.foo  # => 'bar'
      +  ...
      +end
      +
      +
      +
      +

      Настройка защиты от атак

      @@ -1678,15 +2294,33 @@

      Настройка защиты от атак

      Rack::Protection для защиты приложения от простых атак. Вы можете легко выключить эту защиту (что сделает ваше приложение чрезвычайно уязвимым):

      -
      disable :protection
      -
      + +
      +
      +
      disable :protection
      +
      +
      +
      +

      Чтобы пропустить какой-либо уровень защиты, передайте хеш опций в параметр protection:

      -
      set :protection, :except => :path_traversal
      -
      + +
      +
      +
      set :protection, :except => :path_traversal
      +
      +
      +
      +

      Вы также можете отключить сразу несколько уровней защиты:

      -
      set :protection, :except => [:path_traversal, :session_hijacking]
      -
      + +
      +
      +
      set :protection, :except => [:path_traversal, :session_hijacking]
      +
      +
      +
      +

      Доступные настройки

      @@ -1865,8 +2499,10 @@

      Режим, окружение

      "production" и "test" шаблоны по умолчанию кэшируются.

      Для запуска приложения в определенном окружении используйте ключ -e

      -
      ruby my_app.rb -e [ENVIRONMENT]
      -
      + +
      ruby my_app.rb -e [ENVIRONMENT]
      +
      +

      Вы можете использовать предопределенные методы development?, test? и +production?, чтобы определить текущее окружение.

      @@ -1882,80 +2518,130 @@

      Not Found

      Когда выброшено исключение Sinatra::NotFound, или кодом ответа является 404, то будет вызван not_found обработчик:

      -
      not_found do
      -  'This is nowhere to be found.'
      -end
      -
      + +
      +
      +
      not_found do
      +  'This is nowhere to be found.'
      +end
      +
      +
      +
      +

      Ошибки

      Обработчик ошибок error будет вызван, когда исключение выброшено из блока маршрута, либо из фильтра. Объект-исключение доступен как переменная sinatra.error в Rack:

      -
      error do
      -  'Sorry there was a nasty error - ' + env['sinatra.error'].name
      -end
      -
      + +
      +
      +
      error do
      +  'Sorry there was a nasty error - ' + env['sinatra.error'].name
      +end
      +
      +
      +
      +

      Конкретные ошибки:

      -
      error MyCustomError do
      -  'So what happened was...' + env['sinatra.error'].message
      -end
      -
      + +
      +
      +
      error MyCustomError do
      +  'So what happened was...' + env['sinatra.error'].message
      +end
      +
      +
      +
      +

      Тогда, если это произошло:

      -
      get '/' do
      -  raise MyCustomError, 'something bad'
      -end
      -
      + +
      +
      +
      get '/' do
      +  raise MyCustomError, 'something bad'
      +end
      +
      +
      +
      +

      То вы получите:

      -
      So what happened was... something bad
      -
      + +
      So what happened was... something bad
      +
      +

      Также вы можете установить обработчик ошибок для кода состояния HTTP:

      -
      error 403 do
      -  'Access forbidden'
      -end
      -
      -get '/secret' do
      -  403
      -end
      -
      + +
      +
      +
      error 403 do
      +  'Access forbidden'
      +end
      +
      +get '/secret' do
      +  403
      +end
      +
      +
      +
      +

      Либо набора кодов:

      -
      error 400..510 do
      -  'Boom'
      -end
      -
      + +
      +
      +
      error 400..510 do
      +  'Boom'
      +end
      +
      +
      +
      +

      Sinatra устанавливает специальные not_found и error обработчики, когда приложение запущено в режиме разработки (окружение :development).

      - -

      Rack "прослойки"

      + +

      Rack “прослойки”

      Sinatra использует Rack, минимальный стандартный интерфейс для веб-фреймворков на Ruby. Одной из самых интересных для -разработчиков возможностей Rack является поддержка "прослоек" ("middleware") — -компонентов, находящихся "между" сервером и вашим приложением, которые +разработчиков возможностей Rack является поддержка “прослоек” (“middleware”) — +компонентов, находящихся “между” сервером и вашим приложением, которые отслеживают и/или манипулируют HTTP запросами/ответами для предоставления различной функциональности.

      -

      В Sinatra очень просто использовать такие "прослойки" с помощью метода use:

      -
      require 'sinatra'
      -require 'my_custom_middleware'
      +

      В Sinatra очень просто использовать такие “прослойки” с помощью метода use:

      + +
      +
      +
      require 'sinatra'
      +require 'my_custom_middleware'
       
      -use Rack::Lint
      -use MyCustomMiddleware
      +use Rack::Lint
      +use MyCustomMiddleware
      +
      +get '/hello' do
      +  'Hello World'
      +end
      +
      +
      +
      -get '/hello' do - 'Hello World' -end -

      Семантика use идентична той, что определена для Rack::Builder DSL (чаще всего используется в rackup файлах). Например, метод use принимает как множественные переменные, так и блоки:

      -
      use Rack::Auth::Basic do |username, password|
      -  username == 'admin' && password == 'secret'
      -end
      -
      -

      Rack распространяется с различными стандартными "прослойками" для логирования, + +

      +
      +
      use Rack::Auth::Basic do |username, password|
      +  username == 'admin' && password == 'secret'
      +end
      +
      +
      +
      + +

      Rack распространяется с различными стандартными “прослойками” для логирования, отладки, маршрутизации URL, аутентификации, обработки сессий. Sinatra использует многие из этих компонентов автоматически, основываясь на конфигурации, чтобы вам не приходилось подключать (use) их вручную.

      @@ -1973,35 +2659,41 @@

      Тестирование

      фреймворков, поддерживающих тестирование Rack. Rack::Test рекомендован:

      -
      require 'my_sinatra_app'
      -require 'test/unit'
      -require 'rack/test'
      -
      -class MyAppTest < Test::Unit::TestCase
      -  include Rack::Test::Methods
      -
      -  def app
      -    Sinatra::Application
      -  end
      -
      -  def test_my_default
      -    get '/'
      -    assert_equal 'Hello World!', last_response.body
      -  end
      -
      -  def test_with_params
      -    get '/meet', :name => 'Frank'
      -    assert_equal 'Hello Frank!', last_response.body
      -  end
      -
      -  def test_with_rack_env
      -    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      -    assert_equal "You're using Songbird!", last_response.body
      -  end
      -end
      -
      - -

      Sinatra::Base — "прослойки", библиотеки и модульные приложения

      + +
      +
      +
      require 'my_sinatra_app'
      +require 'test/unit'
      +require 'rack/test'
      +
      +class MyAppTest < Test::Unit::TestCase
      +  include Rack::Test::Methods
      +
      +  def app
      +    Sinatra::Application
      +  end
      +
      +  def test_my_default
      +    get '/'
      +    assert_equal 'Hello World!', last_response.body
      +  end
      +
      +  def test_with_params
      +    get '/meet', :name => 'Frank'
      +    assert_equal 'Hello Frank!', last_response.body
      +  end
      +
      +  def test_with_rack_env
      +    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      +    assert_equal "You're using Songbird!", last_response.body
      +  end
      +end
      +
      +
      +
      + + +

      Sinatra::Base — “прослойки”, библиотеки и модульные приложения

      Описание своего приложения самым простейшим способом (с помощью DSL верхнего уровня, классический стиль) отлично работает для крохотных приложений. В таких @@ -2009,19 +2701,25 @@

      Sinatra::Base — "прослойки", библиотеки и модуль (единственный файл приложения, ./public и ./views директории, логирование, страница информации об исключении и т.д.). Тем не менее, такой метод имеет множество недостатков при создании компонентов, таких как Rack middleware -("прослоек"), Rails metal, простых библиотек с серверными компонентами, +(“прослоек”), Rails metal, простых библиотек с серверными компонентами, расширений Sinatra. И тут на помощь приходит Sinatra::Base:

      -
      require 'sinatra/base'
       
      -class MyApp < Sinatra::Base
      -  set :sessions, true
      -  set :foo, 'bar'
      +
      +
      +
      require 'sinatra/base'
      +
      +class MyApp < Sinatra::Base
      +  set :sessions, true
      +  set :foo, 'bar'
      +
      +  get '/' do
      +    'Hello world!'
      +  end
      +end
      +
      +
      +
      - get '/' do - 'Hello world!' - end -end -

      Методы, доступные Sinatra::Base подклассам идентичны тем, что доступны приложениям в DSL верхнего уровня. Большинство таких приложений могут быть конвертированы в Sinatra::Base компоненты с помощью двух модификаций:

      @@ -2030,7 +2728,7 @@

      Sinatra::Base — "прослойки", библиотеки и модуль
    2. Вы должны подключать sinatra/base вместо sinatra, иначе все методы, предоставляемые Sinatra, будут импортированы в глобальное пространство имен.
    3. -
    4. Поместите все маршруты, обработчики ошибок, фильтры и опции в подкласс +
    5. Поместите все маршруты, обработчики ошибок, фильтры и опции в подкласс Sinatra::Base.
    6. Sinatra::Base — это чистый лист. Большинство опций, включая встроенный @@ -2052,7 +2750,8 @@

      Модульные приложения против классически

      Переходя с одного стиля на другой, примите во внимание следующие изменения в настройках:

      -
      Опция               Классический            Модульный
      +
      +
      Опция               Классический            Модульный
       
       app_file            файл с приложением      файл с подклассом Sinatra::Base
       run                 $0 == app_file          false
      @@ -2060,49 +2759,78 @@ 

      Модульные приложения против классически method_override true false inline_templates true false static true false -

      +
      +

      Запуск модульных приложений

      Есть два общепринятых способа запускать модульные приложения: запуск напрямую с помощью run!:

      -
      # my_app.rb
      -require 'sinatra/base'
       
      -class MyApp < Sinatra::Base
      -  # ... здесь код приложения ...
      +
      +
      +
      # my_app.rb
      +require 'sinatra/base'
      +
      +class MyApp < Sinatra::Base
      +  # ... здесь код приложения ...
      +
      +  # запускаем сервер, если исполняется текущий файл
      +  run! if app_file == $0
      +end
      +
      +
      +
      - # запускаем сервер, если исполняется текущий файл - run! if app_file == $0 -end -

      Затем:

      -
      ruby my_app.rb
      -
      + +
      ruby my_app.rb
      +
      +

      Или с помощью конфигурационного файла config.ru, который позволяет использовать любой Rack-совместимый сервер приложений.

      -
      # config.ru
      -require './my_app'
      -run MyApp
      -
      + +
      +
      +
      # config.ru
      +require './my_app'
      +run MyApp
      +
      +
      +
      +

      Запускаем:

      -
      rackup -p 4567
      -
      + +
      rackup -p 4567
      +
      +

      Запуск классических приложений с config.ru

      Файл приложения:

      -
      # app.rb
      -require 'sinatra'
       
      -get '/' do
      -  'Hello world!'
      -end
      -
      +
      +
      +
      # app.rb
      +require 'sinatra'
      +
      +get '/' do
      +  'Hello world!'
      +end
      +
      +
      +
      +

      И соответствующий config.ru:

      -
      require './app'
      -run Sinatra::Application
      -
      + +
      +
      +
      require './app'
      +run Sinatra::Application
      +
      +
      +
      +

      Когда использовать config.ru?

      @@ -2111,90 +2839,114 @@

      Когда использовать config.ru?

      • вы хотите разворачивать свое приложение на различных Rack-совместимых -серверах (Passenger, Unicorn, Heroku, ...);
      • -
      • вы хотите использовать более одного подкласса Sinatra::Base;
      • -
      • вы хотите использовать Sinatra только в качестве "прослойки" Rack.
      • +серверах (Passenger, Unicorn, Heroku, …); +
      • вы хотите использовать более одного подкласса Sinatra::Base;
      • +
      • вы хотите использовать Sinatra только в качестве “прослойки” Rack.

      Совсем необязательно переходить на использование config.ru лишь потому, что вы стали использовать модульный стиль приложения. И необязательно использовать модульный стиль, чтобы запускать приложение с помощью config.ru.

      - -

      Использование Sinatra в качестве "прослойки"

      + +

      Использование Sinatra в качестве “прослойки”

      -

      Не только сама Sinatra может использовать "прослойки" Rack, но и любое Sinatra +

      Не только сама Sinatra может использовать “прослойки” Rack, но и любое Sinatra приложение само может быть добавлено к любому Rack endpoint в качестве -"прослойки". Этим endpoint (конечной точкой) может быть другое Sinatra -приложение, или приложение, основанное на Rack (Rails/Ramaze/Camping/...):

      -
      require 'sinatra/base'
      -
      -class LoginScreen < Sinatra::Base
      -  enable :sessions
      -
      -  get('/login') { haml :login }
      -
      -  post('/login') do
      -    if params[:name] == 'admin' && params[:password] == 'admin'
      -      session['user_name'] = params[:name]
      -    else
      -      redirect '/login'
      -    end
      -  end
      -end
      -
      -class MyApp < Sinatra::Base
      -  # "прослойка" будет запущена перед фильтрами
      -  use LoginScreen
      -
      -  before do
      -    unless session['user_name']
      -      halt "Access denied, please <a href='/login'>login</a>."
      -    end
      -  end
      -
      -  get('/') { "Hello #{session['user_name']}." }
      -end
      -
      - -

      Создание приложений "на лету"

      - -

      Иногда требуется создавать Sinatra приложения "на лету" (например, из другого +“прослойки”. Этим endpoint (конечной точкой) может быть другое Sinatra +приложение, или приложение, основанное на Rack (Rails/Ramaze/Camping/…):

      + +
      +
      +
      require 'sinatra/base'
      +
      +class LoginScreen < Sinatra::Base
      +  enable :sessions
      +
      +  get('/login') { haml :login }
      +
      +  post('/login') do
      +    if params[:name] == 'admin' && params[:password] == 'admin'
      +      session['user_name'] = params[:name]
      +    else
      +      redirect '/login'
      +    end
      +  end
      +end
      +
      +class MyApp < Sinatra::Base
      +  # "прослойка" будет запущена перед фильтрами
      +  use LoginScreen
      +
      +  before do
      +    unless session['user_name']
      +      halt "Access denied, please <a href='/login'>login</a>."
      +    end
      +  end
      +
      +  get('/') { "Hello #{session['user_name']}." }
      +end
      +
      +
      +
      + + +

      Создание приложений “на лету”

      + +

      Иногда требуется создавать Sinatra приложения “на лету” (например, из другого приложения). Это возможно с помощью Sinatra.new:

      -
      require 'sinatra/base'
      -my_app = Sinatra.new { get('/') { "hi" } }
      -my_app.run!
      -
      + +
      +
      +
      require 'sinatra/base'
      +my_app = Sinatra.new { get('/') { "hi" } }
      +my_app.run!
      +
      +
      +
      +

      Этот метод может принимать аргументом приложение, от которого следует наследоваться:

      -
      # config.ru
      -require 'sinatra/base'
      -
      -controller = Sinatra.new do
      -  enable :logging
      -  helpers MyHelpers
      -end
      -
      -map('/a') do
      -  run Sinatra.new(controller) { get('/') { 'a' } }
      -end
      -
      -map('/b') do
      -  run Sinatra.new(controller) { get('/') { 'b' } }
      -end
      -
      + +
      +
      +
      # config.ru
      +require 'sinatra/base'
      +
      +controller = Sinatra.new do
      +  enable :logging
      +  helpers MyHelpers
      +end
      +
      +map('/a') do
      +  run Sinatra.new(controller) { get('/') { 'a' } }
      +end
      +
      +map('/b') do
      +  run Sinatra.new(controller) { get('/') { 'b' } }
      +end
      +
      +
      +
      +

      Это особенно полезно для тестирования расширений Sinatra и при использовании Sinatra внутри вашей библиотеки.

      -

      Благодаря этому, использовать Sinatra как "прослойку" очень просто:

      -
      require 'sinatra/base'
      +

      Благодаря этому, использовать Sinatra как “прослойку” очень просто:

      + +
      +
      +
      require 'sinatra/base'
      +
      +use Sinatra do
      +  get('/') { ... }
      +end
       
      -use Sinatra do
      -  get('/') { ... }
      -end
      +run RailsProject::Application
      +
      +
      +
      -run RailsProject::Application -

      Области видимости и привязка

      @@ -2212,31 +2964,37 @@

      Область видимости приложения / класса

      существует только один класс приложения для всех запросов.

      Опции, созданные с помощью set, являются методами уровня класса:

      -
      class MyApp < Sinatra::Base
      -  # Я в области видимости приложения!
      -  set :foo, 42
      -  foo # => 42
      -
      -  get '/foo' do
      -    # Я больше не в области видимости приложения!
      -  end
      -end
      -
      + +
      +
      +
      class MyApp < Sinatra::Base
      +  # Я в области видимости приложения!
      +  set :foo, 42
      +  foo # => 42
      +
      +  get '/foo' do
      +    # Я больше не в области видимости приложения!
      +  end
      +end
      +
      +
      +
      +

      У вас будет область видимости приложения внутри:

      • тела вашего класса приложения;
      • -
      • методов, определенных расширениями;
      • -
      • блока, переданного в helpers;
      • -
      • блоков, использованных как значения для set;
      • -
      • блока, переданного в Sinatra.new.
      • +
      • методов, определенных расширениями;
      • +
      • блока, переданного в helpers;
      • +
      • блоков, использованных как значения для set;
      • +
      • блока, переданного в Sinatra.new.

      Вы можете получить доступ к объекту области видимости (классу приложения) следующими способами:

      • через объект, переданный блокам конфигурации (configure { |c| ... });
      • -
      • +
      • settings внутри области видимости запроса.
      @@ -2248,28 +3006,34 @@

      Область видимости запроса/экземпляра

      рендеринга, такие как erb или haml. Вы можете получить доступ к области видимости приложения из контекста запроса, используя метод-помощник settings:

      -
      class MyApp < Sinatra::Base
      -  # Я в области видимости приложения!
      -  get '/define_route/:name' do
      -    # Область видимости запроса '/define_route/:name'
      -    @value = 42
      -
      -    settings.get("/#{params[:name]}") do
      -      # Область видимости запроса "/#{params[:name]}"
      -      @value # => nil (другой запрос)
      -    end
      -
      -    "Route defined!"
      -  end
      -end
      -
      + +
      +
      +
      class MyApp < Sinatra::Base
      +  # Я в области видимости приложения!
      +  get '/define_route/:name' do
      +    # Область видимости запроса '/define_route/:name'
      +    @value = 42
      +
      +    settings.get("/#{params[:name]}") do
      +      # Область видимости запроса "/#{params[:name]}"
      +      @value # => nil (другой запрос)
      +    end
      +
      +    "Route defined!"
      +  end
      +end
      +
      +
      +
      +

      У вас будет область видимости запроса в:

      • get/head/post/put/delete/options блоках;
      • -
      • before/after фильтрах;
      • -
      • методах-помощниках;
      • -
      • шаблонах/отображениях.
      • +
      • before/after фильтрах;
      • +
      • методах-помощниках;
      • +
      • шаблонах/отображениях.

      Область видимости делегирования

      @@ -2285,8 +3049,8 @@

      Область видимости делегирования

      У вас будет контекст делегирования внутри:

        -
      • привязки верхнего уровня, если вы сделали require 'sinatra';
      • -
      • объекта, расширенного с помощью Sinatra::Delegator.
      • +
      • привязки верхнего уровня, если вы сделали require 'sinatra';
      • +
      • объекта, расширенного с помощью Sinatra::Delegator.

      Посмотрите сами в код: вот примесь Sinatra::Delegator @@ -2296,16 +3060,20 @@

      Область видимости делегирования

      Командная строка

      Sinatra приложения могут быть запущены напрямую:

      -
      ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
      -
      + +
      ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
      +
      +

      Опции включают:

      -
      -h # раздел помощи
      +
      +
      -h # раздел помощи
       -p # указание порта (по умолчанию 4567)
       -o # указание хоста (по умолчанию 0.0.0.0)
       -e # указание окружения, режима (по умолчанию development)
       -s # указание rack сервера/обработчика (по умолчанию thin)
       -x # включить мьютекс-блокировку (по умолчанию выключена)
      -
      +
      +

      Системные требования

      @@ -2353,9 +3121,9 @@

      Системные требования

      • старые версии JRuby и Rubinius;
      • -
      • Ruby Enterprise Edition;
      • -
      • MacRuby, Maglev, IronRuby;
      • -
      • Ruby 1.9.0 и 1.9.1 (настоятельно не рекомендуются к использованию).
      • +
      • Ruby Enterprise Edition;
      • +
      • MacRuby, Maglev, IronRuby;
      • +
      • Ruby 1.9.0 и 1.9.1 (настоятельно не рекомендуются к использованию).

      То, что версия официально не поддерживается, означает, что, если что-то не работает на этой версии, а на поддерживаемой работает — это не наша проблема, @@ -2380,8 +3148,10 @@

      На острие

      Мы также время от времени выпускаем предварительные версии, так что вы можете делать так:

      -
      gem install sinatra --pre
      -
      + +
      gem install sinatra --pre
      +
      +

      Чтобы воспользоваться некоторыми самыми последними возможностями.

      @@ -2391,49 +3161,67 @@

      С помощью Bundler

      рекомендуем использовать Bundler.

      Сначала установите Bundler, если у вас его еще нет:

      -
      gem install bundler
      -
      + +
      gem install bundler
      +
      +

      Затем создайте файл Gemfile в директории вашего проекта:

      -
      source :rubygems
      -gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
       
      -# другие зависимости
      -gem 'haml'                    # например, если используете haml
      -gem 'activerecord', '~> 3.0'  # может быть, вам нужен и ActiveRecord 3.x
      -
      +
      +
      +
      source :rubygems
      +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
      +
      +# другие зависимости
      +gem 'haml'                    # например, если используете haml
      +gem 'activerecord', '~> 3.0'  # может быть, вам нужен и ActiveRecord 3.x
      +
      +
      +
      +

      Обратите внимание, вам нужно будет указывать все зависимости вашего приложения в этом файле. Однако, непосредственные зависимости Sinatra (Rack и Tilt) Bundler автоматически скачает и добавит.

      Теперь вы можете запускать свое приложение так:

      -
      bundle exec ruby myapp.rb
      -
      + +
      bundle exec ruby myapp.rb
      +
      +

      Вручную

      Создайте локальный клон репозитория и запускайте свое приложение с sinatra/lib директорией в $LOAD_PATH:

      -
      cd myapp
      +
      +
      cd myapp
       git clone git://github.com/sinatra/sinatra.git
       ruby -Isinatra/lib myapp.rb
      -
      +
      +

      Чтобы обновить исходники Sinatra:

      -
      cd myapp/sinatra
      +
      +
      cd myapp/sinatra
       git pull
      -
      +
      +

      Установка глобально

      Вы можете самостоятельно собрать gem:

      -
      git clone git://github.com/sinatra/sinatra.git
      +
      +
      git clone git://github.com/sinatra/sinatra.git
       cd sinatra
       rake sinatra.gemspec
       rake install
      -
      +
      +

      Если вы устанавливаете пакеты (gem) от пользователя root, то вашим последним шагом должна быть команда

      -
      sudo rake install
      -
      + +
      sudo rake install
      +
      +

      Версии

      @@ -2447,20 +3235,21 @@

      Дальнейшее чтение

    7. Веб-сайт проекта — Дополнительная документация, новости и ссылки на другие ресурсы.
    8. -
    9. +
    10. Участие в проекте — Обнаружили баг? Нужна помощь? Написали патч?
    11. -
    12. Слежение за проблемами/ошибками
    13. -
    14. Twitter
    15. -
    16. Группы рассылки
    17. -
    18. [#sinatra](irc://chat.freenode.net/#sinatra) на http://freenode.net
    19. -
    20. +
    21. Слежение за проблемами/ошибками
    22. +
    23. Twitter
    24. +
    25. Группы рассылки
    26. +
    27. +#sinatra на http://freenode.net
    28. +
    29. Sinatra Book учебник и сборник рецептов
    30. -
    31. +
    32. Sinatra Recipes сборник рецептов
    33. -
    34. API документация к последнему релизу +
    35. API документация к последнему релизу или текущему HEAD на http://rubydoc.info
    36. -
    37. Сервер непрерывной интеграции
    38. +
    39. Сервер непрерывной интеграции
    40. diff --git a/_includes/README.zh.html b/_includes/README.zh.html index 4ab25c12..2bbb01b9 100644 --- a/_includes/README.zh.html +++ b/_includes/README.zh.html @@ -94,17 +94,29 @@

      Sinatra是一个基于Ruby语言,以最小精力为代价快速创建web应用为目的的DSL( 领域专属语言):

      -
      # myapp.rb
      -require 'sinatra'
       
      -get '/' do
      -  'Hello world!'
      -end
      -
      +
      +
      +
      # myapp.rb
      +require 'sinatra'
      +
      +get '/' do
      +  'Hello world!'
      +end
      +
      +
      +
      +

      安装gem然后运行:

      -
      gem install sinatra
      +
      +
      +
      +
      gem install sinatra
       ruby myapp.rb
      -
      +
      + + +

      在该地址查看: localhost:4567

      推荐同时运行gem install thin,Sinatra会优先选择thin作为服务器。

      @@ -114,97 +126,151 @@

      路由

      在Sinatra中,一个路由是一个HTTP方法与URL匹配范式的配对。 每个路由都与一个代码块关联:

      -
      get '/' do
      +
      +
      +
      +
      get '/' do
         .. 显示一些事物 ..
      -end
      +end
       
      -post '/' do
      +post '/' do
         .. 创建一些事物 ..
      -end
      +end
       
      -put '/' do
      +put '/' do
         .. 更新一些事物 ..
      -end
      +end
       
      -delete '/' do
      +delete '/' do
         .. 消灭一些事物 ..
      -end
      +end
       
      -options '/' do
      +options '/' do
         .. 满足一些事物 ..
      -end
      -
      +end +
      + + +

      路由按照它们被定义的顺序进行匹配。 第一个与请求匹配的路由会被调用。

      路由范式可以包括具名参数,可通过params哈希表获得:

      -
      get '/hello/:name' do
      -  # 匹配 "GET /hello/foo" 和 "GET /hello/bar"
      -  # params[:name] 的值是 'foo' 或者 'bar'
      -  "Hello #{params[:name]}!"
      -end
      -
      + +
      +
      +
      get '/hello/:name' do
      +  # 匹配 "GET /hello/foo" 和 "GET /hello/bar"
      +  # params[:name] 的值是 'foo' 或者 'bar'
      +  "Hello #{params[:name]}!"
      +end
      +
      +
      +
      +

      你同样可以通过代码块参数获得具名参数:

      -
      get '/hello/:name' do |n|
      -  "Hello #{n}!"
      -end
      -
      + +
      +
      +
      get '/hello/:name' do |n|
      +  "Hello #{n}!"
      +end
      +
      +
      +
      +

      路由范式也可以包含通配符参数, 可以通过params[:splat]数组获得。

      -
      get '/say/*/to/*' do
      -  # 匹配 /say/hello/to/world
      -  params[:splat] # => ["hello", "world"]
      -end
      -
      -get '/download/*.*' do
      -  # 匹配 /download/path/to/file.xml
      -  params[:splat] # => ["path/to/file", "xml"]
      -end
      -
      + +
      +
      +
      get '/say/*/to/*' do
      +  # 匹配 /say/hello/to/world
      +  params[:splat] # => ["hello", "world"]
      +end
      +
      +get '/download/*.*' do
      +  # 匹配 /download/path/to/file.xml
      +  params[:splat] # => ["path/to/file", "xml"]
      +end
      +
      +
      +
      +

      通过正则表达式匹配的路由:

      -
      get %r{/hello/([\w]+)} do
      -  "Hello, #{params[:captures].first}!"
      -end
      -
      + +
      +
      +
      get %r{/hello/([\w]+)} do
      +  "Hello, #{params[:captures].first}!"
      +end
      +
      +
      +
      +

      或者使用代码块参数:

      -
      get %r{/hello/([\w]+)} do |c|
      -  "Hello, #{c}!"
      -end
      -
      + +
      +
      +
      get %r{/hello/([\w]+)} do |c|
      +  "Hello, #{c}!"
      +end
      +
      +
      +
      +

      条件

      路由也可以包含多样的匹配条件,比如user agent:

      -
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      -  "你正在使用Songbird,版本是 #{params[:agent][0]}"
      -end
      -
      -get '/foo' do
      -  # 匹配除Songbird以外的浏览器
      -end
      -
      + +
      +
      +
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      +  "你正在使用Songbird,版本是 #{params[:agent][0]}"
      +end
      +
      +get '/foo' do
      +  # 匹配除Songbird以外的浏览器
      +end
      +
      +
      +
      +

      其他可选的条件是 host_nameprovides

      -
      get '/', :host_name => /^admin\./ do
      -  "管理员区域,无权进入!"
      -end
      -
      -get '/', :provides => 'html' do
      -  haml :index
      -end
      -
      -get '/', :provides => ['rss', 'atom', 'xml'] do
      -  builder :feed
      -end
      -
      + +
      +
      +
      get '/', :host_name => /^admin\./ do
      +  "管理员区域,无权进入!"
      +end
      +
      +get '/', :provides => 'html' do
      +  haml :index
      +end
      +
      +get '/', :provides => ['rss', 'atom', 'xml'] do
      +  builder :feed
      +end
      +
      +
      +
      +

      你也可以很轻松地定义自己的条件:

      -
      set(:probability) { |value| condition { rand <= value } }
       
      -get '/win_a_car', :probability => 0.1 do
      -  "You won!"
      -end
      +
      +
      +
      set(:probability) { |value| condition { rand <= value } }
      +
      +get '/win_a_car', :probability => 0.1 do
      +  "You won!"
      +end
      +
      +get '/win_a_car' do
      +  "Sorry, you lost."
      +end
      +
      +
      +
      -get '/win_a_car' do - "Sorry, you lost." -end -

      返回值

      @@ -217,58 +283,88 @@

      返回值

      body对象或者HTTP状态码:

      那样,我们可以轻松的实现例如流式传输的例子:

      -
      class Stream
      -  def each
      -    100.times { |i| yield "#{i}\n" }
      -  end
      -end
      -
      -get('/') { Stream.new }
      -
      + +
      +
      +
      class Stream
      +  def each
      +    100.times { |i| yield "#{i}\n" }
      +  end
      +end
      +
      +get('/') { Stream.new }
      +
      +
      +
      +

      自定义路由匹配器

      如上显示,Sinatra内置了对于使用字符串和正则表达式作为路由匹配的支持。 但是,它并没有只限于此。 你可以非常容易地定义你自己的匹配器:

      -
      class AllButPattern
      -  Match = Struct.new(:captures)
      -
      -  def initialize(except)
      -    @except   = except
      -    @captures = Match.new([])
      -  end
      -
      -  def match(str)
      -    @captures unless @except === str
      -  end
      -end
      -
      -def all_but(pattern)
      -  AllButPattern.new(pattern)
      -end
      -
      -get all_but("/index") do
      -  # ...
      -end
      -
      + +
      +
      +
      class AllButPattern
      +  Match = Struct.new(:captures)
      +
      +  def initialize(except)
      +    @except   = except
      +    @captures = Match.new([])
      +  end
      +
      +  def match(str)
      +    @captures unless @except === str
      +  end
      +end
      +
      +def all_but(pattern)
      +  AllButPattern.new(pattern)
      +end
      +
      +get all_but("/index") do
      +  # ...
      +end
      +
      +
      +
      +

      请注意上面的例子可能超工程了, 因为它也可以用更简单的方式表述:

      -
      get // do
      -  pass if request.path_info == "/index"
      -  # ...
      -end
      -
      + +
      +
      +
      get // do
      +  pass if request.path_info == "/index"
      +  # ...
      +end
      +
      +
      +
      +

      或者,使用消极向前查找:

      -
      get %r{^(?!/index$)} do
      -  # ...
      -end
      -
      + +
      +
      +
      get %r{^(?!/index$)} do
      +  # ...
      +end
      +
      +
      +
      +

      静态文件

      静态文件是从 ./public_folder 目录提供服务。你可以通过设置:public 选项设定一个不同的位置:

      -
      set :public_folder, File.dirname(__FILE__) + '/static'
      -
      + +
      +
      +
      set :public_folder, File.dirname(__FILE__) + '/static'
      +
      +
      +
      +

      请注意public目录名并没有被包含在URL之中。文件 ./public/css/style.css是通过 http://example.com/css/style.css地址访问的。

      @@ -277,8 +373,14 @@

      静态文件

      视图 / 模板

      模板被假定直接位于./views目录。 要使用不同的视图目录:

      -
      set :views, File.dirname(__FILE__) + '/templates'
      -
      + +
      +
      +
      set :views, File.dirname(__FILE__) + '/templates'
      +
      +
      +
      +

      请记住一件非常重要的事情,你只可以通过符号引用模板, 即使它们在子目录下 (在这种情况下,使用 :'subdir/template')。 你必须使用一个符号, 因为渲染方法会直接地渲染任何传入的字符串。

      @@ -287,96 +389,144 @@

      视图 / 模板

      Haml模板

      需要引入 haml gem/library以渲染 HAML 模板:

      -
      # 你需要在你的应用中引入 haml
      -require 'haml'
       
      -get '/' do
      -  haml :index
      -end
      -
      +
      +
      +
      # 你需要在你的应用中引入 haml
      +require 'haml'
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.haml

      Haml的选项 可以通过Sinatra的配置全局设定, 参见 选项和配置, 也可以个别的被覆盖。

      -
      set :haml, {:format => :html5 } # 默认的Haml输出格式是 :xhtml
       
      -get '/' do
      -  haml :index, :haml_options => {:format => :html4 } # 被覆盖,变成:html4
      -end
      -
      +
      +
      +
      set :haml, {:format => :html5 } # 默认的Haml输出格式是 :xhtml
      +
      +get '/' do
      +  haml :index, :haml_options => {:format => :html4 } # 被覆盖,变成:html4
      +end
      +
      +
      +
      +

      Erb模板

      -
      # 你需要在你的应用中引入 erb
      -require 'erb'
       
      -get '/' do
      -  erb :index
      -end
      -
      +
      +
      +
      # 你需要在你的应用中引入 erb
      +require 'erb'
      +
      +get '/' do
      +  erb :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.erb

      Erubis

      需要引入 erubis gem/library以渲染 erubis 模板:

      -
      # 你需要在你的应用中引入 erubis
      -require 'erubis'
       
      -get '/' do
      -  erubis :index
      -end
      -
      +
      +
      +
      # 你需要在你的应用中引入 erubis
      +require 'erubis'
      +
      +get '/' do
      +  erubis :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.erubis

      使用Erubis代替Erb也是可能的:

      -
      require 'erubis'
      -Tilt.register :erb, Tilt[:erubis]
       
      -get '/' do
      -  erb :index
      -end
      -
      +
      +
      +
      require 'erubis'
      +Tilt.register :erb, Tilt[:erubis]
      +
      +get '/' do
      +  erb :index
      +end
      +
      +
      +
      +

      使用Erubis来渲染 ./views/index.erb

      Builder 模板

      需要引入 builder gem/library 以渲染 builder templates:

      -
      # 需要在你的应用中引入builder
      -require 'builder'
       
      -get '/' do
      -  builder :index
      -end
      -
      +
      +
      +
      # 需要在你的应用中引入builder
      +require 'builder'
      +
      +get '/' do
      +  builder :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.builder

      Nokogiri 模板

      需要引入 nokogiri gem/library 以渲染 nokogiri 模板:

      -
      # 需要在你的应用中引入 nokogiri
      -require 'nokogiri'
       
      -get '/' do
      -  nokogiri :index
      -end
      -
      +
      +
      +
      # 需要在你的应用中引入 nokogiri
      +require 'nokogiri'
      +
      +get '/' do
      +  nokogiri :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.nokogiri

      Sass 模板

      需要引入 haml 或者 sass gem/library 以渲染 Sass 模板:

      -
      # 需要在你的应用中引入 haml 或者 sass
      -require 'sass'
       
      -get '/stylesheet.css' do
      -  sass :stylesheet
      -end
      -
      +
      +
      +
      # 需要在你的应用中引入 haml 或者 sass
      +require 'sass'
      +
      +get '/stylesheet.css' do
      +  sass :stylesheet
      +end
      +
      +
      +
      +

      渲染 ./views/stylesheet.sass

      Sass @@ -384,156 +534,258 @@

      Sass 模板

      可以通过Sinatra选项全局设定, 参考 选项和配置(英文), 也可以在个体的基础上覆盖。

      -
      set :sass, {:style => :compact } # 默认的 Sass 样式是 :nested
       
      -get '/stylesheet.css' do
      -  sass :stylesheet, :style => :expanded # 覆盖
      -end
      -
      +
      +
      +
      set :sass, {:style => :compact } # 默认的 Sass 样式是 :nested
      +
      +get '/stylesheet.css' do
      +  sass :stylesheet, :style => :expanded # 覆盖
      +end
      +
      +
      +
      +

      Scss 模板

      需要引入 haml 或者 sass gem/library 来渲染 Scss templates:

      -
      # 需要在你的应用中引入 haml 或者 sass
      -require 'sass'
       
      -get '/stylesheet.css' do
      -  scss :stylesheet
      -end
      -
      +
      +
      +
      # 需要在你的应用中引入 haml 或者 sass
      +require 'sass'
      +
      +get '/stylesheet.css' do
      +  scss :stylesheet
      +end
      +
      +
      +
      +

      渲染 ./views/stylesheet.scss

      Scss的选项 可以通过Sinatra选项全局设定, 参考 选项和配置(英文), 也可以在个体的基础上覆盖。

      -
      set :scss, :style => :compact # default Scss style is :nested
       
      -get '/stylesheet.css' do
      -  scss :stylesheet, :style => :expanded # overridden
      -end
      -
      +
      +
      +
      set :scss, :style => :compact # default Scss style is :nested
      +
      +get '/stylesheet.css' do
      +  scss :stylesheet, :style => :expanded # overridden
      +end
      +
      +
      +
      +

      Less 模板

      需要引入 less gem/library 以渲染 Less 模板:

      -
      # 需要在你的应用中引入 less
      -require 'less'
       
      -get '/stylesheet.css' do
      -  less :stylesheet
      -end
      -
      +
      +
      +
      # 需要在你的应用中引入 less
      +require 'less'
      +
      +get '/stylesheet.css' do
      +  less :stylesheet
      +end
      +
      +
      +
      +

      渲染 ./views/stylesheet.less

      Liquid 模板

      需要引入 liquid gem/library 来渲染 Liquid 模板:

      -
      # 需要在你的应用中引入 liquid
      -require 'liquid'
       
      -get '/' do
      -  liquid :index
      -end
      -
      +
      +
      +
      # 需要在你的应用中引入 liquid
      +require 'liquid'
      +
      +get '/' do
      +  liquid :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.liquid

      因为你不能在Liquid 模板中调用 Ruby 方法 (除了 yield) , 你几乎总是需要传递locals给它:

      -
      liquid :index, :locals => { :key => 'value' }
      -
      + +
      +
      +
      liquid :index, :locals => { :key => 'value' }
      +
      +
      +
      +

      Markdown 模板

      需要引入 rdiscount gem/library 以渲染 Markdown 模板:

      -
      # 需要在你的应用中引入rdiscount
      -require "rdiscount"
       
      -get '/' do
      -  markdown :index
      -end
      -
      +
      +
      +
      # 需要在你的应用中引入rdiscount
      +require "rdiscount"
      +
      +get '/' do
      +  markdown :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.markdown (mdmkd 也是合理的文件扩展名)。

      在markdown中是不可以调用方法的,也不可以传递 locals给它。 你因此一般会结合其他的渲染引擎来使用它:

      -
      erb :overview, :locals => { :text => markdown(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => markdown(:introduction) }
      +
      +
      +
      +

      请注意你也可以从其他模板中调用 markdown 方法:

      -
      %h1 Hello From Haml!
      -%p= markdown(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= markdown(:greetings)
      +
      +
      +
      +

      既然你不能在Markdown中调用Ruby,你不能使用Markdown编写的布局。 不过,使用其他渲染引擎作为模版的布局是可能的, 通过传递:layout_engine选项:

      -
      get '/' do
      -  markdown :index, :layout_engine => :erb
      -end
      -
      + +
      +
      +
      get '/' do
      +  markdown :index, :layout_engine => :erb
      +end
      +
      +
      +
      +

      这将会渲染 ./views/index.md 并使用 ./views/layout.erb 作为布局。

      请记住你可以全局设定这个选项:

      -
      set :markdown, :layout_engine => :haml, :layout => :post
       
      -get '/' do
      -  markdown :index
      -end
      -
      +
      +
      +
      set :markdown, :layout_engine => :haml, :layout => :post
      +
      +get '/' do
      +  markdown :index
      +end
      +
      +
      +
      +

      这将会渲染 ./views/index.markdown (和任何其他的 Markdown 模版) 并使用 ./views/post.haml 作为布局.

      也可能使用BlueCloth而不是RDiscount来解析Markdown文件:

      -
      require 'bluecloth'
       
      -Tilt.register 'markdown', BlueClothTemplate
      -Tilt.register 'mkd',      BlueClothTemplate
      -Tilt.register 'md',       BlueClothTemplate
      +
      +
      +
      require 'bluecloth'
      +
      +Tilt.register 'markdown', BlueClothTemplate
      +Tilt.register 'mkd',      BlueClothTemplate
      +Tilt.register 'md',       BlueClothTemplate
      +
      +get '/' do
      +  markdown :index
      +end
      +
      +
      +
      -get '/' do - markdown :index -end -

      使用BlueCloth来渲染 ./views/index.md

      Textile 模板

      需要引入 RedCloth gem/library 以渲染 Textile 模板:

      -
      # 在你的应用中引入redcloth
      -require "redcloth"
       
      -get '/' do
      -  textile :index
      -end
      -
      +
      +
      +
      # 在你的应用中引入redcloth
      +require "redcloth"
      +
      +get '/' do
      +  textile :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.textile

      在textile中是不可以调用方法的,也不可以传递 locals给它。 你因此一般会结合其他的渲染引擎来使用它:

      -
      erb :overview, :locals => { :text => textile(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => textile(:introduction) }
      +
      +
      +
      +

      请注意你也可以从其他模板中调用textile方法:

      -
      %h1 Hello From Haml!
      -%p= textile(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= textile(:greetings)
      +
      +
      +
      +

      既然你不能在Textile中调用Ruby,你不能使用Textile编写的布局。 不过,使用其他渲染引擎作为模版的布局是可能的, 通过传递:layout_engine选项:

      -
      get '/' do
      -  textile :index, :layout_engine => :erb
      -end
      -
      + +
      +
      +
      get '/' do
      +  textile :index, :layout_engine => :erb
      +end
      +
      +
      +
      +

      这将会渲染 ./views/index.textile 并使用 ./views/layout.erb 作为布局。

      请记住你可以全局设定这个选项:

      -
      set :textile, :layout_engine => :haml, :layout => :post
       
      -get '/' do
      -  textile :index
      -end
      -
      +
      +
      +
      set :textile, :layout_engine => :haml, :layout => :post
      +
      +get '/' do
      +  textile :index
      +end
      +
      +
      +
      +

      这将会渲染 ./views/index.textile (和任何其他的 Textile 模版) 并使用 ./views/post.haml 作为布局.

      @@ -541,40 +793,70 @@

      Textile 模板

      RDoc 模板

      需要引入 RDoc gem/library 以渲染RDoc模板:

      -
      # 需要在你的应用中引入rdoc/markup/to_html
      -require "rdoc"
      -require "rdoc/markup/to_html"
      -
      -get '/' do
      -  rdoc :index
      -end
      -
      + +
      +
      +
      # 需要在你的应用中引入rdoc/markup/to_html
      +require "rdoc"
      +require "rdoc/markup/to_html"
      +
      +get '/' do
      +  rdoc :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.rdoc

      在rdoc中是不可以调用方法的,也不可以传递locals给它。 你因此一般会结合其他的渲染引擎来使用它:

      -
      erb :overview, :locals => { :text => rdoc(:introduction) }
      -
      + +
      +
      +
      erb :overview, :locals => { :text => rdoc(:introduction) }
      +
      +
      +
      +

      请注意你也可以从其他模板中调用rdoc方法:

      -
      %h1 Hello From Haml!
      -%p= rdoc(:greetings)
      -
      + +
      +
      +
      %h1 Hello From Haml!
      +%p= rdoc(:greetings)
      +
      +
      +
      +

      既然你不能在RDoc中调用Ruby,你不能使用RDoc编写的布局。 不过,使用其他渲染引擎作为模版的布局是可能的, 通过传递:layout_engine选项:

      -
      get '/' do
      -  rdoc :index, :layout_engine => :erb
      -end
      -
      + +
      +
      +
      get '/' do
      +  rdoc :index, :layout_engine => :erb
      +end
      +
      +
      +
      +

      这将会渲染 ./views/index.rdoc 并使用 ./views/layout.erb 作为布局。

      请记住你可以全局设定这个选项:

      -
      set :rdoc, :layout_engine => :haml, :layout => :post
       
      -get '/' do
      -  rdoc :index
      -end
      -
      +
      +
      +
      set :rdoc, :layout_engine => :haml, :layout => :post
      +
      +get '/' do
      +  rdoc :index
      +end
      +
      +
      +
      +

      这将会渲染 ./views/index.rdoc (和任何其他的 RDoc 模版) 并使用 ./views/post.haml 作为布局.

      @@ -582,61 +864,97 @@

      RDoc 模板

      Radius 模板

      需要引入 radius gem/library 以渲染 Radius 模板:

      -
      # 需要在你的应用中引入radius
      -require 'radius'
       
      -get '/' do
      -  radius :index
      -end
      -
      +
      +
      +
      # 需要在你的应用中引入radius
      +require 'radius'
      +
      +get '/' do
      +  radius :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.radius

      因为你不能在Radius 模板中调用 Ruby 方法 (除了 yield) , 你几乎总是需要传递locals给它:

      -
      radius :index, :locals => { :key => 'value' }
      -
      + +
      +
      +
      radius :index, :locals => { :key => 'value' }
      +
      +
      +
      +

      Markaby 模板

      需要引入markaby gem/library以渲染Markaby模板:

      -
      #需要在你的应用中引入 markaby
      -require 'markaby'
       
      -get '/' do
      -  markaby :index
      -end
      -
      +
      +
      +
      #需要在你的应用中引入 markaby
      +require 'markaby'
      +
      +get '/' do
      +  markaby :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.mab

      你也可以使用嵌入的 Markaby:

      -
      get '/' do
      -  markaby { h1 "Welcome!" }
      -end
      -
      + +
      +
      +
      get '/' do
      +  markaby { h1 "Welcome!" }
      +end
      +
      +
      +
      +

      Slim 模板

      需要引入 slim gem/library 来渲染 Slim 模板:

      -
      # 需要在你的应用中引入 slim
      -require 'slim'
       
      -get '/' do
      -  slim :index
      -end
      -
      +
      +
      +
      # 需要在你的应用中引入 slim
      +require 'slim'
      +
      +get '/' do
      +  slim :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.slim

      Creole 模板

      需要引入 creole gem/library 来渲染 Creole 模板:

      -
      # 需要在你的应用中引入 creole
      -require 'creole'
       
      -get '/' do
      -  creole :index
      -end
      -
      +
      +
      +
      # 需要在你的应用中引入 creole
      +require 'creole'
      +
      +get '/' do
      +  creole :index
      +end
      +
      +
      +
      +

      渲染 ./views/index.creole

      @@ -646,30 +964,48 @@

      CoffeeScript 模板

      以执行Javascript:

        -
      • node (来自 Node.js) 在你的路径中

      • -
      • 你正在运行 OSX

      • -
      • therubyracer gem/library

      • +
      • +

        node (来自 Node.js) 在你的路径中

        +
      • +
      • +

        你正在运行 OSX

        +
      • +
      • +

        therubyracer gem/library

        +

      请察看 github.com/josh/ruby-coffee-script 获取更新的选项。

      现在你可以渲染 CoffeeScript 模版了:

      -
      # 需要在你的应用中引入coffee-script
      -require 'coffee-script'
       
      -get '/application.js' do
      -  coffee :application
      -end
      -
      +
      +
      +
      # 需要在你的应用中引入coffee-script
      +require 'coffee-script'
      +
      +get '/application.js' do
      +  coffee :application
      +end
      +
      +
      +
      +

      渲染 ./views/application.coffee

      嵌入模板字符串

      -
      get '/' do
      -  haml '%div.title Hello World'
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml '%div.title Hello World'
      +end
      +
      +
      +
      +

      渲染嵌入模板字符串。

      @@ -677,38 +1013,56 @@

      在模板中访问变量

      模板和路由执行器在同样的上下文求值。 在路由执行器中赋值的实例变量可以直接被模板访问。

      -
      get '/:id' do
      -  @foo = Foo.find(params[:id])
      -  haml '%h1= @foo.name'
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  @foo = Foo.find(params[:id])
      +  haml '%h1= @foo.name'
      +end
      +
      +
      +
      +

      或者,显式地指定一个本地变量的哈希:

      -
      get '/:id' do
      -  foo = Foo.find(params[:id])
      -  haml '%h1= foo.name', :locals => { :foo => foo }
      -end
      -
      + +
      +
      +
      get '/:id' do
      +  foo = Foo.find(params[:id])
      +  haml '%h1= foo.name', :locals => { :foo => foo }
      +end
      +
      +
      +
      +

      典型的使用情况是在别的模板中按照局部模板的方式来渲染。

      内联模板

      模板可以在源文件的末尾定义:

      -
      require 'sinatra'
       
      -get '/' do
      -  haml :index
      -end
      +
      +
      +
      require 'sinatra'
      +
      +get '/' do
      +  haml :index
      +end
       
      -__END__
      +__END__
       
       @@ layout
       %html
         = yield
       
       @@ index
      -%div.title Hello world!!!!!
      -
      +%div.title Hello world!!!!! +
      + + +

      注意:引入sinatra的源文件中定义的内联模板才能被自动载入。 如果你在其他源文件中有内联模板, 需要显式执行调用enable :inline_templates

      @@ -717,47 +1071,71 @@

      内联模板

      具名模板

      模板可以通过使用顶层 template 方法定义:

      -
      template :layout do
      -  "%html\n  =yield\n"
      -end
      -
      -template :index do
      -  '%div.title Hello World!'
      -end
      -
      -get '/' do
      -  haml :index
      -end
      -
      + +
      +
      +
      template :layout do
      +  "%html\n  =yield\n"
      +end
      +
      +template :index do
      +  '%div.title Hello World!'
      +end
      +
      +get '/' do
      +  haml :index
      +end
      +
      +
      +
      +

      如果存在名为“layout”的模板,该模板会在每个模板渲染的时候被使用。 你可以单独地通过传送 :layout => false来禁用, 或者通过set :haml, :layout => false来禁用他们。

      -
      get '/' do
      -  haml :index, :layout => !request.xhr?
      -end
      -
      + +
      +
      +
      get '/' do
      +  haml :index, :layout => !request.xhr?
      +end
      +
      +
      +
      +

      关联文件扩展名

      为了关联一个文件扩展名到一个模版引擎,使用 Tilt.register。比如,如果你喜欢使用 tt 作为Textile模版的扩展名,你可以这样做:

      -
      Tilt.register :tt, Tilt[:textile]
      -
      + +
      +
      +
      Tilt.register :tt, Tilt[:textile]
      +
      +
      +
      +

      添加你自己的模版引擎

      首先,通过Tilt注册你自己的引擎,然后创建一个渲染方法:

      -
      Tilt.register :myat, MyAwesomeTemplateEngine
       
      -helpers do
      -  def myat(*args) render(:myat, *args) end
      -end
      +
      +
      +
      Tilt.register :myat, MyAwesomeTemplateEngine
      +
      +helpers do
      +  def myat(*args) render(:myat, *args) end
      +end
      +
      +get '/' do
      +  myat :index
      +end
      +
      +
      +
      -get '/' do - myat :index -end -

      渲染 ./views/index.myat。察看 github.com/rtomayko/tilt 来更多了解Tilt.

      @@ -767,118 +1145,196 @@

      过滤器

      前置过滤器在每个请求前,在请求的上下文环境中被执行, 而且可以修改请求和响应。 在过滤器中设定的实例变量可以被路由和模板访问:

      -
      before do
      -  @note = 'Hi!'
      -  request.path_info = '/foo/bar/baz'
      -end
      -
      -get '/foo/*' do
      -  @note #=> 'Hi!'
      -  params[:splat] #=> 'bar/baz'
      -end
      -
      + +
      +
      +
      before do
      +  @note = 'Hi!'
      +  request.path_info = '/foo/bar/baz'
      +end
      +
      +get '/foo/*' do
      +  @note #=> 'Hi!'
      +  params[:splat] #=> 'bar/baz'
      +end
      +
      +
      +
      +

      后置过滤器在每个请求之后,在请求的上下文环境中执行, 而且可以修改请求和响应。 在前置过滤器和路由中设定的实例变量可以被后置过滤器访问:

      -
      after do
      +
      +
      +
      +
      after do
         puts response.status
      -end
      -
      +end +
      + + +

      请注意:除非你显式使用 body 方法,而不是在路由中直接返回字符串, 消息体在后置过滤器是不可用的, 因为它在之后才会生成。

      过滤器可以可选地带有范式, 只有请求路径满足该范式时才会执行:

      -
      before '/protected/*' do
      +
      +
      +
      +
      before '/protected/*' do
         authenticate!
      -end
      +end
      +
      +after '/create/:slug' do |slug|
      +  session[:last_slug] = slug
      +end
      +
      +
      +
      -after '/create/:slug' do |slug| - session[:last_slug] = slug -end -

      和路由一样,过滤器也可以带有条件:

      -
      before :agent => /Songbird/ do
      -  # ...
      -end
      -
      -after '/blog/*', :host_name => 'example.com' do
      -  # ...
      -end
      -
      + +
      +
      +
      before :agent => /Songbird/ do
      +  # ...
      +end
      +
      +after '/blog/*', :host_name => 'example.com' do
      +  # ...
      +end
      +
      +
      +
      +

      辅助方法

      使用顶层的 helpers 方法来定义辅助方法, 以便在路由处理器和模板中使用:

      -
      helpers do
      -  def bar(name)
      -    "#{name}bar"
      -  end
      -end
      -
      -get '/:name' do
      -  bar(params[:name])
      -end
      -
      + +
      +
      +
      helpers do
      +  def bar(name)
      +    "#{name}bar"
      +  end
      +end
      +
      +get '/:name' do
      +  bar(params[:name])
      +end
      +
      +
      +
      +

      使用 Sessions

      Session被用来在请求之间保持状态。如果被激活,每一个用户会话 对应有一个session哈希:

      -
      enable :sessions
       
      -get '/' do
      -  "value = " << session[:value].inspect
      -end
      +
      +
      +
      enable :sessions
      +
      +get '/' do
      +  "value = " << session[:value].inspect
      +end
      +
      +get '/:value' do
      +  session[:value] = params[:value]
      +end
      +
      +
      +
      -get '/:value' do - session[:value] = params[:value] -end -

      请注意 enable :sessions 实际上保存所有的数据在一个cookie之中。 这可能不会总是做你想要的(比如,保存大量的数据会增加你的流量)。 你可以使用任何的Rack session中间件,为了这么做, *不要*调用 enable :sessions,而是 按照自己的需要引入你的中间件:

      -
      use Rack::Session::Pool, :expire_after => 2592000
       
      -get '/' do
      -  "value = " << session[:value].inspect
      -end
      +
      +
      +
      use Rack::Session::Pool, :expire_after => 2592000
      +
      +get '/' do
      +  "value = " << session[:value].inspect
      +end
      +
      +get '/:value' do
      +  session[:value] = params[:value]
      +end
      +
      +
      +
      + + +

      挂起

      + +

      要想直接地停止请求,在过滤器或者路由中使用:

      + +
      +
      +
      halt
      +
      +
      +
      + +

      你也可以指定挂起时的状态码:

      + +
      +
      +
      halt 410
      +
      +
      +
      + +

      或者消息体:

      -get '/:value' do - session[:value] = params[:value] -end -
      - -

      挂起

      +
      +
      +
      halt 'this will be the body'
      +
      +
      +
      -

      要想直接地停止请求,在过滤器或者路由中使用:

      -
      halt
      -
      -

      你也可以指定挂起时的状态码:

      -
      halt 410
      -
      -

      或者消息体:

      -
      halt 'this will be the body'
      -

      或者两者;

      -
      halt 401, 'go away!'
      -
      + +
      +
      +
      halt 401, 'go away!'
      +
      +
      +
      +

      也可以带消息头:

      -
      halt 402, {'Content-Type' => 'text/plain'}, 'revenge'
      -
      + +
      +
      +
      halt 402, {'Content-Type' => 'text/plain'}, 'revenge'
      +
      +
      +
      +

      让路

      一个路由可以放弃处理,将处理让给下一个匹配的路由,使用 pass

      -
      get '/guess/:who' do
      -  pass unless params[:who] == 'Frank'
      -  'You got me!'
      -end
      -
      -get '/guess/*' do
      -  'You missed!'
      -end
      -
      + +
      +
      +
      get '/guess/:who' do
      +  pass unless params[:who] == 'Frank'
      +  'You got me!'
      +end
      +
      +get '/guess/*' do
      +  'You missed!'
      +end
      +
      +
      +
      +

      路由代码块被直接退出,控制流继续前进到下一个匹配的路由。 如果没有匹配的路由,将返回404。

      @@ -887,18 +1343,26 @@

      触发另一个路由

      有些时候,pass 并不是你想要的,你希望得到的是另一个路由的结果 。简单的使用 call 可以做到这一点:

      -
      get '/foo' do
      -  status, headers, body = call env.merge("PATH_INFO" => '/bar')
      -  [status, headers, body.map(&:upcase)]
      -end
      -
      -get '/bar' do
      -  "bar"
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  status, headers, body = call env.merge("PATH_INFO" => '/bar')
      +  [status, headers, body.map(&:upcase)]
      +end
      +
      +get '/bar' do
      +  "bar"
      +end
      +
      +
      +
      +

      请注意在以上例子中,你可以更加简化测试并增加性能,只要简单的移动

      -
      <tt>"bar"</tt>到一个被<tt>/foo</tt>
      -
      + +
      <tt>"bar"</tt>到一个被<tt>/foo</tt>
      +
      +

      /bar同时使用的helper。

      如果你希望请求被发送到同一个应用,而不是副本, 使用 call! 而不是 @@ -913,26 +1377,38 @@

      设定 消息体,状态码和消息头

      但是,在某些场景中你可能想在作业流程中的特定点上设置消息体。 你可以通过 body 辅助方法这么做。 如果你这样做了, 你可以在那以后使用该方法获得消息体:

      -
      get '/foo' do
      -  body "bar"
      -end
       
      -after do
      +
      +
      +
      get '/foo' do
      +  body "bar"
      +end
      +
      +after do
         puts body
      -end
      -
      +end +
      + + +

      也可以传一个代码块给 body,它将会被Rack处理器执行( 这将可以被用来实现streaming,参见“返回值”)。

      和消息体类似,你也可以设定状态码和消息头:

      -
      get '/foo' do
      -  status 418
      +
      +
      +
      +
      get '/foo' do
      +  status 418
         headers \
      -    "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
      -    "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
      -  body "I'm a tea pot!"
      -end
      -
      + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +
      + + +

      如同 body, 不带参数的 headersstatus 可以用来访问 他们你的当前值.

      @@ -941,20 +1417,38 @@

      媒体类型

      当使用 send_file 或者静态文件的场合,你的媒体类型可能 Sinatra并不理解。使用 mime_type 通过文件扩展名来注册它们:

      -
      mime_type :foo, 'text/foo'
      -
      + +
      +
      +
      mime_type :foo, 'text/foo'
      +
      +
      +
      +

      你也可以通过 content_type 辅助方法使用:

      -
      get '/' do
      -  content_type :foo
      -  "foo foo foo"
      -end
      -
      + +
      +
      +
      get '/' do
      +  content_type :foo
      +  "foo foo foo"
      +end
      +
      +
      +
      +

      生成 URL

      为了生成URL,你需要使用 url 辅助方法, 例如,在Haml中:

      -
      %a{:href => url('/foo')} foo
      -
      + +
      +
      +
      %a{:href => url('/foo')} foo
      +
      +
      +
      +

      它会根据反向代理和Rack路由,如果有的话,来计算生成的URL。

      这个方法还有一个别名 to (见下面的例子).

      @@ -963,101 +1457,179 @@

      生成 URL

      浏览器重定向

      你可以通过 redirect 辅助方法触发浏览器重定向:

      -
      get '/foo' do
      -  redirect to('/bar')
      -end
      -
      + +
      +
      +
      get '/foo' do
      +  redirect to('/bar')
      +end
      +
      +
      +
      +

      任何额外的参数都会被以 halt相同的方式来处理:

      -
      redirect to('/bar'), 303
      -redirect 'http://google.com', 'wrong place, buddy'
      -
      + +
      +
      +
      redirect to('/bar'), 303
      +redirect 'http://google.com', 'wrong place, buddy'
      +
      +
      +
      +

      你可以方便的通过 redirect back把用户重定向到来自的页面:

      -
      get '/foo' do
      -  "<a href='/bar'>do something</a>"
      -end
       
      -get '/bar' do
      +
      +
      +
      get '/foo' do
      +  "<a href='/bar'>do something</a>"
      +end
      +
      +get '/bar' do
         do_something
         redirect back
      -end
      -
      +end +
      + + +

      为了传递参数给redirect,或者加入query:

      -
      redirect to('/bar?sum=42')
      -
      + +
      +
      +
      redirect to('/bar?sum=42')
      +
      +
      +
      +

      或者使用session:

      -
      enable :sessions
       
      -get '/foo' do
      -  session[:secret] = 'foo'
      -  redirect to('/bar')
      -end
      +
      +
      +
      enable :sessions
      +
      +get '/foo' do
      +  session[:secret] = 'foo'
      +  redirect to('/bar')
      +end
      +
      +get '/bar' do
      +  session[:secret]
      +end
      +
      +
      +
      -get '/bar' do - session[:secret] -end -

      缓存控制

      正确地设定消息头是恰当的HTTP缓存的基础。

      你可以方便的设定 Cache-Control 消息头,像这样:

      -
      get '/' do
      -  cache_control :public
      -  "cache it!"
      -end
      -
      + +
      +
      +
      get '/' do
      +  cache_control :public
      +  "cache it!"
      +end
      +
      +
      +
      +

      核心提示: 在前置过滤器中设定缓存.

      -
      before do
      -  cache_control :public, :must_revalidate, :max_age => 60
      -end
      -
      + +
      +
      +
      before do
      +  cache_control :public, :must_revalidate, :max_age => 60
      +end
      +
      +
      +
      +

      如果你正在用 expires 辅助方法设定对应的消息头 Cache-Control 会自动设定:

      -
      before do
      -  expires 500, :public, :must_revalidate
      -end
      -
      + +
      +
      +
      before do
      +  expires 500, :public, :must_revalidate
      +end
      +
      +
      +
      +

      为了合适地使用缓存,你应该考虑使用 etaglast_modified方法。. 推荐在执行繁重任务*之前*使用这些helpers, 他们会立刻发送响应,如果客户端在缓存中已经有了当前版本。

      -
      get '/article/:id' do
      -  @article = Article.find params[:id]
      -  last_modified @article.updated_at
      -  etag @article.sha1
      -  erb :article
      -end
      -
      + +
      +
      +
      get '/article/:id' do
      +  @article = Article.find params[:id]
      +  last_modified @article.updated_at
      +  etag @article.sha1
      +  erb :article
      +end
      +
      +
      +
      +

      使用 weak ETag 也是有可能的:

      -
      etag @article.sha1, :weak
      -
      + +
      +
      +
      etag @article.sha1, :weak
      +
      +
      +
      +

      这些辅助方法并不会为你做任何缓存,而是将必要的信息传送给你的缓存 如果你在寻找缓存的快速解决方案,试试 rack-cache:

      -
      require "rack/cache"
      -require "sinatra"
       
      -use Rack::Cache
      +
      +
      +
      require "rack/cache"
      +require "sinatra"
      +
      +use Rack::Cache
      +
      +get '/' do
      +  cache_control :public, :max_age => 36000
      +  sleep 5
      +  "hello"
      +end
      +
      +
      +
      -get '/' do - cache_control :public, :max_age => 36000 - sleep 5 - "hello" -end -

      发送文件

      为了发送文件,你可以使用 send_file 辅助方法:

      -
      get '/' do
      -  send_file 'foo.png'
      -end
      -
      + +
      +
      +
      get '/' do
      +  send_file 'foo.png'
      +end
      +
      +
      +
      +

      也可以带一些选项:

      -
      send_file 'foo.png', :type => :jpg
      -
      + +
      +
      +
      send_file 'foo.png', :type => :jpg
      +
      +
      +
      +

      可用的选项有:

      @@ -1087,92 +1659,140 @@

      访问请求对象

      传入的请求对象可以在请求层(过滤器,路由,错误处理) 通过 request 方法被访问:

      -
      # 在 http://example.com/example 上运行的应用
      -get '/foo' do
      -  request.body              # 被客户端设定的请求体(见下)
      -  request.scheme            # "http"
      -  request.script_name       # "/example"
      -  request.path_info         # "/foo"
      -  request.port              # 80
      -  request.request_method    # "GET"
      -  request.query_string      # ""
      -  request.content_length    # request.body的长度
      -  request.media_type        # request.body的媒体类型
      -  request.host              # "example.com"
      -  request.get?              # true (其他动词也具有类似方法)
      -  request.form_data?        # false
      -  request["SOME_HEADER"]    # SOME_HEADER header的值
      -  request.referrer          # 客户端的referrer 或者 '/'
      -  request.user_agent        # user agent (被 :agent 条件使用)
      -  request.cookies           # 浏览器 cookies 哈希
      -  request.xhr?              # 这是否是ajax请求?
      -  request.url               # "http://example.com/example/foo"
      -  request.path              # "/example/foo"
      -  request.ip                # 客户端IP地址
      -  request.secure?           # false(如果是ssl则为true)
      -  request.forwarded?        # true (如果是运行在反向代理之后)
      -  request.env               # Rack中使用的未处理的env哈希
      -end
      -
      + +
      +
      +
      # 在 http://example.com/example 上运行的应用
      +get '/foo' do
      +  request.body              # 被客户端设定的请求体(见下)
      +  request.scheme            # "http"
      +  request.script_name       # "/example"
      +  request.path_info         # "/foo"
      +  request.port              # 80
      +  request.request_method    # "GET"
      +  request.query_string      # ""
      +  request.content_length    # request.body的长度
      +  request.media_type        # request.body的媒体类型
      +  request.host              # "example.com"
      +  request.get?              # true (其他动词也具有类似方法)
      +  request.form_data?        # false
      +  request["SOME_HEADER"]    # SOME_HEADER header的值
      +  request.referrer          # 客户端的referrer 或者 '/'
      +  request.user_agent        # user agent (被 :agent 条件使用)
      +  request.cookies           # 浏览器 cookies 哈希
      +  request.xhr?              # 这是否是ajax请求?
      +  request.url               # "http://example.com/example/foo"
      +  request.path              # "/example/foo"
      +  request.ip                # 客户端IP地址
      +  request.secure?           # false(如果是ssl则为true)
      +  request.forwarded?        # true (如果是运行在反向代理之后)
      +  request.env               # Rack中使用的未处理的env哈希
      +end
      +
      +
      +
      +

      一些选项,例如 script_name 或者 path_info 也是可写的:

      -
      before { request.path_info = "/" }
       
      -get "/" do
      -  "all requests end up here"
      -end
      -
      +
      +
      +
      before { request.path_info = "/" }
      +
      +get "/" do
      +  "all requests end up here"
      +end
      +
      +
      +
      +

      request.body 是一个IO或者StringIO对象:

      -
      post "/api" do
      -  request.body.rewind  # 如果已经有人读了它
      -  data = JSON.parse request.body.read
      -  "Hello #{data['name']}!"
      -end
      -
      + +
      +
      +
      post "/api" do
      +  request.body.rewind  # 如果已经有人读了它
      +  data = JSON.parse request.body.read
      +  "Hello #{data['name']}!"
      +end
      +
      +
      +
      +

      附件

      你可以使用 attachment 辅助方法来告诉浏览器响应 应当被写入磁盘而不是在浏览器中显示。

      -
      get '/' do
      +
      +
      +
      +
      get '/' do
         attachment
      -  "store it!"
      -end
      -
      + "store it!" +end +
      + + +

      你也可以传递一个文件名:

      -
      get '/' do
      -  attachment "info.txt"
      -  "store it!"
      -end
      -
      + +
      +
      +
      get '/' do
      +  attachment "info.txt"
      +  "store it!"
      +end
      +
      +
      +
      +

      查找模板文件

      find_template 辅助方法被用于在渲染时查找模板文件:

      -
      find_template settings.views, 'foo', Tilt[:haml] do |file|
      -  puts "could be #{file}"
      -end
      -
      + +
      +
      +
      find_template settings.views, 'foo', Tilt[:haml] do |file|
      +  puts "could be #{file}"
      +end
      +
      +
      +
      +

      这并不是很有用。但是在你需要重载这个方法 来实现你自己的查找机制的时候有用。 比如,如果你想支持多于一个视图目录:

      -
      set :views, ['views', 'templates']
      -
      -helpers do
      -  def find_template(views, name, engine, &block)
      -    Array(views).each { |v| super(v, name, engine, &block) }
      -  end
      -end
      -
      + +
      +
      +
      set :views, ['views', 'templates']
      +
      +helpers do
      +  def find_template(views, name, engine, &block)
      +    Array(views).each { |v| super(v, name, engine, &block) }
      +  end
      +end
      +
      +
      +
      +

      另一个例子是为不同的引擎使用不同的目录:

      -
      set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
      -
      -helpers do
      -  def find_template(views, name, engine, &block)
      -    _, folder = views.detect { |k,v| engine == Tilt[k] }
      -    folder ||= views[:default]
      -    super(folder, name, engine, &block)
      -  end
      -end
      -
      + +
      +
      +
      set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
      +
      +helpers do
      +  def find_template(views, name, engine, &block)
      +    _, folder = views.detect { |k,v| engine == Tilt[k] }
      +    folder ||= views[:default]
      +    super(folder, name, engine, &block)
      +  end
      +end
      +
      +
      +
      +

      你可以很容易地包装成一个扩展然后与他人分享!

      请注意 find_template 并不会检查文件真的存在, @@ -1185,44 +1805,68 @@

      查找模板文件

      配置

      运行一次,在启动的时候,在任何环境下:

      -
      configure do
      -  # setting one option
      -  set :option, 'value'
       
      -  # setting multiple options
      -  set :a => 1, :b => 2
      +
      +
      +
      configure do
      +  # setting one option
      +  set :option, 'value'
      +
      +  # setting multiple options
      +  set :a => 1, :b => 2
       
      -  # same as `set :option, true`
      -  enable :option
      +  # same as `set :option, true`
      +  enable :option
       
      -  # same as `set :option, false`
      -  disable :option
      +  # same as `set :option, false`
      +  disable :option
      +
      +  # you can also have dynamic settings with blocks
      +  set(:css_dir) { File.join(views, 'css') }
      +end
      +
      +
      +
      - # you can also have dynamic settings with blocks - set(:css_dir) { File.join(views, 'css') } -end -

      只当环境 (RACK_ENV environment 变量) 被设定为 :production的时候运行:

      -
      configure :production do
      +
      +
      +
      +
      configure :production do
         ...
      -end
      -
      +end +
      + + +

      当环境被设定为 :production 或者 :test的时候运行:

      -
      configure :production, :test do
      +
      +
      +
      +
      configure :production, :test do
         ...
      -end
      -
      +end +
      + + +

      你可以使用 settings 获得这些配置:

      -
      configure do
      -  set :foo, 'bar'
      -end
       
      -get '/' do
      -  settings.foo? # => true
      -  settings.foo  # => 'bar'
      +
      +
      +
      configure do
      +  set :foo, 'bar'
      +end
      +
      +get '/' do
      +  settings.foo? # => true
      +  settings.foo  # => 'bar'
         ...
      -end
      -
      +end +
      + + +

      可选的设置

      @@ -1232,18 +1876,17 @@

      可选的设置

      如果被禁用,Sinatra会允许使用相对路径重定向, 但是,Sinatra就不再遵守 RFC 2616标准 (HTTP 1.1), 该标准只允许绝对路径重定向。 -

      -

      + +

      如果你的应用运行在一个未恰当设置的反向代理之后, 你需要启用这个选项。注意 url 辅助方法 仍然会生成绝对 URL,除非你传入 false 作为第二参数。 -

      -

      +

      默认禁用。 -

      - +

      +
      add_charsets
      @@ -1253,8 +1896,8 @@

      可选的设置

      你应该添加而不是覆盖这个选项: - settings.add_charsets -

      + settings.add_charsets << "application/foobar" +

      app_file
      @@ -1384,46 +2027,84 @@

      未找到

      当一个 Sinatra::NotFound 错误被抛出的时候, 或者响应状态码是404,not_found 处理器会被调用:

      -
      not_found do
      -  'This is nowhere to be found'
      -end
      -
      + +
      +
      +
      not_found do
      +  'This is nowhere to be found'
      +end
      +
      +
      +
      +

      错误

      error 处理器,在任何路由代码块或者过滤器抛出异常的时候会被调用。 异常对象可以通过sinatra.error Rack 变量获得:

      -
      error do
      -  'Sorry there was a nasty error - ' + env['sinatra.error'].name
      -end
      -
      + +
      +
      +
      error do
      +  'Sorry there was a nasty error - ' + env['sinatra.error'].name
      +end
      +
      +
      +
      +

      自定义错误:

      -
      error MyCustomError do
      -  'So what happened was...' + env['sinatra.error'].message
      -end
      -
      + +
      +
      +
      error MyCustomError do
      +  'So what happened was...' + env['sinatra.error'].message
      +end
      +
      +
      +
      +

      那么,当这个发生的时候:

      -
      get '/' do
      -  raise MyCustomError, 'something bad'
      -end
      -
      + +
      +
      +
      get '/' do
      +  raise MyCustomError, 'something bad'
      +end
      +
      +
      +
      +

      你会得到:

      -
      So what happened was... something bad
      -
      + +
      So what happened was... something bad
      +
      +

      另一种替代方法是,为一个状态码安装错误处理器:

      -
      error 403 do
      -  'Access forbidden'
      -end
      -
      -get '/secret' do
      -  403
      -end
      -
      + +
      +
      +
      error 403 do
      +  'Access forbidden'
      +end
      +
      +get '/secret' do
      +  403
      +end
      +
      +
      +
      +

      或者一个范围:

      -
      error 400..510 do
      -  'Boom'
      -end
      -
      + +
      +
      +
      error 400..510 do
      +  'Boom'
      +end
      +
      +
      +
      +

      在运行在development环境下时,Sinatra会安装特殊的 not_founderror 处理器。

      @@ -1436,24 +2117,36 @@

      Rack 中间件

      监视 并/或 操作HTTP请求/响应以 提供多样类型的常用功能。

      Sinatra 让建立Rack中间件管道异常简单, 通过顶层的 use 方法:

      -
      require 'sinatra'
      -require 'my_custom_middleware'
       
      -use Rack::Lint
      -use MyCustomMiddleware
      +
      +
      +
      require 'sinatra'
      +require 'my_custom_middleware'
      +
      +use Rack::Lint
      +use MyCustomMiddleware
      +
      +get '/hello' do
      +  'Hello World'
      +end
      +
      +
      +
      -get '/hello' do - 'Hello World' -end -

      use 的语义和在 Rack::Builder DSL(在rack文件中最频繁使用)中定义的完全一样。例如,use 方法接受 多个/可变 参数,包括代码块:

      -
      use Rack::Auth::Basic do |username, password|
      -  username == 'admin' && password == 'secret'
      -end
      -
      + +
      +
      +
      use Rack::Auth::Basic do |username, password|
      +  username == 'admin' && password == 'secret'
      +end
      +
      +
      +
      +

      Rack中分布有多样的标准中间件,针对日志, 调试,URL路由,认证和session处理。 Sinatra会自动使用这里面的大部分组件, 所以你一般不需要显示地 use 他们。

      @@ -1463,33 +2156,39 @@

      测试

      Sinatra的测试可以使用任何基于Rack的测试程序库或者框架来编写。 Rack::Test 是推荐候选:

      -
      require 'my_sinatra_app'
      -require 'test/unit'
      -require 'rack/test'
      -
      -class MyAppTest < Test::Unit::TestCase
      -  include Rack::Test::Methods
      -
      -  def app
      -    Sinatra::Application
      -  end
      -
      -  def test_my_default
      -    get '/'
      -    assert_equal 'Hello World!', last_response.body
      -  end
      -
      -  def test_with_params
      -    get '/meet', :name => 'Frank'
      -    assert_equal 'Hello Frank!', last_response.body
      -  end
      -
      -  def test_with_rack_env
      -    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      -    assert_equal "You're using Songbird!", last_response.body
      -  end
      -end
      -
      + +
      +
      +
      require 'my_sinatra_app'
      +require 'test/unit'
      +require 'rack/test'
      +
      +class MyAppTest < Test::Unit::TestCase
      +  include Rack::Test::Methods
      +
      +  def app
      +    Sinatra::Application
      +  end
      +
      +  def test_my_default
      +    get '/'
      +    assert_equal 'Hello World!', last_response.body
      +  end
      +
      +  def test_with_params
      +    get '/meet', :name => 'Frank'
      +    assert_equal 'Hello Frank!', last_response.body
      +  end
      +
      +  def test_with_rack_env
      +    get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
      +    assert_equal "You're using Songbird!", last_response.body
      +  end
      +end
      +
      +
      +
      +

      请注意: 内置的 Sinatra::Test 模块和 Sinatra::TestHarness 类 在 0.9.2 版本已废弃。

      @@ -1503,25 +2202,35 @@

      Sinatra::Base - 中间件,程序库和模块化应用

      并假定了一个微型应用风格的配置 (例如, 单一的应用文件, ./public 和 ./views 目录,日志,异常细节页面,等等)。 这时应该让 Sinatra::Base 走到台前了:

      -
      require 'sinatra/base'
       
      -class MyApp < Sinatra::Base
      -  set :sessions, true
      -  set :foo, 'bar'
      +
      +
      +
      require 'sinatra/base'
      +
      +class MyApp < Sinatra::Base
      +  set :sessions, true
      +  set :foo, 'bar'
      +
      +  get '/' do
      +    'Hello world!'
      +  end
      +end
      +
      +
      +
      - get '/' do - 'Hello world!' - end -end -

      Sinatra::Base子类可用的方法实际上就是通过顶层 DSL 可用的方法。 大部分顶层应用可以通过两个改变转换成Sinatra::Base组件:

        -
      • 你的文件应当引入 sinatra/base 而不是 sinatra; -否则,所有的Sinatra的 DSL 方法将会被引进到 主命名空间。

      • -
      • 把你的应用的路由,错误处理,过滤器和选项放在 -一个Sinatra::Base的子类中。

      • +
      • +

        你的文件应当引入 sinatra/base 而不是 sinatra; +否则,所有的Sinatra的 DSL 方法将会被引进到 主命名空间。

        +
      • +
      • +

        把你的应用的路由,错误处理,过滤器和选项放在 +一个Sinatra::Base的子类中。

        +

      +Sinatra::Base+ 是一张白纸。大部分的选项默认是禁用的, 包含内置的服务器。参见 @@ -1537,71 +2246,111 @@

      模块化 vs. 传统的方式

      和模块化方式相比只有两个缺点:

        -
      • 你对每个Ruby进程只能定义一个Sinatra应用,如果你需要更多, -切换到模块化方式。

      • -
      • 传统方式使用代理方法污染了 Object 。如果你打算 把你的应用封装进一个 -library/gem,转换到模块化方式。

      • +
      • +

        你对每个Ruby进程只能定义一个Sinatra应用,如果你需要更多, +切换到模块化方式。

        +
      • +
      • +

        传统方式使用代理方法污染了 Object 。如果你打算 把你的应用封装进一个 +library/gem,转换到模块化方式。

        +

      没有任何原因阻止你混合模块化和传统方式。

      如果从一种转换到另一种,你需要注意settings中的 一些微小的不同:

      -
      Setting             Classic                 Modular
      +
      +
      Setting             Classic                 Modular
       
       app_file            file loading sinatra    nil
       run                 $0 == app_file          false
       logging             true                    false
       method_override     true                    false
       inline_templates    true                    false
      -
      +
      +

      运行一个模块化应用

      有两种方式运行一个模块化应用,使用 run!来运行:

      -
      # my_app.rb
      -require 'sinatra/base'
       
      -class MyApp < Sinatra::Base
      -  # ... app code here ...
      +
      +
      +
      # my_app.rb
      +require 'sinatra/base'
      +
      +class MyApp < Sinatra::Base
      +  # ... app code here ...
      +
      +  # start the server if ruby file executed directly
      +  run! if app_file == $0
      +end
      +
      +
      +
      - # start the server if ruby file executed directly - run! if app_file == $0 -end -

      运行:

      -
      ruby my_app.rb
      -
      + +
      ruby my_app.rb
      +
      +

      或者使用一个 config.ru,允许你使用任何Rack处理器:

      -
      # config.ru
      -require './my_app'
      -run MyApp
      -
      + +
      +
      +
      # config.ru
      +require './my_app'
      +run MyApp
      +
      +
      +
      +

      运行:

      -
      rackup -p 4567
      -
      + +
      rackup -p 4567
      +
      +

      使用config.ru运行传统方式的应用

      编写你的应用:

      -
      # app.rb
      -require 'sinatra'
       
      -get '/' do
      -  'Hello world!'
      -end
      -
      +
      +
      +
      # app.rb
      +require 'sinatra'
      +
      +get '/' do
      +  'Hello world!'
      +end
      +
      +
      +
      +

      加入相应的 config.ru:

      -
      require './app'
      -run Sinatra::Application
      -
      + +
      +
      +
      require './app'
      +run Sinatra::Application
      +
      +
      +
      +

      什么时候用 config.ru?

      以下情况你可能需要使用 config.ru:

        -
      • 你要使用不同的 Rack 处理器部署 (Passenger, Unicorn, Heroku, …).

      • -
      • 你想使用一个或者多个 Sinatra::Base的子类.

      • -
      • 你只想把Sinatra当作中间件使用,而不是端点。

      • +
      • +

        你要使用不同的 Rack 处理器部署 (Passenger, Unicorn, Heroku, …).

        +
      • +
      • +

        你想使用一个或者多个 Sinatra::Base的子类.

        +
      • +
      • +

        你只想把Sinatra当作中间件使用,而不是端点。

        +

      你并不需要切换到config.ru仅仅因为你切换到模块化方式, 你同样不需要切换到模块化方式, 仅仅因为要运行 config.ru.

      @@ -1613,35 +2362,41 @@

      把Sinatra当成中间件来使用

      应用程序都可以反过来自身被当作中间件,被加在任何Rack端点前面。 这个端点可以是任何Sinatra应用,或者任何基于Rack的应用程序 (Rails/Ramaze/Camping/…)。

      -
      require 'sinatra/base'
      -
      -class LoginScreen < Sinatra::Base
      -  enable :sessions
      -
      -  get('/login') { haml :login }
      -
      -  post('/login') do
      -    if params[:name] = 'admin' and params[:password] = 'admin'
      -      session['user_name'] = params[:name]
      -    else
      -      redirect '/login'
      -    end
      -  end
      -end
      -
      -class MyApp < Sinatra::Base
      -  # 在前置过滤器前运行中间件
      -  use LoginScreen
      -
      -  before do
      -    unless session['user_name']
      -      halt "Access denied, please <a href='/login'>login</a>."
      -    end
      -  end
      -
      -  get('/') { "Hello #{session['user_name']}." }
      -end
      -
      + +
      +
      +
      require 'sinatra/base'
      +
      +class LoginScreen < Sinatra::Base
      +  enable :sessions
      +
      +  get('/login') { haml :login }
      +
      +  post('/login') do
      +    if params[:name] = 'admin' and params[:password] = 'admin'
      +      session['user_name'] = params[:name]
      +    else
      +      redirect '/login'
      +    end
      +  end
      +end
      +
      +class MyApp < Sinatra::Base
      +  # 在前置过滤器前运行中间件
      +  use LoginScreen
      +
      +  before do
      +    unless session['user_name']
      +      halt "Access denied, please <a href='/login'>login</a>."
      +    end
      +  end
      +
      +  get('/') { "Hello #{session['user_name']}." }
      +end
      +
      +
      +
      +

      变量域和绑定

      @@ -1658,29 +2413,47 @@

      应用/类 变量域

      只有单一的应用类。

      通过 `set` 创建的选项是类层面的方法:

      -
      class MyApp < Sinatra::Base
      -  # 嘿,我在应用变量域!
      -  set :foo, 42
      -  foo # => 42
      -
      -  get '/foo' do
      -    # 嘿,我不再处于应用变量域了!
      -  end
      -end
      -
      + +
      +
      +
      class MyApp < Sinatra::Base
      +  # 嘿,我在应用变量域!
      +  set :foo, 42
      +  foo # => 42
      +
      +  get '/foo' do
      +    # 嘿,我不再处于应用变量域了!
      +  end
      +end
      +
      +
      +
      +

      在下列情况下你将拥有应用变量域的绑定:

        -
      • 在应用类中

      • -
      • 在扩展中定义的方法

      • -
      • 传递给 `helpers` 的代码块

      • -
      • 用作`set`值的过程/代码块

      • +
      • +

        在应用类中

        +
      • +
      • +

        在扩展中定义的方法

        +
      • +
      • +

        传递给 `helpers` 的代码块

        +
      • +
      • +

        用作`set`值的过程/代码块

        +

      你可以访问变量域对象(就是应用类)就像这样:

        -
      • 通过传递给代码块的对象 (configure { |c| ... })

      • -
      • 在请求变量域中使用`settings`

      • +
      • +

        通过传递给代码块的对象 (configure { |c| ... })

        +
      • +
      • +

        在请求变量域中使用`settings`

        +

      请求/实例 变量域

      @@ -1690,28 +2463,42 @@

      请求/实例 变量域

      `request` 和 `session` 对象,或者调用渲染方法比如 `erb` 或者 `haml`。你可以在请求变量域当中通过`settings`辅助方法 访问应用变量域:

      -
      class MyApp < Sinatra::Base
      -  # 嘿,我在应用变量域!
      -  get '/define_route/:name' do
      -    # 针对 '/define_route/:name' 的请求变量域
      -    @value = 42
      -
      -    settings.get("/#{params[:name]}") do
      -      # 针对 "/#{params[:name]}" 的请求变量域
      -      @value # => nil (并不是相同的请求)
      -    end
      -
      -    "Route defined!"
      -  end
      -end
      -
      + +
      +
      +
      class MyApp < Sinatra::Base
      +  # 嘿,我在应用变量域!
      +  get '/define_route/:name' do
      +    # 针对 '/define_route/:name' 的请求变量域
      +    @value = 42
      +
      +    settings.get("/#{params[:name]}") do
      +      # 针对 "/#{params[:name]}" 的请求变量域
      +      @value # => nil (并不是相同的请求)
      +    end
      +
      +    "Route defined!"
      +  end
      +end
      +
      +
      +
      +

      在以下情况将获得请求变量域:

        -
      • get/head/post/put/delete 代码块

      • -
      • 前置/后置 过滤器

      • -
      • 辅助方法

      • -
      • 模板/视图

      • +
      • +

        get/head/post/put/delete 代码块

        +
      • +
      • +

        前置/后置 过滤器

        +
      • +
      • +

        辅助方法

        +
      • +
      • +

        模板/视图

        +

      代理变量域

      @@ -1726,8 +2513,12 @@

      代理变量域

      在以下情况将获得代理变量域:

        -
      • 顶层的绑定,如果你做过 require "sinatra"

      • -
      • 在扩展了 `Sinatra::Delegator` mixin的对象

      • +
      • +

        顶层的绑定,如果你做过 require "sinatra"

        +
      • +
      • +

        在扩展了 `Sinatra::Delegator` mixin的对象

        +

      自己在这里看一下代码: Sinatra::Delegator mixin @@ -1738,16 +2529,20 @@

      代理变量域

      命令行

      Sinatra 应用可以被直接运行:

      -
      ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
      -
      + +
      ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
      +
      +

      选项是:

      -
      -h # help
      +
      +
      -h # help
       -p # 设定端口 (默认是 4567)
       -o # 设定主机名 (默认是 0.0.0.0)
       -e # 设定环境 (默认是 development)
       -s # 限定 rack 服务器/处理器 (默认是 thin)
       -x # 打开互斥锁 (默认是 off)
      -
      +
      +

      必要条件

      @@ -1796,11 +2591,21 @@

      必要条件

      下面的 Ruby 实现没有被官方支持, 但是已知可以运行 Sinatra:

        -
      • JRuby 和 Rubinius 老版本

      • -
      • MacRuby

      • -
      • Maglev

      • -
      • IronRuby

      • -
      • Ruby 1.9.0 and 1.9.1

      • +
      • +

        JRuby 和 Rubinius 老版本

        +
      • +
      • +

        MacRuby

        +
      • +
      • +

        Maglev

        +
      • +
      • +

        IronRuby

        +
      • +
      • +

        Ruby 1.9.0 and 1.9.1

        +

      不被官方支持的意思是,如果在不被支持的平台上有运行错误, 我们假定不是我们的问题,而是平台的问题。

      @@ -1812,13 +2617,17 @@

      紧追前沿

      如果你喜欢使用 Sinatra 的最新鲜的代码,请放心的使用 master 分支来运行你的程序,它会非常的稳定。

      -
      cd myapp
      +
      +
      cd myapp
       git clone git://github.com/sinatra/sinatra.git
       ruby -Isinatra/lib myapp.rb
      -
      +
      +

      我们也会不定期的发布预发布gems,所以你也可以运行

      -
      gem install sinatra --pre
      -
      + +
      gem install sinatra --pre
      +
      +

      来获得最新的特性。

      @@ -1828,59 +2637,89 @@

      通过Bundler

      Bundler 是推荐的方式。

      首先,安装bundler,如果你还没有安装:

      -
      gem install bundler
      -
      + +
      gem install bundler
      +
      +

      然后,在你的项目目录下,创建一个 Gemfile:

      -
      source :rubygems
      -gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
       
      -# 其他的依赖关系
      -gem 'haml'                    # 举例,如果你想用haml
      -gem 'activerecord', '~> 3.0'  # 也许你还需要 ActiveRecord 3.x
      -
      +
      +
      +
      source :rubygems
      +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
      +
      +# 其他的依赖关系
      +gem 'haml'                    # 举例,如果你想用haml
      +gem 'activerecord', '~> 3.0'  # 也许你还需要 ActiveRecord 3.x
      +
      +
      +
      +

      请注意在这里你需要列出你的应用的所有依赖关系。 Sinatra的直接依赖关系 (Rack and Tilt) 将会, 自动被Bundler获取和添加。

      现在你可以像这样运行你的应用:

      -
      bundle exec ruby myapp.rb
      -
      + +
      bundle exec ruby myapp.rb
      +
      +

      使用自己的

      创建一个本地克隆并通过 sinatra/lib 目录运行你的应用, 通过 $LOAD_PATH:

      -
      cd myapp
      +
      +
      cd myapp
       git clone git://github.com/sinatra/sinatra.git
       ruby -Isinatra/lib myapp.rb
      -
      +
      +

      为了在未来更新 Sinatra 源代码:

      -
      cd myapp/sinatra
      +
      +
      cd myapp/sinatra
       git pull
      -
      +
      +

      全局安装

      你可以自行编译 gem :

      -
      git clone git://github.com/sinatra/sinatra.git
      +
      +
      git clone git://github.com/sinatra/sinatra.git
       cd sinatra
       rake sinatra.gemspec
       rake install
      -
      +
      +

      如果你以root身份安装 gems,最后一步应该是

      -
      sudo rake install
      -
      + +
      sudo rake install
      +
      +

      更多

      From 7d7770ba14f9f775eb9d2ba10230e10b290c0e54 Mon Sep 17 00:00:00 2001 From: Kashyap Date: Tue, 12 Mar 2013 23:47:01 +0530 Subject: [PATCH 2/2] deleted book.html and changes.txt since they are now linked --- _includes/CHANGES.txt | 508 ------------------ _includes/book.html | 1176 ----------------------------------------- 2 files changed, 1684 deletions(-) delete mode 100644 _includes/CHANGES.txt delete mode 100644 _includes/book.html diff --git a/_includes/CHANGES.txt b/_includes/CHANGES.txt deleted file mode 100644 index 5177ff39..00000000 --- a/_includes/CHANGES.txt +++ /dev/null @@ -1,508 +0,0 @@ -= 1.0 / 2010-01-28 (prerelease) - - * It's now possible to register blocks to run after each request using - after filters. After filters run at the end of each request, after - routes and error handlers. (Jimmy Schementi) - - * Sinatra now uses Tilt <http://github.com/rtomayko/tilt> for rendering - templates. This adds support for template caching, consistent - template backtraces, and support for new template engines, like - mustache and liquid. (Ryan Tomayko) - - * ERB, Erubis, and Haml templates are now compiled the first time - they're rendered instead of being string eval'd on each invocation. - Benchmarks show a 5x-10x improvement in render time. This also - reduces the number of objects created, decreasing pressure on Ruby's - GC. (Ryan Tomayko) - - * New 'settings' method gives access to options in both class and request - scopes. This replaces the 'options' method. (Chris Wanstrath) - - * New boolean 'reload_templates' setting controls whether template files - are reread from disk and recompiled on each request. Template read/compile - is cached by default in all environments except development. (Ryan Tomayko) - - * New 'erubis' helper method for rendering ERB template with Erubis. The - erubis gem is required. (Dylan Egan) - - * New 'cache_control' helper method provides a convenient way of - setting the Cache-Control response header. Takes a variable number - of boolean directives followed by a hash of value directives, like - this: cache_control :public, :must_revalidate, :max_age => 60 - (Ryan Tomayko) - - * New 'expires' helper method is like cache_control but takes an - integer number of seconds or Time object: - expires 300, :public, :must_revalidate - (Ryan Tomayko) - - * New request.secure? method for checking for an SSL connection. - (Adam Wiggins) - - * Sinatra apps can now be run with a `-o <addr>` argument to specify - the address to bind to. (Ryan Tomayko) - - * Rack::Session::Cookie is now added to the middleware pipeline when - running in test environments if the :sessions option is set. - (Simon Rozet) - - * Route handlers, before filters, templates, error mappings, and - middleware are now resolved dynamically up the inheritance hierarchy - when needed instead of duplicating the superclass's version when - a new Sinatra::Base subclass is created. This should fix a variety - of issues with extensions that need to add any of these things - to the base class. (Ryan Tomayko) - - * Exception error handlers always override the raise_errors option now. - Previously, all exceptions would be raised outside of the application - when the raise_errors option was enabled, even if an error handler was - defined for that exception. The raise_errors option now controls - whether unhandled exceptions are raised (enabled) or if a generic 500 - error is returned (disabled). (Ryan Tomayko) - - * The X-Cascade response header is set to 'pass' when no matching route - is found or all routes pass. (Josh Peek) - - * Filters do not run when serving static files anymore. (Ryan Tomayko) - -The following Sinatra features have been obsoleted (removed entirely) in -the 1.0 release: - - * The `sinatra/test` library is obsolete. This includes the `Sinatra::Test` - module, the `Sinatra::TestHarness` class, and the `get_it`, `post_it`, - `put_it`, `delete_it`, and `head_it` helper methods. The - [`Rack::Test` library](http://gitrdoc.com/brynary/rack-test) should - be used instead. - - * Test framework specific libraries (`sinatra/test/spec`, - `sinatra/test/bacon`,`sinatra/test/rspec`, etc.) are obsolete. See - http://www.sinatrarb.com/testing.html for instructions on setting up a - testing environment under each of these frameworks. - - * `Sinatra::Default` is obsolete; use `Sinatra::Base` instead. - `Sinatra::Base` acts more like `Sinatra::Default` in development mode. - For example, static file serving and sexy development error pages are - enabled by default. - - * Auto-requiring template libraries in the `erb`, `builder`, `haml`, - and `sass` methods is obsolete due to thread-safety issues. You must - require the template libraries explicitly in your app. - - * The `:views_directory` option to rendering methods is obsolete; use - `:views` instead. - - * The `:haml` and `:sass` options to rendering methods are obsolete. - Template engine options should be passed in the second Hash argument - instead. - - * The `use_in_file_templates` method is obsolete. Use - `enable :inline_templates` or `set :inline_templates, 'path/to/file'` - - * The 'media_type' helper method is obsolete. Use 'mime_type' instead. - - * The 'mime' main and class method is obsolete. Use 'mime_type' instead. - - * The request-level `send_data` method is no longer supported. - - * The `Sinatra::Event` and `Sinatra::EventContext` classes are no longer - supported. This may effect extensions written for versions prior to 0.9.2. - See [Writing Sinatra Extensions](http://www.sinatrarb.com/extensions.html) - for the officially supported extensions API. - - * The `set_option` and `set_options` methods are obsolete; use `set` - instead. - - * The `:env` setting (`settings.env`) is obsolete; use `:environment` - instead. - - * The request level `stop` method is obsolete; use `halt` instead. - - * The request level `entity_tag` method is obsolete; use `etag` - instead. - - * The request level `headers` method (HTTP response headers) is obsolete; - use `response['Header-Name']` instead. - - * `Sinatra.application` is obsolete; use `Sinatra::Application` instead. - - * Using `Sinatra.application = nil` to reset an application is obsolete. - This should no longer be necessary. - - * Using `Sinatra.default_options` to set base configuration items is - obsolete; use `Sinatra::Base.set(key, value)` instead. - - * The `Sinatra::ServerError` exception is obsolete. All exceptions raised - within a request are now treated as internal server errors and result in - a 500 response status. - - * The `:methodoverride' option to enable/disable the POST _method hack is - obsolete; use `:method_override` instead. - -= 0.9.2 / 2009-05-18 - - * This version is compatible with Rack 1.0. [Rein Henrichs] - - * The development-mode unhandled exception / error page has been - greatly enhanced, functionally and aesthetically. The error - page is used when the :show_exceptions option is enabled and an - exception propagates outside of a route handler or before filter. - [Simon Rozet / Matte Noble / Ryan Tomayko] - - * Backtraces that move through templates now include filenames and - line numbers where possible. [#51 / S. Brent Faulkner] - - * All templates now have an app-level option for setting default - template options (:haml, :sass, :erb, :builder). The app-level - option value must be a Hash if set and is merged with the - template options specified to the render method (Base#haml, - Base#erb, Base#builder). [S. Brent Faulkner, Ryan Tomayko] - - * The method signature for all template rendering methods has - been unified: "def engine(template, options={}, locals={})". - The options Hash now takes the generic :views, :layout, and - :locals options but also any template-specific options. The - generic options are removed before calling the template specific - render method. Locals may be specified using either the - :locals key in the options hash or a second Hash option to the - rendering method. [#191 / Ryan Tomayko] - - * The receiver is now passed to "configure" blocks. This - allows for the following idiom in top-level apps: - configure { |app| set :foo, app.root + '/foo' } - [TJ Holowaychuck / Ryan Tomayko] - - * The "sinatra/test" lib is deprecated and will be removed in - Sinatra 1.0. This includes the Sinatra::Test module and - Sinatra::TestHarness class in addition to all the framework - test helpers that were deprecated in 0.9.1. The Rack::Test - lib should be used instead: http://gitrdoc.com/brynary/rack-test - [#176 / Simon Rozet] - - * Development mode source file reloading has been removed. The - "shotgun" (http://rtomayko.github.com/shotgun/) program can be - used to achieve the same basic functionality in most situations. - Passenger users should use the "tmp/always_restart.txt" - file (http://tinyurl.com/c67o4h). [#166 / Ryan Tomayko] - - * Auto-requiring template libs in the erb, builder, haml, and - sass methods is deprecated due to thread-safety issues. You must - require the template libs explicitly in your app file. [Simon Rozet] - - * A new Sinatra::Base#route_missing method was added. route_missing - is sent when no route matches the request or all route handlers - pass. The default implementation forwards the request to the - downstream app when running as middleware (i.e., "@app" is - non-nil), or raises a NotFound exception when no downstream app - is defined. Subclasses can override this method to perform custom - route miss logic. [Jon Crosby] - - * A new Sinatra::Base#route_eval method was added. The method - yields to the block and throws :halt with the result. Subclasses - can override this method to tap into the route execution logic. - [TJ Holowaychuck] - - * Fix the "-x" (enable request mutex / locking) command line - argument. Passing -x now properly sets the :lock option. - [S. Brent Faulkner, Ryan Tomayko] - - * Fix writer ("foo=") and predicate ("foo?") methods in extension - modules not being added to the registering class. - [#172 / Pat Nakajima] - - * Fix in-file templates when running alongside activesupport and - fatal errors when requiring activesupport before sinatra - [#178 / Brian Candler] - - * Fix various issues running on Google AppEngine. - [Samuel Goebert, Simon Rozet] - - * Fix in-file templates __END__ detection when __END__ exists with - other stuff on a line [Yoji Shidara] - -= 0.9.1.1 / 2009-03-09 - - * Fix directory traversal vulnerability in default static files - route. See [#177] for more info. - -= 0.9.1 / 2009-03-01 - - * Sinatra now runs under Ruby 1.9.1 [#61] - - * Route patterns (splats, :named, or Regexp captures) are now - passed as arguments to the block. [#140] - - * The "helpers" method now takes a variable number of modules - along with the normal block syntax. [#133] - - * New request-level #forward method for middleware components: passes - the env to the downstream app and merges the response status, headers, - and body into the current context. [#126] - - * Requests are now automatically forwarded to the downstream app when - running as middleware and no matching route is found or all routes - pass. - - * New simple API for extensions/plugins to add DSL-level and - request-level methods. Use Sinatra.register(mixin) to extend - the DSL with all public methods defined in the mixin module; - use Sinatra.helpers(mixin) to make all public methods defined - in the mixin module available at the request level. [#138] - See http://www.sinatrarb.com/extensions.html for details. - - * Named parameters in routes now capture the "." character. This makes - routes like "/:path/:filename" match against requests like - "/foo/bar.txt"; in this case, "params[:filename]" is "bar.txt". - Previously, the route would not match at all. - - * Added request-level "redirect back" to redirect to the referring - URL. - - * Added a new "clean_trace" option that causes backtraces dumped - to rack.errors and displayed on the development error page to - omit framework and core library backtrace lines. The option is - enabled by default. [#77] - - * The ERB output buffer is now available to helpers via the @_out_buf - instance variable. - - * It's now much easier to test sessions in unit tests by passing a - ":session" option to any of the mock request methods. e.g., - get '/', {}, :session => { 'foo' => 'bar' } - - * The testing framework specific files ('sinatra/test/spec', - 'sinatra/test/bacon', 'sinatra/test/rspec', etc.) have been deprecated. - See http://sinatrarb.com/testing.html for instructions on setting up - a testing environment with these frameworks. - - * The request-level #send_data method from Sinatra 0.3.3 has been added - for compatibility but is deprecated. - - * Fix :provides causing crash on any request when request has no - Accept header [#139] - - * Fix that ERB templates were evaluated twice per "erb" call. - - * Fix app-level middleware not being run when the Sinatra application is - run as middleware. - - * Fixed some issues with running under Rack's CGI handler caused by - writing informational stuff to stdout. - - * Fixed that reloading was sometimes enabled when starting from a - rackup file [#110] - - * Fixed that "." in route patterns erroneously matched any character - instead of a literal ".". [#124] - -= 0.9.0.4 / 2009-01-25 - - * Using halt with more than 1 args causes ArgumentError [#131] - * using halt in a before filter doesn't modify response [#127] - * Add deprecated Sinatra::EventContext to unbreak plugins [#130] - * Give access to GET/POST params in filters [#129] - * Preserve non-nested params in nested params hash [#117] - * Fix backtrace dump with Rack::Lint [#116] - -= 0.9.0.3 / 2009-01-21 - - * Fall back on mongrel then webrick when thin not found. [#75] - * Use :environment instead of :env in test helpers to - fix deprecation warnings coming from framework. - * Make sinatra/test/rspec work again [#113] - * Fix app_file detection on windows [#118] - * Fix static files with Rack::Lint in pipeline [#121] - -= 0.9.0.2 / 2009-01-18 - - * Halting a before block should stop processing of routes [#85] - * Fix redirect/halt in before filters [#85] - -= 0.9.0 / 2009-01-18 - - * Works with and requires Rack >= 0.9.1 - - * Multiple Sinatra applications can now co-exist peacefully within a - single process. The new "Sinatra::Base" class can be subclassed to - establish a blank-slate Rack application or middleware component. - Documentation on using these features is forth-coming; the following - provides the basic gist: http://gist.github.com/38605 - - * Parameters with subscripts are now parsed into a nested/recursive - Hash structure. e.g., "post[title]=Hello&post[body]=World" yields - params: {'post' => {'title' => 'Hello', 'body' => 'World'}}. - - * Regular expressions may now be used in route pattens; captures are - available at "params[:captures]". - - * New ":provides" route condition takes an array of mime types and - matches only when an Accept request header is present with a - corresponding type. [cypher] - - * New request-level "pass" method; immediately exits the current block - and passes control to the next matching route. - - * The request-level "body" method now takes a block; evaluation is - deferred until an attempt is made to read the body. The block must - return a String or Array. - - * New "route conditions" system for attaching rules for when a route - matches. The :agent and :host route options now use this system. - - * New "dump_errors" option controls whether the backtrace is dumped to - rack.errors when an exception is raised from a route. The option is - enabled by default for top-level apps. - - * Better default "app_file", "root", "public", and "views" location - detection; changes to "root" and "app_file" automatically cascade to - other options that depend on them. - - * Error mappings are now split into two distinct layers: exception - mappings and custom error pages. Exception mappings are registered - with "error(Exception)" and are run only when the app raises an - exception. Custom error pages are registered with "error(status_code)", - where "status_code" is an integer, and are run any time the response - has the status code specified. It's also possible to register an error - page for a range of status codes: "error(500..599)". - - * In-file templates are now automatically imported from the file that - requires 'sinatra'. The use_in_file_templates! method is still available - for loading templates from other files. - - * Sinatra's testing support is no longer dependent on Test::Unit. Requiring - 'sinatra/test' adds the Sinatra::Test module and Sinatra::TestHarness - class, which can be used with any test framework. The 'sinatra/test/unit', - 'sinatra/test/spec', 'sinatra/test/rspec', or 'sinatra/test/bacon' files - can be required to setup a framework-specific testing environment. See the - README for more information. - - * Added support for Bacon (test framework). The 'sinatra/test/bacon' file - can be required to setup Sinatra test helpers on Bacon::Context. - - * Deprecated "set_option" and "set_options"; use "set" instead. - - * Deprecated the "env" option ("options.env"); use "environment" instead. - - * Deprecated the request level "stop" method; use "halt" instead. - - * Deprecated the request level "entity_tag" method; use "etag" instead. - Both "entity_tag" and "etag" were previously supported. - - * Deprecated the request level "headers" method (HTTP response headers); - use "response['Header-Name']" instead. - - * Deprecated "Sinatra.application"; use "Sinatra::Application" instead. - - * Deprecated setting Sinatra.application = nil to reset an application. - This should no longer be necessary. - - * Deprecated "Sinatra.default_options"; use - "Sinatra::Default.set(key, value)" instead. - - * Deprecated the "ServerError" exception. All Exceptions are now - treated as internal server errors and result in a 500 response - status. - - * Deprecated the "get_it", "post_it", "put_it", "delete_it", and "head_it" - test helper methods. Use "get", "post", "put", "delete", and "head", - respectively, instead. - - * Removed Event and EventContext classes. Applications are defined in a - subclass of Sinatra::Base; each request is processed within an - instance. - -= 0.3.3 / 2009-01-06 - - * Pin to Rack 0.4.0 (this is the last release on Rack 0.4) - - * Log unhandled exception backtraces to rack.errors. - - * Use RACK_ENV environment variable to establish Sinatra - environment when given. Thin sets this when started with - the -e argument. - - * BUG: raising Sinatra::NotFound resulted in a 500 response - code instead of 404. - - * BUG: use_in_file_templates! fails with CR/LF (#45) - - * BUG: Sinatra detects the app file and root path when run under - thin/passenger. - -= 0.3.2 - - * BUG: Static and send_file read entire file into String before - sending. Updated to stream with 8K chunks instead. - - * Rake tasks and assets for building basic documentation website. - See http://sinatra.rubyforge.org - - * Various minor doc fixes. - -= 0.3.1 - - * Unbreak optional path parameters [jeremyevans] - -= 0.3.0 - - * Add sinatra.gemspec w/ support for github gem builds. Forks can now - enable the build gem option in github to get free username-sinatra.gem - builds: gem install username-sinatra.gem --source=http://gems.github.com/ - - * Require rack-0.4 gem; removes frozen rack dir. - - * Basic RSpec support; require 'sinatra/test/rspec' instead of - 'sinatra/test/spec' to use. [avdi] - - * before filters can modify request environment vars used for - routing (e.g., PATH_INFO, REQUEST_METHOD, etc.) for URL rewriting - type functionality. - - * In-file templates now uses @@ instead of ## as template separator. - - * Top-level environment test predicates: development?, test?, production? - - * Top-level "set", "enable", and "disable" methods for tweaking - app options. [rtomayko] - - * Top-level "use" method for building Rack middleware pipelines - leading to app. See README for usage. [rtomayko] - - * New "reload" option - set false to disable reloading in development. - - * New "host" option - host/ip to bind to [cschneid] - - * New "app_file" option - override the file to reload in development - mode [cschneid] - - * Development error/not_found page cleanup [sr, adamwiggins] - - * Remove a bunch of core extensions (String#to_param, String#from_param, - Hash#from_params, Hash#to_params, Hash#symbolize_keys, Hash#pass) - - * Various grammar and formatting fixes to README; additions on - community and contributing [cypher] - - * Build RDoc using Hanna template: http://sinatrarb.rubyforge.org/api - - * Specs, documentation and fixes for splat'n routes [vic] - - * Fix whitespace errors across all source files. [rtomayko] - - * Fix streaming issues with Mongrel (body not closed). [bmizerany] - - * Fix various issues with environment not being set properly (configure - blocks not running, error pages not registering, etc.) [cypher] - - * Fix to allow locals to be passed to ERB templates [cschneid] - - * Fix locking issues causing random errors during reload in development. - - * Fix for escaped paths not resolving static files [Matthew Walker] - -= 0.2.1 - - * File upload fix and minor tweaks. - -= 0.2.0 - - * Initial gem release of 0.2 codebase. diff --git a/_includes/book.html b/_includes/book.html deleted file mode 100644 index b7a89d26..00000000 --- a/_includes/book.html +++ /dev/null @@ -1,1176 +0,0 @@ - - -
      -

      Introduction

      - -

      What is Sinatra?

      - -

      Sinatra is a Domain Specific Language (DSL) for quickly creating web-applications in Ruby.

      - -

      It keeps a minimal feature set, leaving the developer to use the tools that best suit them and their application.

      - -

      It doesn’t assume much about your application, apart from that:

      - -
        -
      • it will be written in Ruby programming language
      • - -
      • it will have URLs
      • -
      - -

      In Sinatra, you can write short ad hoc applications or mature, larger application with the same easiness. (See section “Real World Applications” later in this book.)

      - -

      You can use the power of various Rubygems and other libraries for Ruby available.

      - -

      Sinatra really shines when used for experiments and application mock-ups or for creating a quick interface for your code.

      - -

      It isn’t a typical Model-View-Controller framework, but ties specific URL directly to relevant Ruby code and returns its output in response. It does enable you, however, to write clean, properly organized applications: separating views from application code, for instance.

      - -

      Installation

      - -

      The simplest way to obtain Sinatra is through Rubygems

      - -
      $ sudo gem install sinatra
      - -

      Dependencies

      - -

      Sinatra depends on the Rack gem (http://rack.rubyforge.org).

      - -

      For optimal experience, you should also install the Haml (http://haml.hamptoncatlin.com) and Builder gem (http://builder.rubyforge.org), which simplifies working with views.

      - -
      $ sudo gem install builder haml
      - -

      Living on the Edge

      - -

      The edge version of Sinatra lives in its Git repository, available at http://github.com/sinatra/sinatra/tree/master.

      - -

      You can use the edge version to try new functionality or to contribute to the framework. You need to have Git version control software installed (http://www.git-scm.com). Then follow these steps:

      - -
        -
      1. cd where/you/keep/your/projects
      2. - -
      3. git clone git://github.com/sinatra/sinatra.git
      4. - -
      5. cd sinatra
      6. - -
      7. cd your_project
      8. - -
      9. ln -s ../sinatra
      10. -
      - -

      Then add this to your application:

      - -
      $:.unshift File.dirname(__FILE__) + '/sinatra/lib'
      -require 'sinatra'
      - -

      You can check the version you are running by adding this route

      - -
      get '/about' do
      -  "I'm running on Version " + Sinatra::VERSION
      -end
      - -

      and loading http://localhost:4567/about in your browser.

      - -

      Hello World Application

      - -

      Sinatra is installed and you’re done eating cake, how about making your first application?

      - -
      # hello_world.rb
      -require 'rubygems'
      -require 'sinatra'
      -
      -get '/' do
      -  "Hello world, it's #{Time.now} at the server!"
      -end
      - -

      Run this application by $ ruby hello_world.rb and load http://localhost:4567 in your browser.

      - -

      As you can see, Sinatra doesn’t force you to setup much infrastructure: a request to some URL (root URL in this case) evaluates some Ruby code and returns some text in response.

      - -

      Real World Applications in Sinatra

      - -

      Github Services

      - -

      Git hosting provider Github uses Sinatra for post-receive hooks, calling user specified services/URLs, whenever someone pushes to their repository:

      - - - -

      Git Wiki

      - -

      Git Wiki is minimal Wiki engine powered by Sinatra and Git. See also various forks with additional functionality.

      - - - -

      Integrity

      - -

      Integrity is small and clean continuous integration service using Sinatra, watching for failing builds of your codebase and notifying you by various channels.

      - - - -

      Seinfeld Calendar

      - -

      Seinfeld Calendar is a fun application tracking your contributions to open-source projects, displaying your “streaks”, ie. continuous commits to Github repositories.

      - - - -

      About this book

      - -

      This book will assume you have a basic knowledge of the Ruby scripting language and a working Ruby interpreter.

      - -

      For more information about the Ruby language visit the following links:

      - - -
      -

      Routes

      - -

      HTTP methods

      - -

      Sinatra’s routes are designed to respond to the HTTP request methods.

      - -
        -
      • GET
      • - -
      • POST
      • - -
      • PUT
      • - -
      • DELETE
      • -
      - -

      Basic

      - -

      Simple

      - -
      get '/hi' do
      -  ...
      -end
      - -

      With params

      - -
      get '/:name' do
      -  # matches /sinatra and the like and sets params[:name]
      -end
      - -

      Options

      - -

      Splats

      - -
      get '/say/*/to/*' do
      -  # matches /say/hello/to/world
      -  params["splat"] # => ["hello", "world"]
      -end
      -
      -get '/download/*.*' do
      -  # matches /download/path/to/file.xml
      -  params["splat"] # => ["path/to/file", "xml"]
      -end
      - -

      User agent

      - -
      get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
      -  "You're using Songbird version #{params[:agent][0]}"
      -end
      -
      -get '/foo' do
      -  # matches non-songbird browsers
      -end
      - -

      Other methods

      - -

      Other methods are requested exactly the same as “get” routes. You simply use the post, put, or delete functions to define the route, rather then the get one. To access POSTed parameters, use params[:xxx] where xxx is the name of the form element that was posted.

      - -
      post '/foo' do
      -  "You just asked for foo, with post param bar equal to #{params[:bar]}"
      -end
      - -

      The PUT and DELETE methods

      - -

      Since browsers don’t natively support the PUT and DELETE methods, a hacky workaround has been adopted by the web community. Simply add a hidden element with the name “_method” and the value equal to the HTTP method you want to use. The form itself is sent as a POST, but Sinatra will interpret it as the desired method. For example:

      - -
      <form method="post" action="/destroy_it">
      -  <input name="_method" value="delete" />
      -  <div><button type="submit">Destroy it</button></div>
      -</form>
      - -

      When you want to use PUT or DELETE from a client that does support them (like Curl, or ActiveResource), just go ahead and use them as you normally would, and ignore the _method advice above. That is only for hacking in support for browsers.

      - -

      How routes are looked up

      - -

      Each time you add a new route to your application, it gets compiled down into a regular expression that will match it. That is stored in an array along with the handler block attached to that route.

      - -

      When a new request comes in, each regex is run in turn, until one matches. Then the the handler (the code block) attached to that route gets executed.

      - -

      Splitting into multiple files

      - -

      Because Sinatra clears out your routes and reloads your application on every request in development mode, you can’t use require to load files containing your routes because these will only be loaded when the application starts (and reloaded even on the first request!) Instead, use load:

      - -
      # application.rb
      -require 'rubygems'
      -require 'sinatra'
      -
      -get '/' do
      -  "Hello world!"
      -end
      -
      -load 'more_routes.rb'
      - -

      and

      - -
      # more_routes.rb
      -
      -get '/foo' do
      -  "Bar?  How unimaginative."
      -end
      -
      -

      Handlers

      - -

      Structure

      - -

      Handler is the generic term that Sinatra uses for the “controllers”. A handler is the initial point of entry for new HTTP requests into your application.

      - -

      To find more about the routes, head to the Routes section

      - -

      Form parameters

      - -

      In handlers you can reach submitted form parameters directly via the params hash:

      - -
      get '/' do
      -  params['post']
      -end
      - -

      Nested form parameters

      - -

      The support of Rails like nested parameters is built-in since Sinatra version 9.0. Before this version you have to implement this functionality as a before filter!

      - -
      <form>
      -  <input ... name="post[title]" />
      -  <input ... name="post[body]" />
      -  <input ... name="post[author]" />
      -</form>
      - -

      The parameters in this case became as a hash:

      - -
      {"post"=>{ "title"=>"", "body"=>"", "author"=>"" }}
      - -

      Therefore in handlers you can use nested parameters like a regular hash:

      - -
      params['post']['title']
      - -

      Redirect

      - -

      The redirect helper is a shortcut to a common http response code (302).

      - -

      Basic usage is easy:

      - -
      redirect '/'
      -
      -redirect '/posts/1'
      -
      -redirect 'http://www.google.com'
      - -

      The redirect actually sends back a Location header to the browser, and the browser makes a followup request to the location indicated. Since the browser makes that followup request, you can redirect to any page, in your application, or another site entirely.

      - -

      The flow of requests during a redirect is: Browser → Server (redirect to ’/’) → Browser (request ’/’) → Server (result for ’/’)

      - -

      To force Sinatra to send a different response code, it’s very simple:

      - -
      redirect '/', 303 # forces the 303 return code
      -
      -redirect '/', 307 # forces the 307 return code
      - -

      Sessions

      - - - -

      Sinatra ships with basic support for cookie based sessions. To enable it, in a configure block, or at the top of your application, you just need to enable the option.

      - -
      enable :sessions
      -
      -get '/' do
      -  session["counter"] ||= 0
      -  session["counter"] += 1
      -
      -  "You've hit this page #{session["counter"]} time(s)"
      -end
      - -

      The downside to this session approach is that all the data is stored in the cookie. Since cookies have a fairly hard limit of 4 kilobytes, you can’t store much data. The other issue is that cookies are not tamper proof. The user can change any data in their session. But… it is easy, and it doesn’t have the scaling problems that memory or database backed sessions run into.

      - -

      Memory Based Sessions

      - -

      Memcached Based Sessions

      - -

      File Based Sessions

      - -

      Database Based Sessions

      - -

      Cookies

      - -

      Cookies are a fairly simple thing to use in Sinatra, but they have a few quirks.

      - -

      Lets first look at the simple use case:

      - -
      require 'rubygems'
      -require 'sinatra'
      -
      -get '/' do
      -    # Get the string representation
      -    cookie = request.cookies["thing"]
      -
      -    # Set a default
      -    cookie ||= 0
      -
      -    # Convert to an integer
      -    cookie = cookie.to_i
      -
      -    # Do something with the value
      -    cookie += 1
      -
      -    # Reset the cookie
      -    set_cookie("thing", cookie)
      -
      -    # Render something
      -    "Thing is now: #{cookie}"
      -end
      - -

      Setting a path, expiration date, or domain gets a little more complicated - see the source code for set_cookie if you want to dig deeper.

      - -
      set_cookie("thing", :domain => myDomain,
      -                    :path => myPath,
      -                    :expires => Date.new)
      - -

      That’s the easy stuff with cookies - It can also serialize Array objects, separating them with ampersands (&), but when they come back, it doesn’t deserialize or split them in any way, it hands you the raw, encoded string for your parsing pleasure.

      - -

      Status

      - -

      If you want to set your own status response instead of the normal 200 (Success), you can use the status-helper to set the code, and then still render normally:

      - -
      get '/' do
      -  status 404
      -  "Not found"
      -end
      - -

      Alternatively you can use throw :halt, [404, "Not found"] to immediately stop any further actions and return the specified status code and string to the client. throw supports more options in this regard, see the appropriate section for more info.

      - -

      Authentication

      -
      -

      Filters

      - -

      before do…

      - -

      These are run in Sinatra::EventContext

      - -
      before do
      -  # .. this code will run before each event ..
      -end
      - -

      Handling of Rails like nested params (Sinatra <= 3.0)

      - -

      If you want to use a form with parameters like this (aka. Rails’ nested params):

      - -
      <form>
      -  <input ... name="post[title]" />
      -  <input ... name="post[body]" />
      -  <input ... name="post[author]" />
      -</form>
      - -

      You have convert parameters to a hash. You can easily do this with a before filter:

      - -
      before do
      -  new_params = {}
      -  params.each_pair do |full_key, value|
      -    this_param = new_params
      -    split_keys = full_key.split(/\]\[|\]|\[/)
      -    split_keys.each_index do |index|
      -      break if split_keys.length == index + 1
      -      this_param[split_keys[index]] ||= {}
      -      this_param = this_param[split_keys[index]]
      -   end
      -   this_param[split_keys.last] = value
      -  end
      -  request.params.replace new_params
      -end
      - -

      Then parameters became:

      - -
      {"post"=>{ "title"=>"", "body"=>"", "author"=>"" }}
      -
      -

      Views

      - -

      All file-based views are looked up in:

      - -
      root
      -  | - views/
      - -

      Template Languages

      - -

      Haml

      - -
      get '/' do
      -  haml :index
      -end
      - -

      This will render ./views/index.haml

      - -

      Sass

      - -
      get '/' do
      -  sass :styles
      -end
      - -

      This will render ./views/styles.sass

      - -

      Erb

      - -
      get '/' do
      -  erb :index
      -end
      - -

      This will render ./views/index.erb

      - -

      Builder

      - -
      get '/' do
      -  builder :index
      -end
      - -

      This will render ./views/index.builder

      - -
      get '/' do
      -  builder do |xml|
      -    xml.node do
      -      xml.subnode "Inner text"
      -    end
      -  end
      -end
      - -

      This will render the xml inline, directly from the handler.

      - -

      Atom Feed

      - -

      RSS Feed

      - -

      Assume that your site url is http://liftoff.msfc.nasa.gov/.

      - -
      get '/rss.xml' do
      -  builder do |xml|
      -    xml.instruct! :xml, :version => '1.0'
      -    xml.rss :version => "2.0" do
      -      xml.channel do
      -        xml.title "Liftoff News"
      -        xml.description "Liftoff to Space Exploration."
      -        xml.link "http://liftoff.msfc.nasa.gov/"
      -        
      -        @posts.each do |post|
      -          xml.item do
      -            xml.title post.title
      -            xml.link "http://liftoff.msfc.nasa.gov/posts/#{post.id}"
      -            xml.description post.body
      -            xml.pubDate Time.parse(post.created_at.to_s).rfc822()
      -            xml.guid "http://liftoff.msfc.nasa.gov/posts/#{post.id}"
      -          end
      -        end
      -      end
      -    end
      -  end
      -end
      - -

      This will render the rss inline, directly from the handler.

      - -

      Layouts

      - -

      Layouts are simple in Sinatra. Put a file in your views directory named “layout.erb”, “layout.haml”, or “layout.builder”. When you render a page, the appropriate layout will be grabbed (of the same filetype), and used.

      - -

      The layout itself should call yield at the point you want the content to be included.

      - -

      An example haml layout file could look something like this:

      - -
      %html
      -  %head
      -    %title SINATRA BOOK
      -  %body
      -    #container
      -      = yield
      - -

      Avoiding a layout

      - -
      -
      Sometimes you don’t want the layout rendered. In your render method just pass
      - -
      -

      layout => false, and you’re good.

      - -

      get ’/’ do haml :index, :layout => false end

      -
      -
      - -

      In File Views

      - -

      This one is cool:

      - -
      get '/' do
      -  haml :index
      -end
      -
      -use_in_file_templates!
      -
      -__END__
      -
      -@@ layout
      -X
      -= yield
      -X
      -
      -@@ index
      -%div.title Hello world!!!!!
      - -

      Try it!

      - -

      Partials

      -
      -

      Models

      - -

      Datamapper

      - -

      Start out by getting the DataMapper gem if you don’t already have it, and then making sure it’s in your applicaton. A call to setup as usual will get the show started, and this example will include a ‘Post’ model.

      - -
      require 'rubygems'
      -require 'sinatra'
      -require 'datamapper'
      -
      -DataMapper::setup(:default, "sqlite://#{Dir.pwd}/blog.db")
      -
      -class Post
      -    include DataMapper::Resource
      -    property :id, Serial
      -    property :title, String
      -    property :body, Text
      -    property :created_at, DateTime
      -end
      -
      -# automatically create the post table
      -Post.auto_migrate! unless Post.table_exists?
      - -

      Once that is all well and good, you can actually start developing your application!

      - -
      get '/' do
      -    # get the latest 20 posts
      -    @posts = Post.get(:order => [ :id.desc ], :limit => 20)
      -    erb :index
      -end
      - -

      Finally, the view at ./view/index.html:

      - -
      <% for post in @posts %>
      -    <h3><%= post.title %></h3>
      -    <p><%= post.body %></p>
      -<% end %>
      - -

      Sequel

      - -

      Require the Sequel gem in your app:

      - -
      require 'rubygems'
      -require 'sinatra'
      -require 'sequel'
      - -

      Use a simple in-memory DB:

      - -
      DB = Sequel.sqlite
      - -

      Create a table:

      - -
      DB.create_table :links do
      - primary_key :id
      - varchar :title
      - varchar :link
      -end
      - -

      Create the Model class:

      - -
      class Link < Sequel::Model
      -end
      - -

      Create the route:

      - -
      get '/' do
      - @links = Link.all
      - haml :links
      -end
      - -

      ActiveRecord

      - -

      First require ActiveRecord gem in your application, then give your database connection settings:

      - -
      require 'rubygems'
      -require 'sinatra'
      -require 'activerecord'
      -
      -ActiveRecord::Base.establish_connection(
      -  :adapter => 'sqlite3',
      -  :dbfile =>  'sinatra_application.sqlite3.db'
      -)
      - -

      Now you can create and use ActiveRecord models just like in Rails (the example assumes you already have a ‘posts’ table in your database):

      - -
      class Post < ActiveRecord::Base
      -end
      -
      -get '/' do
      -  @posts = Post.all()
      -  erb :index 
      -end
      - -

      This will render ./views/index.erb:

      - -
      <% for post in @posts %>
      -  <h1><%= post.title %></h1>
      -<% end %>
      -
      -

      Helpers

      - -

      The basics

      - -

      It is ill-advised to create helpers on the root level of your application. They muddy the global namespace, and don’t have easy access to the request, response, session or cookie variables.

      - -

      Instead, use the handy helpers method to install methods on Sinatra::EventContext for use inside events and templates.

      - -

      Example:

      - -
      helpers do
      -  def bar(name)
      -    "#{name}bar"
      -  end
      -end
      -
      -get '/:name' do
      -  bar(params[:name])
      -end
      - -

      Implemention of rails style partials

      - -

      Using partials in your views is a great way to keep them clean. Since Sinatra takes the hands off approach to framework design, you’ll have to implement a partial handler yourself.

      - -

      Here is a really basic version:

      - -
      # Usage: partial :foo
      -helpers do
      -  def partial(page, options={})
      -    haml page, options.merge!(:layout => false)
      -  end
      -end
      - -

      A more advanced version that would handle passing local options, and looping over a hash would look like:

      - -
      # Render the page once:
      -# Usage: partial :foo
      -# 
      -# foo will be rendered once for each element in the array, passing in a local variable named "foo"
      -# Usage: partial :foo, :collection => @my_foos    
      -
      -helpers do
      -  def partial(template, *args)
      -    options = args.extract_options!
      -    options.merge!(:layout => false)
      -    if collection = options.delete(:collection) then
      -      collection.inject([]) do |buffer, member|
      -        buffer << haml(template, options.merge(
      -                                  :layout => false, 
      -                                  :locals => {template.to_sym => member}
      -                                )
      -                     )
      -      end.join("\n")
      -    else
      -      haml(template, options)
      -    end
      -  end
      -end
      -
      -

      Rack Middleware

      - -

      Sinatra rides on Rack, a minimal standard interface for Ruby web frameworks. One of Rack’s most interesting capabilities for application developers is support for “middleware” – components that sit between the server and your application monitoring and/or manipulating the HTTP request/response to provide various types of common functionality.

      - -

      Sinatra makes building Rack middleware pipelines a cinch via a top-level use method:

      - -
      require 'sinatra'
      -require 'my_custom_middleware'
      -
      -use Rack::Lint
      -use MyCustomMiddleware
      -
      -get '/hello' do
      -  'Hello World'
      -end
      - -

      The semantics of “use” are identical to those defined for the Rack::Builder DSL (most frequently used from rackup files). For example, the use method accepts multiple/variable args as well as blocks:

      - -
      use Rack::Auth::Basic do |username, password|
      -  username == 'admin' && password == 'secret'
      -end
      - -

      Rack is distributed with a variety of standard middleware for logging, debugging, URL routing, authentication, and session handling. Sinatra uses many of of these components automatically based on configuration so you typically don’t have to use them explicitly.

      -
      -

      Error Handling

      - -

      not_found

      - -

      Remember: These are run inside the Sinatra::EventContext which means you get all the goodies is has to offer (i.e. haml, erb, :halt, etc.)

      - -

      Whenever NotFound is raised this will be called

      - -
      not_found do
      -  'This is nowhere to be found'
      -end
      - -

      error

      - -

      By default error will catch Sinatra::ServerError

      - -

      Sinatra will pass you the error via the ‘sinatra.error’ in request.env

      - -
      error do
      -  'Sorry there was a nasty error - ' + request.env['sinatra.error'].name
      -end
      - -

      Custom error mapping:

      - -
      error MyCustomError do
      -  'So what happened was...' + request.env['sinatra.error'].message
      -end
      - -

      then if this happens:

      - -
      get '/' do
      -  raise MyCustomError, 'something bad'
      -end
      - -

      you gets this:

      - -
      So what happened was... something bad
      - -

      Additional Information

      - -

      Because Sinatra give you a default not_found and error do :production that are secure. If you want to customize only for :production but want to keep the friendly helper screens for :development then do this:

      - -
      configure :production do
      -  not_found do
      -    "We're so sorry, but we don't what this is"
      -  end
      -
      -  error do
      -    "Something really nasty happened.  We're on it!"
      -  end
      -end
      -
      -

      Configuration

      - -

      Use Sinatra’s “set” option

      - -

      Configure blocks are not executed in the event context, and don’t have access to the same instance variables. To store a piece of information that you want to access in your routes, use set.

      - -
      configure :development do
      -  set :dbname, 'devdb'
      -end
      -
      -configure :production do
      -  set :dbname, 'productiondb'
      -end
      - -

      - -
      get '/whatdb' do
      -  'We are using the database named ' + options.dbname
      -end
      - -

      External config file via the configure block

      - -

      Application module / config area

      -
      -

      Deployment

      - -

      Heroku

      - -

      This is the easiest configuration + deployment option. Heroku has full support for Sinatra applications. Deploying to Heroku is simply a matter of pushing to a remote git repository.

      - -

      Steps to deploy to Heroku:

      - -
        -
      • Create an account if you don’t have one
      • - -
      • sudo gem install heroku
      • - -
      • Make a config.ru in the root-directory
      • - -
      • Create the app on heroku
      • - -
      • Push to it
      • -
      - -
        -
      1. -

        An example config.ru file (Heroku sets RACK_ENV to production for you)

        - -
        require "myapp"
        -
        -run Sinatra::Application
        -
      2. - -
      3. -

        Create the app and push to it

        - -
        From the root-directory of the application
        -
        -$ heroku create <app-name>  # This will add heroku as a remote
        -$ git push heroku master
        -
      4. -
      - -

      For more details see this

      - -

      Lighttpd Proxied to Thin

      - -

      This will cover how to deploy Sinatra to a load balanced reverse proxy setup using Lighttpd and Thin.

      - -
        -
      1. -

        Install Lighttpd and Thin

        - -
        # Figure out lighttpd yourself, it should be handled by your 
        -# linux distro's package manager
        - 
        -# For thin:
        -gem install thin
        -
      2. - -
      3. -

        Create your rackup file – the require 'app' line should require the actual Sinatra app you have written.

        - -
        ## This is not needed for Thin > 1.0.0
        -ENV['RACK_ENV'] = "production"
        -
        -require 'app'
        -
        -run Sinatra::Application
        -
      4. - -
      5. -

        Setup a config.yml - change the /path/to/my/app path to reflect reality.

        - -
        ---
        -  environment: production
        -  chdir: /path/to/my/app
        -  address: 127.0.0.1
        -  user: root
        -  group: root
        -  port: 4567
        -  pid: /path/to/my/app/thin.pid
        -  rackup: /path/to/my/app/config.ru
        -  log: /path/to/my/app/thin.log
        -  max_conns: 1024
        -  timeout: 30
        -  max_persistent_conns: 512
        -  daemonize: true
        -
      6. - -
      7. -

        Setup lighttpd.conf - change mydomain to reflect reality. Also make sure the first port here matches up with the port setting in config.yml.

        - -
        $HTTP["host"] =~ "(www\.)?mydomain\.com"  {
        -        proxy.balance = "fair"
        -        proxy.server =  ("/" =>
        -                                (
        -                                        ( "host" => "127.0.0.1", "port" => 4567 ),
        -                                        ( "host" => "127.0.0.1", "port" => 4568 )
        -                                )
        -                        )
        -}
        -
      8. - -
      9. -

        Start thin and your application. I have a rake script so I can just call “rake start” rather than typing this in.

        - -
        thin -s 2 -C config.yml -R config.ru start
        -
      10. -
      - -

      You’re done! Go to mydomain.com/ and see the result! Everything should be setup now, check it out at the domain you setup in your lighttpd.conf file.

      - -

      Variation - nginx via proxy - The same approach to proxying can be applied to the nginx web server

      - -
      upstream www_mydomain_com {
      -  server 127.0.0.1:5000;
      -  server 127.0.0.1:5001;
      -}
      -
      -server {
      -  listen    www.mydomain.com:80
      -  server_name  www.mydomain.com live;
      -  access_log /path/to/logfile.log
      -  
      -  location / {
      -    proxy_pass http://www_mydomain_com;
      -  }
      -  
      -}
      - -

      Variation - More Thin instances - To add more thin instances, change the -s 2 parameter on the thin start command to be how ever many servers you want. Then be sure lighttpd proxies to all of them by adding more lines to the proxy statements. Then restart lighttpd and everything should come up as expected.

      - -

      Passenger (mod rails)

      - -

      Hate deployment via FastCGI? You’re not alone. But guess what, Passenger supports Rack; and this book tells you how to get it all going.

      - -

      You can find additional documentation at the Passenger Github repository.

      - -
        -
      1. -

        Setting up the account in the Dreamhost interface

        - -
        Domains -> Manage Domains -> Edit (web hosting column)
        -Enable 'Ruby on Rails Passenger (mod_rails)'
        -Add the public directory to the web directory box. So if you were using 'rails.com', it would change to 'rails.com/public'
        -Save your changes
        -
      2. - -
      3. -

        Creating the directory structure

        - -
        domain.com/
        -domain.com/tmp
        -domain.com/public
        -# a vendored version of sinatra - not necessary if you use the gem
        -domain.com/sinatra
        -
      4. - -
      5. -

        Creating the “Rackup file” (rack configuration file) config.ru – the require 'app' line should require the actual Sinatra app you have written.

        - -
        ## Passenger should set RACK_ENV for Sinatra
        -
        -require 'app'
        -
        -run Sinatra::Application
        -
      6. - -
      7. -

        A very simple Sinatra application

        - -
        # this is test.rb referred to above
        -get '/' do
        -  "Worked on dreamhost"
        -end
        - 
        -get '/foo/:bar' do
        -  "You asked for foo/#{params[:bar]}"
        -end
        -
      8. -
      - -

      And that’s all there is to it! Once it’s all setup, point your browser at your domain, and you should see a ‘Worked on Dreamhost’ page. To restart the application after making changes, you need to run touch tmp/restart.txt.

      - -

      Please note that currently passenger 2.0.3 has a bug where it can cause Sinatra to not find the view directory. In that case, add :views => '/path/to/views/' to the Sinatra options in your Rackup file.

      - -

      You may encounter the dreaded “Ruby (Rack) application could not be started” error with this message “can’t activate rack (>= 0.9.1, < 1.0, runtime), already activated rack-0.4.0”. This happens because DreamHost has version 0.4.0 installed, when recent versions of Sinatra require more recent versions of Rack. The solution is to explicitly require the rack and sinatra gems in your config.ru. Add the following two lines to the start of your config.ru file:

      - -
         require '/home/USERNAME/.gem/ruby/1.8/gems/rack-VERSION-OF-RACK-GEM-YOU-HAVE-INSTALLELD/lib/rack.rb'
      -   require '/home/USERNAME/.gem/ruby/1.8/gems/sinatra-VERSION-OF-SINATRA-GEM-YOU-HAVE-INSTALLELD/lib/sinatra.rb'
      - -

      FastCGI

      - -

      The standard method for deployment is to use Thin or Mongrel, and have a reverse proxy (lighttpd, nginx, or even Apache) point to your bundle of servers.

      - -

      But that isn’t always possible. Cheaper shared hosting (like Dreamhost) won’t let you run Thin or Mongrel, or setup reverse proxies (at least on the default shared plan).

      - -

      Luckily, Rack supports various connectors, including CGI and FastCGI. Unluckily for us, FastCGI doesn’t quite work with the current Sinatra release without some tweaking.

      - -

      Deployment with Sinatra version 0.9

      - -

      From version 9.0 Sinatra requires Rack 0.9.1, however FastCGI wrapper from this version seems not working well with Sinatra unless you define your application as a subclass of Sinatra::Application class and run this application directly as a Rack application.

      - -

      Steps to deploy via FastCGI:

      - -
        -
      • htaccess
      • - -
      • subclass your application as Sinatra::Application
      • - -
      • dispatch.fcgi
      • -
      - -
        -
      1. -

        .htaccess

        - -
         RewriteEngine on
        - 
        - AddHandler fastcgi-script .fcgi
        - Options +FollowSymLinks +ExecCGI
        - 
        - RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
        -
      2. - -
      3. -

        Subclass your application as Sinatra::Application

        - -
         # my_sinatra_app.rb
        - class MySinatraApp < Sinatra::Application
        -   # your sinatra application definitions
        - end
        -
      4. - -
      5. -

        dispatch.fcgi - Run this application directly as a Rack application

        - -
         #!/usr/local/bin/ruby
        -
        - require 'rubygems'
        - require 'rack'
        - 
        - fastcgi_log = File.open("fastcgi.log", "a")
        - STDOUT.reopen fastcgi_log
        - STDERR.reopen fastcgi_log
        - STDOUT.sync = true
        -
        - module Rack
        -   class Request
        -     def path_info
        -       @env["REDIRECT_URL"].to_s
        -     end
        -     def path_info=(s)
        -       @env["REDIRECT_URL"] = s.to_s
        -     end
        -   end
        - end
        -
        - load 'my\_sinatra\_app.rb'
        -
        - builder = Rack::Builder.new do
        -   map '/' do
        -     run MySinatraApp.new
        -   end
        - end
        -
        - Rack::Handler::FastCGI.run(builder)
        -
      6. -
      - -

      Deployment with Sinatra version <= 0.3

      - -

      In version 0.3 to get a simple ‘hello world’ Sinatra application up and running via FastCGI, you have to pulling down the current Sinatra code, and hacking at it a bit. Don’t worry though, it only requires commenting out a few lines, and tweaking another.

      - -

      Steps to deploy:

      - -
        -
      • .htaccess
      • - -
      • dispatch.fcgi
      • - -
      • Tweaked sinatra.rb
      • -
      - -
        -
      1. -

        .htaccess RewriteEngine on

        - -
        AddHandler fastcgi-script .fcgi
        -Options +FollowSymLinks +ExecCGI
        -
        -RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
        -
      2. - -
      3. -

        dispatch.fcgi

        - -
        #!/usr/bin/ruby
        -
        -require 'rubygems'
        -require 'sinatra/lib/sinatra'
        -
        -fastcgi_log = File.open("fastcgi.log", "a")
        -STDOUT.reopen fastcgi_log
        -STDERR.reopen fastcgi_log
        -STDOUT.sync = true
        -
        -set :logging, false
        -set :server, "FastCGI"
        -
        -module Rack
        -  class Request
        -    def path_info
        -      @env["REDIRECT_URL"].to_s
        -    end
        -    def path_info=(s)
        -      @env["REDIRECT_URL"] = s.to_s
        -    end
        -  end
        -end
        -
        -load 'app.rb'
        -
      4. - -
      5. -

        sinatra.rb - Replace this function with the new version here (commenting out the puts lines)

        - -
        def run
        -  begin
        -    #puts "== Sinatra has taken the stage on port #{port} for #{env} with backup by #{server.name}"
        -    require 'pp'
        -    server.run(application) do |server|
        -      trap(:INT) do
        -        server.stop
        -        #puts "\n== Sinatra has ended his set (crowd applauds)"
        -      end
        -    end
        -  rescue Errno::EADDRINUSE => e
        -    #puts "== Someone is already performing on port #{port}!"
        -  end
        -end
        -
      6. -
      -
      -

      Contributing

      - -

      How can I clone the Sinatra repository?

      - -

      First of all, you’ll need the Git version control system. Git is available for all major platforms:

      - -
        -
      • Windows
      • - -
      • Mac OS X
      • - -
      • Linux and BSD users can usually acquire Git through their Package Management System, e.g. apt-get install git-core on Debian systems.
      • -
      - -

      After that, cloning the Sinatra repository is as easy as typing the following into your command line:

      - -
      git clone git://github.com/sinatra/sinatra.git
      - -

      How to create a patch?

      - -

      How to get that patch into the official Sinatra?