README.de.md in sinatra-1.4.5 vs README.de.md in sinatra-1.4.6

- old
+ new

@@ -1,9 +1,9 @@ # Sinatra *Wichtig: Dieses Dokument ist eine Übersetzung aus dem Englischen und unter -Umständen nicht auf dem aktuellen Stand (aktuell Sinatra 1.4.2).* +Umständen nicht auf dem aktuellen Stand (aktuell Sinatra 1.4.5).* Sinatra ist eine [DSL](http://de.wikipedia.org/wiki/Domänenspezifische_Sprache), die das schnelle Erstellen von Webanwendungen in Ruby mit minimalem Aufwand ermöglicht: @@ -52,25 +52,28 @@ * [Less Templates](#less-templates) * [Liquid Templates](#liquid-templates) * [Markdown Templates](#markdown-templates) * [Textile Templates](#textile-templates) * [RDoc Templates](#rdoc-templates) + * [AsciiDoc Templates](#asciidoc-templates) * [Radius Templates](#radius-templates) * [Markaby Templates](#markaby-templates) * [RABL Templates](#rabl-templates) * [Slim Templates](#slim-templates) * [Creole Templates](#creole-templates) + * [MediaWiki Templates](#mediawiki-templates) * [CoffeeScript Templates](#coffeescript-templates) * [Stylus Templates](#stylus-templates) * [Yajl Templates](#yajl-templates) * [WLang Templates](#wlang-templates) * [Auf Variablen in Templates zugreifen](#auf-variablen-in-templates-zugreifen) * [Templates mit `yield` und verschachtelte Layouts](#templates-mit-yield-und-verschachtelte-layouts) * [Inline-Templates](#inline-templates) * [Benannte Templates](#benannte-templates) * [Dateiendungen zuordnen](#dateiendungen-zuordnen) * [Eine eigene Template-Engine hinzufügen](#eine-eigene-template-engine-hinzufgen) + * [Eigene Methoden zum Aufsuchen von Templates verwenden](#eigene-methoden-zum-aufsuchen-von-templates-verwenden) * [Filter](#filter) * [Helfer](#helfer) * [Sessions verwenden](#sessions-verwenden) * [Anhalten](#anhalten) * [Weiterspringen](#weiterspringen) @@ -158,36 +161,36 @@ `params`-Hash zugänglich gemacht werden: ```ruby get '/hallo/:name' do # passt auf "GET /hallo/foo" und "GET /hallo/bar" - # params[:name] ist dann 'foo' oder 'bar' - "Hallo #{params[:name]}!" + # params['name'] ist dann 'foo' oder 'bar' + "Hallo #{params['name']}!" end ``` Man kann auf diese auch mit Block-Parametern zugreifen: ```ruby get '/hallo/:name' do |n| - # n entspricht hier params[:name] + # n entspricht hier params['name'] "Hallo #{n}!" end ``` Routen-Muster können auch mit sog. Splat- oder Wildcard-Parametern über das -`params[:splat]`-Array angesprochen werden: +`params['splat']`-Array angesprochen werden: ```ruby get '/sag/*/zu/*' do # passt z.B. auf /sag/hallo/zu/welt - params[:splat] # => ["hallo", "welt"] + params['splat'] # => ["hallo", "welt"] end get '/download/*.*' do # passt auf /download/pfad/zu/datei.xml - params[:splat] # => ["pfad/zu/datei", "xml"] + params['splat'] # => ["pfad/zu/datei", "xml"] end ``` Oder mit Block-Parametern: @@ -198,12 +201,12 @@ ``` Routen mit regulären Ausdrücken sind auch möglich: ```ruby -get %r{/hallo/([\w]+)} do - "Hallo, #{params[:captures].first}!" +get /\A\/hallo\/([\w]+)\z/ do + "Hallo, #{params['captures'].first}!" end ``` Und auch hier können Block-Parameter genutzt werden: @@ -220,10 +223,21 @@ # passt auf "GET /posts" sowie jegliche Erweiterung # wie "GET /posts.json", "GET /posts.xml" etc. end ``` +Routen können auch den query-Parameter verwenden: + +``` ruby +get '/posts' do + # matches "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # uses title and author variables; query is optional to the /posts route +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. ### Bedingungen @@ -232,11 +246,11 @@ sein müssen, damit der Block ausgeführt wird. Möglich wäre etwa eine Einschränkung des User-Agents über die interne Bedingung `:agent`: ```ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do - "Du verwendest Songbird Version #{params[:agent][0]}" + "Du verwendest Songbird Version #{params['agent'][0]}" end ``` Wird Songbird als Browser nicht verwendet, springt Sinatra zur nächsten Route: @@ -259,10 +273,11 @@ get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` +`provides` durchsucht den Accept-Header der Anfrage Eigene Bedingungen können relativ einfach hinzugefügt werden: ```ruby set(:wahrscheinlichkeit) { |value| condition { rand <= value } } @@ -571,11 +586,11 @@ #### Builder Templates <table> <tr> <td>Abhängigkeit</td> - <td><a href="http://builder.rubyforge.org/">builder</a></td> + <td><a href="https://github.com/jimweirich/builder">builder</a></td> </tr> <tr> <td>Dateierweiterung</td> <td><tt>.builder</tt></td> </tr> @@ -688,12 +703,12 @@ <td>Abhängigkeit</td> <td>Eine der folgenden Bibliotheken: <a href="https://github.com/rtomayko/rdiscount" title="RDiscount">RDiscount</a>, <a href="https://github.com/vmg/redcarpet" title="RedCarpet">RedCarpet</a>, <a href="http://deveiate.org/projects/BlueCloth" title="BlueCloth">BlueCloth</a>, - <a href="http://kramdown.rubyforge.org/" title="kramdown">kramdown</a> oder - <a href="http://maruku.rubyforge.org/" title="maruku">maruku</a> + <a href="http://kramdown.gettalong.org/" title="kramdown">kramdown</a> oder + <a href="https://github.com/bhollis/maruku" title="maruku">maruku</a> </td> </tr> <tr> <td>Dateierweiterungen</td> <td><tt>.markdown</tt>, <tt>.mkd</tt> und <tt>.md</tt></td> @@ -766,11 +781,11 @@ #### RDoc Templates <table> <tr> <td>Abhängigkeit</td> - <td><a href="http://rdoc.rubyforge.org/">rdoc</a></td> + <td><a href="http://rdoc.sourceforge.net/">rdoc</a></td> </tr> <tr> <td>Dateierweiterung</td> <td><tt>.rdoc</tt></td> </tr> @@ -799,16 +814,37 @@ 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 `:layout_engine`-Option verwendet wird. +#### AsciiDoc Templates + +<table> + <tr> + <td>Abhängigkeit</td> + <td><a href="http://asciidoctor.org/" title="Asciidoctor">Asciidoctor</a></td> + </tr> + <tr> + <td>Dateierweiterungen</td> + <td><tt>.asciidoc</tt>, <tt>.adoc</tt> und <tt>.ad</tt></td> + </tr> + <tr> + <td>Beispiel</td> + <td><tt>asciidoc :README, :layout_engine => :erb</tt></td> + </tr> +</table> + +Da man aus dem AsciiDoc-Template heraus keine Ruby-Methoden aufrufen kann +(ausgenommen `yield`), wird man üblicherweise locals verwenden wollen, mit +denen man Variablen weitergibt. + #### Radius Templates <table> <tr> <td>Abhängigkeit</td> - <td><a href="http://radius.rubyforge.org/">radius</a></td> + <td><a href="https://github.com/jlong/radius">radius</a></td> </tr> <tr> <td>Dateierweiterung</td> <td><tt>.radius</tt></td> </tr> @@ -910,10 +946,48 @@ 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. +#### MediaWiki Templates + +<table> + <tr> + <td>Abhängigkeit</td> + <td><a href="https://github.com/nricciar/wikicloth" title="WikiCloth">WikiCloth</a></td> + </tr> + <tr> + <td>Dateierweiterungen</td> + <td><tt>.mediawiki</tt> und <tt>.mw</tt></td> + </tr> + <tr> + <td>Beispiel</td> + <td><tt>mediawiki :wiki, :layout_engine => :erb</tt></td> + </tr> +</table> + +Da man aus dem Mediawiki-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Mediawiki üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +``` ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Beachte: Man kann die `mediawiki`-Methode auch aus anderen Templates +heraus aufrufen: + +``` ruby +%h1 Grüße von Haml! +%p= mediawiki(:greetings) +``` + +Da man Ruby nicht von MediaWiki heraus aufrufen kann, können auch Layouts nicht +in MediaWiki 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 <table> <tr> <td>Abhängigkeit</td> @@ -1000,12 +1074,13 @@ ``` Die `:callback` und `:variable` Optionen können mit dem gerenderten Objekt verwendet werden: -``` ruby -var resource = {"foo":"bar","baz":"qux"}; present(resource); +``` javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); ``` #### WLang Templates <table> @@ -1021,33 +1096,33 @@ <td>Beispiel</td> <td><tt>wlang :index, :locals => { :key => 'value' }</tt></td> </tr> </table> -Ruby-Methoden in wlang aufzurufen entspricht nicht den idiomatischen Vorgaben -von wlang, es bietet sich deshalb an, `:locals` zu verwenden. Layouts, die -wlang und `yield` verwenden, werden aber trotzdem unterstützt. +Ruby-Methoden in Wlang aufzurufen entspricht nicht den idiomatischen Vorgaben +von Wlang, es bietet sich deshalb an, `:locals` zu verwenden. Layouts, die +Wlang und `yield` verwenden, werden aber trotzdem unterstützt. Rendert den eingebetteten Template-String. ### Auf Variablen in Templates zugreifen Templates werden in demselben Kontext ausgeführt wie Routen. Instanzvariablen in Routen sind auch direkt im Template verfügbar: ```ruby get '/:id' do - @foo = Foo.find(params[:id]) + @foo = Foo.find(params['id']) haml '%h1= @foo.name' end ``` Oder durch einen expliziten Hash von lokalen Variablen: ```ruby get '/:id' do - foo = Foo.find(params[:id]) + foo = Foo.find(params['id']) haml '%h1= bar.name', :locals => { :bar => foo } end ``` Dies wird typischerweise bei Verwendung von Subtemplates (partials) in anderen @@ -1175,10 +1250,27 @@ Dieser Code rendert `./views/application.mtt`. Siehe [github.com/rtomayko/tilt](https://github.com/rtomayko/tilt), um mehr über Tilt zu erfahren. +### Eigene Methoden zum Aufsuchen von Templates verwenden + +Um einen eigenen Mechanismus zum Aufsuchen von Templates zu +implementieren, muss `#find_template` definiert werden: + +``` ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + ## Filter Before-Filter werden vor jedem Request in demselben Kontext, wie danach die Routen, ausgeführt. So können etwa Request und Antwort geändert werden. Gesetzte Instanzvariablen in Filtern können in Routen und Templates verwendet @@ -1190,11 +1282,11 @@ request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Hi!' - params[:splat] #=> 'bar/baz' + 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 @@ -1243,11 +1335,11 @@ "#{name}bar" end end get '/:name' do - bar(params[:name]) + bar(params['name']) end ``` ### Sessions verwenden Sessions werden verwendet, um Zustände zwischen den Requests zu speichern. Sind @@ -1259,11 +1351,11 @@ get '/' do "value = " << session[:value].inspect end get '/:value' do - session[:value] = params[:value] + 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 @@ -1277,11 +1369,11 @@ get '/' do "value = " << session[:value].inspect end get '/:value' do - session[:value] = params[:value] + 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 @@ -1298,10 +1390,17 @@ ```ruby set :sessions, :domain => 'foo.com' ``` +Um eine Session mit anderen Apps und zwischen verschiedenen Subdomains +von foo.com zu teilen, wird ein *.* der Domain vorangestellt: + +``` ruby +set :sessions, :domain => '.foo,com' +``` + ### Anhalten Zum sofortigen Stoppen eines Request in einem Filter oder einer Route: ```ruby @@ -1342,11 +1441,11 @@ Eine Route kann mittels `pass` zu der nächsten passenden Route springen: ```ruby get '/raten/:wer' do - pass unless params[:wer] == 'Frank' + pass unless params['wer'] == 'Frank' 'Du hast mich!' end get '/raten/*' do 'Du hast mich nicht!' @@ -1465,24 +1564,22 @@ set :server, :thin connections = [] 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?) - - # Rückmeldung - "Angemeldet" + stream(:keep_open) do |out| + connections << out + # tote Verbindungen entfernen + connections.reject!(&:closed?) + end end -post '/message' do +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" + out << params['message'] << "\n" # Den Client zur erneuten Verbindung auffordern out.close end @@ -1649,11 +1746,11 @@ eigentliche Arbeit anfängt, da sie sofort eine Antwort senden, wenn der Client eine aktuelle Version im Cache vorhält: ```ruby get '/article/:id' do - @article = Article.find params[:id] + @article = Article.find params['id'] last_modified @article.updated_at etag @article.sha1 erb :article end ``` @@ -1709,11 +1806,12 @@ etag '', :new_resource => true, :kind => :weak ``` ### Dateien versenden -Zum Versenden von Dateien kann die `send_file`-Helfer-Methode verwendet werden: +Um den Inhalt einer Datei als Response zurückzugeben, kann die +`send_file`-Helfer-Methode verwendet werden: ```ruby get '/' do send_file 'foo.png' end @@ -2008,14 +2106,14 @@ einem Reverse-Proxy liegt, der nicht ordentlich eingerichtet ist. Beachte, dass die <tt>url</tt>-Helfer-Methode nach wie vor absolute URLs erstellen wird, es sei denn, es wird als zweiter Parameter <tt>false</tt> angegeben. Standardmäßig nicht aktiviert.</dd> - <dt>add_charsets</dt> + <dt>add_charset</dt> <dd>Mime-Types werden hier automatisch der Helfer-Methode <tt>content_type</tt> zugeordnet. Es empfielt sich, Werte hinzuzufügen statt - sie zu überschreiben: <tt>settings.add_charsets << "application/foobar"</tt> + sie zu überschreiben: <tt>settings.add_charset << "application/foobar"</tt> </dd> <dt>app_file</dt> <dd>Pfad zur Hauptdatei der Applikation. Wird verwendet, um das Wurzel-, Inline-, View- und öffentliche Verzeichnis des Projekts festzustellen.</dd> @@ -2123,10 +2221,13 @@ <dt>threaded</dt> <dd>Wird es auf <tt>true</tt> gesetzt, wird Thin aufgefordert <tt>EventMachine.defer</tt> zur Verarbeitung des Requests einzusetzen.</dd> + <dt>traps</dt> + <dd>Einstellung, Sinatra System signalen umgehen soll.</dd> + <dt>views</dt> <dd>Verzeichnis der Views. Leitet sich von der <tt>app_file</tt> Einstellung ab, wenn nicht gesetzt.</dd> <dt>x_cascade</dt> @@ -2170,16 +2271,23 @@ ``` ### 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: +Routen-Block oder in einem Filter geworfen wurde. In der +`development`-Umgebung wird es nur dann funktionieren, wenn die +`:show_exceptions`-Option auf `:after_handler` eingestellt wurde: ```ruby +set :show_exceptions, :after_handler +``` + +Die Exception kann über die `sinatra.error`-Rack-Variable angesprochen werden: + +```ruby error do - 'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].name + 'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].message end ``` Benutzerdefinierte Fehler: @@ -2227,11 +2335,11 @@ Development-Umgebung ein, um hilfreiche Debugging Informationen und Stack Traces anzuzeigen. ## Rack-Middleware -Sinatra baut auf [Rack](http://rack.rubyforge.org/), einem minimalistischen +Sinatra baut auf [Rack](http://rack.github.io/), einem minimalistischen Standard-Interface für Ruby-Webframeworks. Eines der interessantesten Features für Entwickler ist der Support von Middlewares, die zwischen den Server und die Anwendung geschaltet werden und so HTTP-Request und/oder Antwort überwachen und/oder manipulieren können. @@ -2249,11 +2357,11 @@ 'Hallo Welt' end ``` Die Semantik von `use` entspricht der gleichnamigen Methode der -[Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html)-DSL +[Rack::Builder](http://rubydoc.info/github/rack/rack/master/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: ```ruby use Rack::Auth::Basic do |username, password| @@ -2266,25 +2374,25 @@ viele von diesen Komponenten automatisch, abhängig von der Konfiguration. So muss `use` häufig nicht explizit verwendet werden. Hilfreiche Middleware gibt es z.B. hier: [rack](https://github.com/rack/rack/tree/master/lib/rack), -[rack-contrib](https://github.com/rack/rack-contrib#readme), +[rack-contrib](https://github.com/rack/rack-contrib#readme), oder im [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). ## Testen Sinatra-Tests können mit jedem auf Rack aufbauendem Test-Framework geschrieben werden. [Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames) wird empfohlen: ```ruby require 'my_sinatra_app' -require 'test/unit' +require 'minitest/autorun' require 'rack/test' -class MyAppTest < Test::Unit::TestCase +class MyAppTest < Minitest::Test include Rack::Test::Methods def app Sinatra::Application end @@ -2351,16 +2459,28 @@ '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 einer Subklasse von `Sinatra::Base` definiert werden. - `Sinatra::Base` ist ein unbeschriebenes Blatt. Die meisten Optionen sind per Standard deaktiviert. Das betrifft auch den eingebauten Server. Siehe [Optionen und Konfiguration](http://sinatra.github.com/configuration.html) für Details über mögliche Optionen. +Damit eine App sich ähnlich wie eine klassische App verhält, kann man +auch eine Subclass von `Sinatra::Application` erstellen: + +``` ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + ### Modularer vs. klassischer Stil Entgegen häufiger Meinungen gibt es nichts gegen den klassischen Stil einzuwenden. Solange es die Applikation nicht beeinträchtigt, besteht kein Grund, eine modulare Applikation zu erstellen. @@ -2377,46 +2497,53 @@ <table> <tr> <th>Szenario</th> <th>Classic</th> <th>Modular</th> + <th>Modular</th> </tr> <tr> <td>app_file</td> <td>Sinatra ladende Datei</td> <td>Sinatra::Base subklassierende Datei</td> + <td>Sinatra::Application subklassierende Datei</td> </tr> <tr> <td>run</td> <td>$0 == app_file</td> <td>false</td> + <td>false</td> </tr> <tr> <td>logging</td> <td>true</td> <td>false</td> + <td>true</td> </tr> <tr> <td>method_override</td> <td>true</td> <td>false</td> + <td>true</td> </tr> <tr> <td>inline_templates</td> <td>true</td> <td>false</td> + <td>true</td> </tr> <tr> <td>static</td> <td>true</td> <td>false</td> + <td>true</td> </tr> </table> ### Eine modulare Applikation bereitstellen @@ -2505,12 +2632,12 @@ enable :sessions get('/login') { haml :login } post('/login') do - if params[:name] == 'admin' && params[:password] == 'admin' - session['user_name'] = params[:name] + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] else redirect '/login' end end end @@ -2634,12 +2761,12 @@ # 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]}" + settings.get "/#{params['name']}" do + # Anfrage-Scope für "/#{params['name']}" @value # => nil (nicht dieselbe Anfrage) end "Route definiert!" end @@ -2712,10 +2839,13 @@ <dt>Ruby 1.9.3</dt> <dd>1.9.3 wird vollständig unterstützt und empfohlen. Achtung, bei einem Upgrade von einer früheren Version von Ruby zu Ruby 1.9.3 werden alle Sessions ungültig. Ruby 1.9.3 wird bis Sinatra 2.0 unterstützt werden.</dd> +<dt>Ruby 2.x</dt> +<dd>2.x wird vollständig unterstützt.</dd> + <dt>Rubinius</dt> <dd>Rubinius (Version >= 2.x) wird offiziell unterstützt. Es wird empfohlen, den <a href="http://puma.io">Puma Server</a> zu installieren (<tt>gem install puma </tt>)</dd> @@ -2736,12 +2866,12 @@ Nicht offiziell unterstützt bedeutet, dass wenn Sachen nicht funktionieren, wir davon ausgehen, dass es nicht an Sinatra sondern an der jeweiligen Implementierung liegt. Im Rahmen unserer CI (Kontinuierlichen Integration) wird bereits ruby-head -(das kommende Ruby 2.1.0) mit eingebunden. Es kann davon ausgegangen -werden, dass Sinatra Ruby 2.1.0 vollständig unterstützen wird. +(zukünftige Versionen von MRI) mit eingebunden. Es kann davon ausgegangen +werden, dass Sinatra MRI auch weiterhin vollständig unterstützen wird. Sinatra sollte auf jedem Betriebssystem laufen, dass einen funktionierenden Ruby-Interpreter aufweist. Sinatra läuft aktuell nicht unter Cardinal, SmallRuby, BlueRuby oder Ruby <= 1.8.7. @@ -2853,10 +2983,10 @@ * [Issue-Tracker](http://github.com/sinatra/sinatra/issues) * [Twitter](http://twitter.com/sinatra) * [Mailing-Liste](http://groups.google.com/group/sinatrarb) * [#sinatra](irc://chat.freenode.net/#sinatra) auf http://freenode.net Es gibt dort auch immer wieder deutschsprachige Entwickler, die gerne weiterhelfen. -* [Sinatra Book](http://sinatra-book.gittr.com) Kochbuch Tutorial +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Kochbuch Tutorial * [Sinatra Recipes](http://recipes.sinatrarb.com/) Sinatra-Rezepte aus der Community * API Dokumentation für die [aktuelle Version](http://rubydoc.info/gems/sinatra) oder für [HEAD](http://rubydoc.info/github/sinatra/sinatra) auf http://rubydoc.info