devcenter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +8 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +39 -0
  5. data/Rakefile +2 -0
  6. data/bin/devcenter +7 -0
  7. data/devcenter.gemspec +30 -0
  8. data/lib/devcenter.rb +6 -0
  9. data/lib/devcenter/cli.rb +45 -0
  10. data/lib/devcenter/coderay_extensions.rb +70 -0
  11. data/lib/devcenter/commands.rb +4 -0
  12. data/lib/devcenter/commands/base.rb +47 -0
  13. data/lib/devcenter/commands/open.rb +34 -0
  14. data/lib/devcenter/commands/preview.rb +28 -0
  15. data/lib/devcenter/commands/pull.rb +37 -0
  16. data/lib/devcenter/helpers.rb +41 -0
  17. data/lib/devcenter/layout.html +299 -0
  18. data/lib/devcenter/md_parser.rb +87 -0
  19. data/lib/devcenter/previewer.rb +36 -0
  20. data/lib/devcenter/previewer/assets/images/public/article-icon-large.png +0 -0
  21. data/lib/devcenter/previewer/assets/images/public/article-icon.png +0 -0
  22. data/lib/devcenter/previewer/assets/images/public/aside_accordion_indicator_default.png +0 -0
  23. data/lib/devcenter/previewer/assets/images/public/aside_accordion_indicator_open.png +0 -0
  24. data/lib/devcenter/previewer/assets/images/public/body_bg.png +0 -0
  25. data/lib/devcenter/previewer/assets/images/public/callout_bg.png +0 -0
  26. data/lib/devcenter/previewer/assets/images/public/feed-icon-sprite.png +0 -0
  27. data/lib/devcenter/previewer/assets/images/public/heroku-header-logo-mobile.png +0 -0
  28. data/lib/devcenter/previewer/assets/images/public/heroku-header-logo.png +0 -0
  29. data/lib/devcenter/previewer/assets/images/public/heroku-logo.png +0 -0
  30. data/lib/devcenter/previewer/assets/images/public/icon_sprite_16.png +0 -0
  31. data/lib/devcenter/previewer/assets/images/public/index_li_bullet.png +0 -0
  32. data/lib/devcenter/previewer/assets/images/public/intro_bg.png +0 -0
  33. data/lib/devcenter/previewer/assets/images/public/jive_discussion_arrow.png +0 -0
  34. data/lib/devcenter/previewer/assets/images/public/jive_discussion_glyph.png +0 -0
  35. data/lib/devcenter/previewer/assets/images/public/line.png +0 -0
  36. data/lib/devcenter/previewer/assets/images/public/pre_code_background.png +0 -0
  37. data/lib/devcenter/previewer/assets/images/public/search-icon.png +0 -0
  38. data/lib/devcenter/previewer/assets/images/public/search_glyph.png +0 -0
  39. data/lib/devcenter/previewer/assets/images/public/search_return.png +0 -0
  40. data/lib/devcenter/previewer/assets/images/public/tag-icon-large.png +0 -0
  41. data/lib/devcenter/previewer/assets/images/public/toc-icon.png +0 -0
  42. data/lib/devcenter/previewer/assets/public.css +2125 -0
  43. data/lib/devcenter/previewer/assets/public/article-icon-large.png +0 -0
  44. data/lib/devcenter/previewer/assets/public/article-icon.png +0 -0
  45. data/lib/devcenter/previewer/assets/public/aside_accordion_indicator_default.png +0 -0
  46. data/lib/devcenter/previewer/assets/public/aside_accordion_indicator_open.png +0 -0
  47. data/lib/devcenter/previewer/assets/public/body_bg.png +0 -0
  48. data/lib/devcenter/previewer/assets/public/callout_bg.png +0 -0
  49. data/lib/devcenter/previewer/assets/public/feed-icon-sprite.png +0 -0
  50. data/lib/devcenter/previewer/assets/public/heroku-header-logo-mobile.png +0 -0
  51. data/lib/devcenter/previewer/assets/public/heroku-header-logo.png +0 -0
  52. data/lib/devcenter/previewer/assets/public/heroku-logo.png +0 -0
  53. data/lib/devcenter/previewer/assets/public/icon_sprite_16.png +0 -0
  54. data/lib/devcenter/previewer/assets/public/index_li_bullet.png +0 -0
  55. data/lib/devcenter/previewer/assets/public/intro_bg.png +0 -0
  56. data/lib/devcenter/previewer/assets/public/jive_discussion_arrow.png +0 -0
  57. data/lib/devcenter/previewer/assets/public/jive_discussion_glyph.png +0 -0
  58. data/lib/devcenter/previewer/assets/public/line.png +0 -0
  59. data/lib/devcenter/previewer/assets/public/pre_code_background.png +0 -0
  60. data/lib/devcenter/previewer/assets/public/public.css +2125 -0
  61. data/lib/devcenter/previewer/assets/public/search-icon.png +0 -0
  62. data/lib/devcenter/previewer/assets/public/search_glyph.png +0 -0
  63. data/lib/devcenter/previewer/assets/public/search_return.png +0 -0
  64. data/lib/devcenter/previewer/assets/public/tag-icon-large.png +0 -0
  65. data/lib/devcenter/previewer/assets/public/toc-icon.png +0 -0
  66. data/lib/devcenter/previewer/file_listener.rb +23 -0
  67. data/lib/devcenter/previewer/views/article.erb +345 -0
  68. data/lib/devcenter/previewer/web_app.rb +53 -0
  69. data/lib/devcenter/previewer/web_server.rb +29 -0
  70. data/lib/devcenter/version.rb +3 -0
  71. data/vendor/sinatra/.gitignore +6 -0
  72. data/vendor/sinatra/.travis.yml +16 -0
  73. data/vendor/sinatra/.yardopts +4 -0
  74. data/vendor/sinatra/AUTHORS +61 -0
  75. data/vendor/sinatra/Gemfile +91 -0
  76. data/vendor/sinatra/LICENSE +22 -0
  77. data/vendor/sinatra/README.de.rdoc +2116 -0
  78. data/vendor/sinatra/README.es.rdoc +2106 -0
  79. data/vendor/sinatra/README.fr.rdoc +2133 -0
  80. data/vendor/sinatra/README.hu.rdoc +608 -0
  81. data/vendor/sinatra/README.jp.rdoc +1056 -0
  82. data/vendor/sinatra/README.ko.rdoc +1932 -0
  83. data/vendor/sinatra/README.pt-br.rdoc +778 -0
  84. data/vendor/sinatra/README.pt-pt.rdoc +647 -0
  85. data/vendor/sinatra/README.rdoc +2049 -0
  86. data/vendor/sinatra/README.ru.rdoc +2033 -0
  87. data/vendor/sinatra/README.zh.rdoc +1816 -0
  88. data/vendor/sinatra/Rakefile +182 -0
  89. data/vendor/sinatra/examples/chat.rb +61 -0
  90. data/vendor/sinatra/examples/simple.rb +3 -0
  91. data/vendor/sinatra/examples/stream.ru +26 -0
  92. data/vendor/sinatra/lib/sinatra.rb +5 -0
  93. data/vendor/sinatra/lib/sinatra/base.rb +1820 -0
  94. data/vendor/sinatra/lib/sinatra/images/404.png +0 -0
  95. data/vendor/sinatra/lib/sinatra/images/500.png +0 -0
  96. data/vendor/sinatra/lib/sinatra/main.rb +30 -0
  97. data/vendor/sinatra/lib/sinatra/showexceptions.rb +345 -0
  98. data/vendor/sinatra/lib/sinatra/version.rb +3 -0
  99. data/vendor/sinatra/sinatra.gemspec +18 -0
  100. data/vendor/sinatra/test/base_test.rb +172 -0
  101. data/vendor/sinatra/test/builder_test.rb +91 -0
  102. data/vendor/sinatra/test/coffee_test.rb +90 -0
  103. data/vendor/sinatra/test/compile_test.rb +139 -0
  104. data/vendor/sinatra/test/contest.rb +98 -0
  105. data/vendor/sinatra/test/creole_test.rb +65 -0
  106. data/vendor/sinatra/test/delegator_test.rb +160 -0
  107. data/vendor/sinatra/test/encoding_test.rb +20 -0
  108. data/vendor/sinatra/test/erb_test.rb +98 -0
  109. data/vendor/sinatra/test/extensions_test.rb +98 -0
  110. data/vendor/sinatra/test/filter_test.rb +437 -0
  111. data/vendor/sinatra/test/haml_test.rb +91 -0
  112. data/vendor/sinatra/test/helper.rb +123 -0
  113. data/vendor/sinatra/test/helpers_test.rb +1768 -0
  114. data/vendor/sinatra/test/integration/app.rb +62 -0
  115. data/vendor/sinatra/test/integration_helper.rb +222 -0
  116. data/vendor/sinatra/test/integration_test.rb +87 -0
  117. data/vendor/sinatra/test/less_test.rb +69 -0
  118. data/vendor/sinatra/test/liquid_test.rb +59 -0
  119. data/vendor/sinatra/test/mapped_error_test.rb +305 -0
  120. data/vendor/sinatra/test/markaby_test.rb +80 -0
  121. data/vendor/sinatra/test/markdown_test.rb +82 -0
  122. data/vendor/sinatra/test/middleware_test.rb +68 -0
  123. data/vendor/sinatra/test/nokogiri_test.rb +67 -0
  124. data/vendor/sinatra/test/public/favicon.ico +0 -0
  125. data/vendor/sinatra/test/rabl_test.rb +89 -0
  126. data/vendor/sinatra/test/rack_test.rb +45 -0
  127. data/vendor/sinatra/test/radius_test.rb +59 -0
  128. data/vendor/sinatra/test/rdoc_test.rb +66 -0
  129. data/vendor/sinatra/test/readme_test.rb +120 -0
  130. data/vendor/sinatra/test/request_test.rb +45 -0
  131. data/vendor/sinatra/test/response_test.rb +64 -0
  132. data/vendor/sinatra/test/result_test.rb +76 -0
  133. data/vendor/sinatra/test/route_added_hook_test.rb +59 -0
  134. data/vendor/sinatra/test/routing_test.rb +1175 -0
  135. data/vendor/sinatra/test/sass_test.rb +116 -0
  136. data/vendor/sinatra/test/scss_test.rb +89 -0
  137. data/vendor/sinatra/test/server_test.rb +48 -0
  138. data/vendor/sinatra/test/settings_test.rb +561 -0
  139. data/vendor/sinatra/test/sinatra_test.rb +12 -0
  140. data/vendor/sinatra/test/slim_test.rb +84 -0
  141. data/vendor/sinatra/test/static_test.rb +219 -0
  142. data/vendor/sinatra/test/streaming_test.rb +149 -0
  143. data/vendor/sinatra/test/templates_test.rb +333 -0
  144. data/vendor/sinatra/test/textile_test.rb +65 -0
  145. data/vendor/sinatra/test/views/a/in_a.str +1 -0
  146. data/vendor/sinatra/test/views/ascii.erb +2 -0
  147. data/vendor/sinatra/test/views/b/in_b.str +1 -0
  148. data/vendor/sinatra/test/views/calc.html.erb +1 -0
  149. data/vendor/sinatra/test/views/error.builder +3 -0
  150. data/vendor/sinatra/test/views/error.erb +3 -0
  151. data/vendor/sinatra/test/views/error.haml +3 -0
  152. data/vendor/sinatra/test/views/error.sass +2 -0
  153. data/vendor/sinatra/test/views/explicitly_nested.str +1 -0
  154. data/vendor/sinatra/test/views/foo/hello.test +1 -0
  155. data/vendor/sinatra/test/views/hello.builder +1 -0
  156. data/vendor/sinatra/test/views/hello.coffee +1 -0
  157. data/vendor/sinatra/test/views/hello.creole +1 -0
  158. data/vendor/sinatra/test/views/hello.erb +1 -0
  159. data/vendor/sinatra/test/views/hello.haml +1 -0
  160. data/vendor/sinatra/test/views/hello.less +5 -0
  161. data/vendor/sinatra/test/views/hello.liquid +1 -0
  162. data/vendor/sinatra/test/views/hello.mab +1 -0
  163. data/vendor/sinatra/test/views/hello.md +1 -0
  164. data/vendor/sinatra/test/views/hello.nokogiri +1 -0
  165. data/vendor/sinatra/test/views/hello.rabl +2 -0
  166. data/vendor/sinatra/test/views/hello.radius +1 -0
  167. data/vendor/sinatra/test/views/hello.rdoc +1 -0
  168. data/vendor/sinatra/test/views/hello.sass +2 -0
  169. data/vendor/sinatra/test/views/hello.scss +3 -0
  170. data/vendor/sinatra/test/views/hello.slim +1 -0
  171. data/vendor/sinatra/test/views/hello.str +1 -0
  172. data/vendor/sinatra/test/views/hello.test +1 -0
  173. data/vendor/sinatra/test/views/hello.textile +1 -0
  174. data/vendor/sinatra/test/views/hello.wlang +1 -0
  175. data/vendor/sinatra/test/views/hello.yajl +1 -0
  176. data/vendor/sinatra/test/views/layout2.builder +3 -0
  177. data/vendor/sinatra/test/views/layout2.erb +2 -0
  178. data/vendor/sinatra/test/views/layout2.haml +2 -0
  179. data/vendor/sinatra/test/views/layout2.liquid +2 -0
  180. data/vendor/sinatra/test/views/layout2.mab +2 -0
  181. data/vendor/sinatra/test/views/layout2.nokogiri +3 -0
  182. data/vendor/sinatra/test/views/layout2.rabl +3 -0
  183. data/vendor/sinatra/test/views/layout2.radius +2 -0
  184. data/vendor/sinatra/test/views/layout2.slim +3 -0
  185. data/vendor/sinatra/test/views/layout2.str +2 -0
  186. data/vendor/sinatra/test/views/layout2.test +1 -0
  187. data/vendor/sinatra/test/views/layout2.wlang +2 -0
  188. data/vendor/sinatra/test/views/nested.str +1 -0
  189. data/vendor/sinatra/test/views/utf8.erb +2 -0
  190. data/vendor/sinatra/test/wlang_test.rb +70 -0
  191. data/vendor/sinatra/test/yajl_test.rb +86 -0
  192. metadata +414 -0
@@ -0,0 +1,3 @@
1
+ module Devcenter
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,6 @@
1
+ # please add general patterns to your global ignore list
2
+ # see https://github.com/github/gitignore#readme
3
+
4
+ /pkg
5
+ /doc/api
6
+ /Gemfile.lock
@@ -0,0 +1,16 @@
1
+ ---
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - rbx-18mode
7
+ - rbx-19mode
8
+ - jruby-18mode
9
+ - jruby-19mode
10
+ - jruby-head
11
+ - ruby-head
12
+ matrix:
13
+ include:
14
+ - { rvm: 1.8.7, env: tilt=master }
15
+ - { rvm: 1.9.3, env: rack=master }
16
+ - { rvm: 1.9.3, env: tilt=master }
@@ -0,0 +1,4 @@
1
+ --readme README.rdoc
2
+ --title 'Sinatra API Documentation'
3
+ --charset utf-8
4
+ 'lib/**/*.rb' - '*.rdoc'
@@ -0,0 +1,61 @@
1
+ Sinatra was designed and developed by Blake Mizerany (bmizerany) in
2
+ California.
3
+
4
+ Sinatra would not have been possible without strong company backing.
5
+ In the past, financial and emotional support have been provided mainly by
6
+ [Heroku](http://heroku.com), [GitHub](http://github.com) and
7
+ [Engine Yard](http://www.engineyard.com/), and is now taken care of by
8
+ [Travis CI](http://travis-ci.com/).
9
+
10
+ Special thanks to the following extraordinary individuals, who-out which
11
+ Sinatra would not be possible:
12
+
13
+ * Ryan Tomayko (rtomayko) for constantly fixing whitespace errors 60d5006
14
+ * Ezra Zygmuntowicz (ezmobius) for initial help and letting Blake steal
15
+ some of merbs internal code.
16
+ * Ari Lerner (http://xnot.org/) for his evangelism, spirit, and gumption
17
+ that got Sinatra recognized from Day 1.
18
+ * Christopher Schneid (cschneid) for The Book, the blog (gittr.com),
19
+ irclogger.com, and a bunch of useful patches.
20
+ * Markus Prinz (cypher) for patches over the years, caring about
21
+ the README, and hanging in there when times were rough.
22
+ * Simon Rozet (sr) for a ton of doc patches, HAML options, and all that
23
+ advocacy stuff he's going to do for 1.0.
24
+ * Erik Kastner (kastner) for fixing `MIME_TYPES` under Rack 0.5.
25
+ * Ben Bleything (bleything) for caring about HTTP status codes and doc fixes.
26
+ * Igal Koshevoy (igal) for root path detection under Thin/Passenger.
27
+ * Jon Crosby (jcrosby) for coffee breaks, doc fixes, and just because, man.
28
+ * Karel Minarik (karmi) for screaming until the website came back up.
29
+ * Jeremy Evans (jeremyevans) for unbreaking optional path params (twice!)
30
+ * The GitHub guys for stealing Blake's table.
31
+ * Nickolas Means (nmeans) for Sass template support.
32
+ * Victor Hugo Borja (vic) for splat'n routes specs and doco.
33
+ * Avdi Grimm (avdi) for basic RSpec support.
34
+ * Jack Danger Canty for a more accurate root directory and for making me
35
+ watch [this](http://www.youtube.com/watch?v=ueaHLHgskkw) just now.
36
+ * Mathew Walker for making escaped paths work with static files.
37
+ * Millions of Us for having the problem that led to Sinatra's conception.
38
+ * Songbird for the problems that helped Sinatra's future become realized.
39
+ * Rick Olson (technoweenie) for the killer plug at RailsConf '08.
40
+ * Steven Garcia for the amazing custom artwork you see on 404's and 500's
41
+ * Pat Nakajima (nakajima) for fixing non-nested params in nested params Hash's.
42
+ * Konstantin Haase for his hard work and ongoing commitment to improving
43
+ Sinatra, for 1.1.0, 1.2.0 and beyond..
44
+ * Zachary Scott for adding Konstantin to the AUTHORS file. He also did help
45
+ writing the book, but mainly for adding Konstantin.
46
+ * Gabriel Andretta for having people wonder whether our documentation is
47
+ actually in English or in Spanish.
48
+ * Vasily Polovnyov, Nickolay Schwarz, Luciano Sousa, Wu Jiang, Mickael Riga,
49
+ Bernhard Essl, Janos Hardi, Kouhei Yanagita and "burningTyger" for willingly
50
+ translating whatever ends up in the README.
51
+ * [Wordy](http://www.wordy.com/) for proofreading our README. 73e137d
52
+ * cactus for digging through code and specs, multiple times.
53
+ * Nicolás Sanguinetti (foca) for strong demand of karma and shaping
54
+ helpers/register.
55
+
56
+ and last but not least:
57
+
58
+ * Frank Sinatra (chairman of the board) for having so much class he
59
+ deserves a web-framework named after him.
60
+
61
+ For a complete list of all contributors to Sinatra itself, run `rake authors`.
@@ -0,0 +1,91 @@
1
+ # Why use bundler?
2
+ # Well, not all development dependencies install on all rubies. Moreover, `gem
3
+ # install sinatra --development` doesn't work, as it will also try to install
4
+ # development dependencies of our dependencies, and those are not conflict free.
5
+ # So, here we are, `bundle install`.
6
+ #
7
+ # If you have issues with a gem: `bundle install --without-coffee-script`.
8
+
9
+ RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE
10
+ source :rubygems unless ENV['QUICK']
11
+ gemspec
12
+
13
+ gem 'rake'
14
+ gem 'rack-test', '>= 0.5.6'
15
+ gem 'ci_reporter', :group => :ci
16
+
17
+ # Allows stuff like `tilt=1.2.2 bundle install` or `tilt=master ...`.
18
+ # Used by the CI.
19
+ github = "git://github.com/%s.git"
20
+ repos = {'tilt' => github % "rtomayko/tilt", 'rack' => github % "rack/rack"}
21
+
22
+ %w[tilt rack].each do |lib|
23
+ dep = case ENV[lib]
24
+ when 'stable', nil then nil
25
+ when /(\d+\.)+\d+/ then "~> " + ENV[lib].sub("#{lib}-", '')
26
+ else {:git => repos[lib], :branch => dep}
27
+ end
28
+ gem lib, dep
29
+ end
30
+
31
+ gem 'haml', '>= 3.0'
32
+ gem 'sass' if RUBY_VERSION < "2.0"
33
+ gem 'builder'
34
+ gem 'erubis'
35
+ gem 'slim', '~> 1.0'
36
+ gem 'temple', '!= 0.3.3'
37
+ gem 'coffee-script', '>= 2.0'
38
+ gem 'rdoc'
39
+ gem 'kramdown'
40
+ gem 'maruku'
41
+ gem 'creole'
42
+ gem 'markaby'
43
+ gem 'radius'
44
+ gem 'rabl' unless RUBY_ENGINE =~ /jruby|maglev/
45
+
46
+ unless RUBY_ENGINE == 'rbx' and RUBY_VERSION > '1.9'
47
+ gem 'wlang', '>= 2.0.1' unless RUBY_ENGINE == "maglev"
48
+ gem 'liquid'
49
+ end
50
+
51
+ if RUBY_ENGINE == 'jruby'
52
+ gem 'nokogiri', '!= 1.5.0'
53
+ gem 'jruby-openssl'
54
+ gem 'trinidad'
55
+ else
56
+ gem 'yajl-ruby'
57
+ gem 'nokogiri'
58
+ gem 'thin'
59
+ end
60
+
61
+ if RUBY_ENGINE == "ruby" and RUBY_VERSION > '1.9'
62
+ gem 'less', '~> 2.0'
63
+ else
64
+ gem 'less', '~> 1.0'
65
+ end
66
+
67
+ if RUBY_ENGINE != 'jruby' or not ENV['TRAVIS']
68
+ # C extensions
69
+ gem 'rdiscount'
70
+ platforms(:ruby_18) do
71
+ gem 'redcarpet'
72
+ gem 'mongrel'
73
+ end
74
+ gem 'RedCloth' unless RUBY_ENGINE == "macruby"
75
+ gem 'puma'
76
+
77
+ ## bluecloth is broken
78
+ #gem 'bluecloth'
79
+ end
80
+
81
+ gem 'net-http-server'
82
+
83
+ platforms :ruby_18, :jruby do
84
+ gem 'json' unless RUBY_VERSION > '1.9' # is there a jruby but 1.8 only selector?
85
+ end
86
+
87
+ platforms :mri_18 do
88
+ # bundler platforms are broken
89
+ next if RUBY_ENGINE != 'ruby' or RUBY_VERSION > "1.8"
90
+ gem 'rcov'
91
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2007, 2008, 2009, 2010, 2011 Blake Mizerany
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,2116 @@
1
+ = Sinatra
2
+
3
+ <i>Wichtig: Dieses Dokument ist eine Übersetzung aus dem Englischen und unter
4
+ Umständen nicht auf dem aktuellen Stand.</i>
5
+
6
+ Sinatra ist eine
7
+ {DSL}[http://de.wikipedia.org/wiki/Domänenspezifische_Sprache], die das
8
+ schnelle Erstellen von Webanwendungen in Ruby mit minimalem Aufwand ermöglicht:
9
+
10
+ # myapp.rb
11
+ require 'sinatra'
12
+ get '/' do
13
+ 'Hallo Welt!'
14
+ end
15
+
16
+ Einfach via +rubygems+ installieren und starten:
17
+
18
+ gem install sinatra
19
+ ruby -rubygems myapp.rb
20
+
21
+ Die Seite kann nun unter http://localhost:4567 betrachtet werden.
22
+
23
+ Es wird empfohlen, den Thin-Server via <tt>gem install thin</tt> zu
24
+ installieren, den Sinatra dann, soweit vorhanden, automatisch verwendet.
25
+
26
+ == Routen
27
+
28
+ In Sinatra wird eine Route durch eine HTTP-Methode und ein URL-Muster
29
+ definiert. Jeder dieser Routen wird ein Ruby-Block zugeordnet:
30
+
31
+ get '/' do
32
+ .. zeige etwas ..
33
+ end
34
+
35
+ post '/' do
36
+ .. erstelle etwas ..
37
+ end
38
+
39
+ put '/' do
40
+ .. update etwas ..
41
+ end
42
+
43
+ delete '/' do
44
+ .. entferne etwas ..
45
+ end
46
+
47
+ options '/' do
48
+ .. zeige, was wir können ..
49
+ end
50
+
51
+ Die Routen werden in der Reihenfolge durchlaufen, in der sie definiert wurden.
52
+ Das erste Routen-Muster, das mit dem Request übereinstimmt, wird ausgeführt.
53
+
54
+ Die Muster der Routen können benannte Parameter beinhalten, die über den
55
+ <tt>params</tt>-Hash zugänglich gemacht werden:
56
+
57
+ get '/hallo/:name' do
58
+ # passt auf "GET /hallo/foo" und "GET /hallo/bar"
59
+ # params[:name] ist 'foo' oder 'bar'
60
+ "Hallo #{params[:name]}!"
61
+ end
62
+
63
+ Man kann auf diese auch mit Block-Parametern zugreifen:
64
+
65
+ get '/hallo/:name' do |n|
66
+ "Hallo #{n}!"
67
+ end
68
+
69
+ Routen-Muster können auch mit Splat- oder Wildcard-Parametern über das
70
+ <tt>params[:splat]</tt>-Array angesprochen werden:
71
+
72
+ get '/sag/*/zu/*' do
73
+ # passt auf /sag/hallo/zu/welt
74
+ params[:splat] # => ["hallo", "welt"]
75
+ end
76
+
77
+ get '/download/*.*' do
78
+ # passt auf /download/pfad/zu/datei.xml
79
+ params[:splat] # => ["pfad/zu/datei", "xml"]
80
+ end
81
+
82
+ Oder mit Block-Parametern:
83
+
84
+ get '/download/*.*' do |pfad, endung|
85
+ [pfad, endung] # => ["Pfad/zu/Datei", "xml"]
86
+ end
87
+
88
+ Routen mit regulären Ausdrücken sind auch möglich:
89
+
90
+ get %r{/hallo/([\w]+)} do
91
+ "Hallo, #{params[:captures].first}!"
92
+ end
93
+
94
+ Und auch hier können Block-Parameter genutzt werden:
95
+
96
+ get %r{/hallo/([\w]+)} do |c|
97
+ "Hallo, #{c}!"
98
+ end
99
+
100
+ Routen-Muster können auch mit optionalen Parametern ausgestattet werden:
101
+
102
+ get '/posts.?:format?' do
103
+ # passt auf "GET /posts" sowie jegliche Erweiterung
104
+ # wie "GET /posts.json", "GET /posts.xml" etc.
105
+ end
106
+
107
+ Anmerkung: Solange man den sog. Path Traversal Attack-Schutz nicht deaktiviert
108
+ (siehe weiter unten), kann es sein, dass der Request-Pfad noch vor dem Abgleich
109
+ mit den Routen modifiziert wird.
110
+
111
+ === Bedingungen
112
+
113
+ An Routen können eine Vielzahl von Bedingungen angehängt werden, die erfüllt
114
+ sein müssen, damit der Block ausgeführt wird. Möglich wäre etwa eine
115
+ Einschränkung des User-Agents:
116
+
117
+ get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
118
+ "Du verwendest Songbird Version #{params[:agent][0]}"
119
+ end
120
+
121
+ get '/foo' do
122
+ # passt auf andere Browser
123
+ end
124
+
125
+ Andere mitgelieferte Bedingungen sind +host_name+ und +provides+:
126
+
127
+ get '/', :host_name => /^admin\./ do
128
+ "Adminbereich, Zugriff verweigert!"
129
+ end
130
+
131
+ get '/', :provides => 'html' do
132
+ haml :index
133
+ end
134
+
135
+ get '/', :provides => ['rss', 'atom', 'xml'] do
136
+ builder :feed
137
+ end
138
+
139
+ Es können auch andere Bedingungen relativ einfach hinzugefügt werden:
140
+
141
+ set(:probability) { |value| condition { rand <= value } }
142
+
143
+ get '/auto_gewinnen', :probability => 0.1 do
144
+ "Du hast gewonnen!"
145
+ end
146
+
147
+ get '/auto_gewinnen' do
148
+ "Tut mir leid, verloren."
149
+ end
150
+
151
+ Bei Bedingungen, die mehrere Werte annehmen können, sollte ein Splat verwendet
152
+ werden:
153
+
154
+ set(:auth) do |*roles| # <- hier kommt der Splat ins Spiel
155
+ condition do
156
+ unless logged_in? && roles.any? {|role| current_user.in_role? role }
157
+ redirect "/login/", 303
158
+ end
159
+ end
160
+ end
161
+
162
+ get "/mein/account/", :auth => [:user, :admin] do
163
+ "Mein Account"
164
+ end
165
+
166
+ get "/nur/admin/", :auth => :admin do
167
+ "Nur Admins dürfen hier rein!"
168
+ end
169
+
170
+ === Rückgabewerte
171
+
172
+ Durch den Rückgabewert eines Routen-Blocks wird mindestens der Response-Body
173
+ festgelegt, der an den HTTP-Client, bzw. die nächste Rack-Middleware,
174
+ weitergegeben wird. Im Normalfall handelt es sich hierbei, wie in den
175
+ vorangehenden Beispielen zu sehen war, um einen String. Es werden allerdings
176
+ auch andere Werte akzeptiert.
177
+
178
+ Es kann jedes gültige Objekt zurückgegeben werden, bei dem es sich entweder um
179
+ einen Rack-Rückgabewert, einen Rack-Body oder einen HTTP-Status-Code handelt:
180
+
181
+ * Ein Array mit drei Elementen: <tt>[Status (Fixnum), Headers (Hash),
182
+ Response-Body (antwortet auf #each)]</tt>.
183
+ * Ein Array mit zwei Elementen: <tt>[Status (Fixnum), Response-Body (antwortet
184
+ auf #each)]</tt>.
185
+ * Ein Objekt, das auf <tt>#each</tt> antwortet und den an diese Methode
186
+ übergebenen Block nur mit Strings als Übergabewerte aufruft.
187
+ * Ein Fixnum, das den Status-Code festlegt.
188
+
189
+ Damit lässt sich relativ einfach Streaming implementieren:
190
+
191
+ class Stream
192
+ def each
193
+ 100.times { |i| yield "#{i}\n" }
194
+ end
195
+ end
196
+
197
+ get('/') { Stream.new }
198
+
199
+ Ebenso kann die +stream+-Helfer-Methode (s.u.) verwendet werden, die Streaming
200
+ direkt in die Route integriert.
201
+
202
+ === Eigene Routen-Muster
203
+
204
+ Wie oben schon beschrieben, ist Sinatra von Haus aus mit Unterstützung für
205
+ String-Muster und Reguläre Ausdrücke zum Abgleichen von Routen ausgestattet.
206
+ Das muss aber noch nicht alles sein, es können ohne großen Aufwand eigene
207
+ Routen-Muster erstellt werden:
208
+
209
+ class AllButPattern
210
+ Match = Struct.new(:captures)
211
+
212
+ def initialize(except)
213
+ @except = except
214
+ @captures = Match.new([])
215
+ end
216
+
217
+ def match(str)
218
+ @captures unless @except === str
219
+ end
220
+ end
221
+
222
+ def all_but(pattern)
223
+ AllButPattern.new(pattern)
224
+ end
225
+
226
+ get all_but("/index") do
227
+ # ...
228
+ end
229
+
230
+ Beachte, dass das obige Beispiel etwas übertrieben wirkt. Es geht auch
231
+ einfacher:
232
+
233
+ get // do
234
+ pass if request.path_info == "/index"
235
+ # ...
236
+ end
237
+
238
+ Oder unter Verwendung eines negativen look ahead:
239
+
240
+ get %r{^(?!/index$)} do
241
+ # ...
242
+ end
243
+
244
+ == Statische Dateien
245
+
246
+ Statische Dateien werden aus dem <tt>./public</tt>-Ordner ausgeliefert. Es ist
247
+ möglich, einen anderen Ort zu definieren, indem man die
248
+ <tt>:public_folder</tt>-Option setzt:
249
+
250
+ set :public_folder, File.dirname(__FILE__) + '/static'
251
+
252
+ Zu beachten ist, dass der Ordnername public nicht Teil der URL ist. Die Datei
253
+ <tt>./public/css/style.css</tt> ist unter
254
+ <tt>http://example.com/css/style.css</tt> zu finden.
255
+
256
+ Um den <tt>Cache-Control</tt>-Header mit Informationen zu versorgen, verwendet
257
+ man die <tt>:static_cache_control</tt>-Einstellung (s.u.).
258
+
259
+ == Views/Templates
260
+
261
+ Alle Templatesprachen verwenden ihre eigene Renderingmethode, die jeweils
262
+ einen String zurückgibt:
263
+
264
+ get '/' do
265
+ erb :index
266
+ end
267
+
268
+ Dieses Beispiel rendert <tt>views/index.erb</tt>.
269
+
270
+ Anstelle eines Templatenamens kann man auch direkt die Templatesprache
271
+ verwenden:
272
+
273
+ get '/' do
274
+ code = "<%= Time.now %>"
275
+ erb code
276
+ end
277
+
278
+ Templates nehmen ein zweite Argument an, den Options-Hash:
279
+
280
+ get '/' do
281
+ erb :index, :layout => :post
282
+ end
283
+
284
+ Dieses Beispiel rendert <tt>views/index.erb</tt> eingebettet in
285
+ <tt>views/post.erb</tt> (Voreinstellung ist <tt>views/layout.erb</tt>, sofern
286
+ es vorhanden ist.)
287
+
288
+ Optionen, die Sinatra nicht versteht, werden an das Template weitergereicht:
289
+
290
+ get '/' do
291
+ haml :index, :format => :html5
292
+ end
293
+
294
+ Für alle Templates können auch generelle Einstellungen festgelegt werden:
295
+
296
+ set :haml, :format => :html5
297
+
298
+ get '/' do
299
+ haml :index
300
+ end
301
+
302
+ Optionen, die an die Rendermethode weitergegeben werden, überschreiben die
303
+ Einstellungen, die mit +set+ festgelegt wurden.
304
+
305
+ Mögliche Einstellungen:
306
+
307
+ [locals]
308
+ Liste von lokalen Variablen, die and das Dokument weitergegeben werden.
309
+ Praktisch für Partials.
310
+ Beispiel: <tt>erb "<%= foo %>", :locals => {:foo => "bar"}</tt>
311
+
312
+ [default_encoding]
313
+ Gibt die Stringkodierung an, die verwendet werden soll. Voreingestellt auf
314
+ <tt>settings.default_encoding</tt>.
315
+
316
+ [views]
317
+ Ordner, aus dem die Templates heraus geladen werden. Voreingestellt auf
318
+ <tt>settings.views</tt>.
319
+
320
+ [layout]
321
+ Legt fest, ob ein Layouttemplate verwendet werden soll oder nicht (+true+
322
+ oder +false+). Ist es ein Symbol, dass legt es fest, welches Template als
323
+ Layout verwendet wird. Beispiel:
324
+ <tt>erb :index, :layout => !request.xhr?</tt>
325
+
326
+ [content_type]
327
+ Content-Type den das Template ausgibt. Voreinstellung hängt von der
328
+ Templatesprache ab.
329
+
330
+ [scope]
331
+ Scope, in dem das Template gerendert wird. Liegt standardmäßig innerhalb der
332
+ App-Instanz. Wird Scope geändert, sind Instanzvariablen und Helfermethoden
333
+ nicht verfügbar.
334
+
335
+ [layout_engine]
336
+ Legt fest, welcher Renderer für das Layout verantwortlich ist.
337
+ Hilfreich für Sprachen, die sonst keine Templates unterstützen.
338
+ Voreingestellt auf den Renderer, der für das Template verwendet wird.
339
+ Beispiel: <tt>set :rdoc, :layout_engine => :erb</tt>
340
+
341
+ Sinatra geht davon aus, dass die Templates sich im <tt>./views</tt> Verzeichnis
342
+ befinden. Es kann jedoch ein anderer Ordner festgelegt werden:
343
+
344
+ set :views, settings.root + '/templates'
345
+
346
+ Es ist zu beachten, dass immer mit Symbolen auf Templates verwiesen werden
347
+ muss, auch dann, wenn sie sich in einem Unterordner befinden:
348
+
349
+ haml :'unterverzeichnis/template'
350
+
351
+ Rendering-Methoden rendern jeden String direkt.
352
+
353
+ === Verfügbare Templatesprachen
354
+
355
+ Einige Sprachen haben mehrere Implementierungen. Um festzulegen, welche
356
+ verwendet wird (und dann auch Thread-sicher ist), verwendet man am besten zu
357
+ Beginn ein 'require':
358
+
359
+ require 'rdiscount' # oder require 'bluecloth'
360
+ get('/') { markdown :index }
361
+
362
+ === Haml Templates
363
+
364
+ Abhängigkeit:: {haml}[http://haml.info/]
365
+ Dateierweiterung:: <tt>.haml</tt>
366
+ Beispiel:: <tt>haml :index, :format => :html5</tt>
367
+
368
+ === Erb Templates
369
+
370
+ Abhängigkeit:: {erubis}[http://www.kuwata-lab.com/erubis/] oder
371
+ erb (included in Ruby)
372
+ Dateierweiterungen:: <tt>.erb</tt>, <tt>.rhtml</tt> oder <tt>.erubis</tt>
373
+ (nur Erubis)
374
+ Beispiel:: <tt>erb :index</tt>
375
+
376
+ === Builder Templates
377
+
378
+ Abhängigkeit:: {builder}[http://builder.rubyforge.org/]
379
+ Dateierweiterung:: <tt>.builder</tt>
380
+ Beispiel:: <tt>builder { |xml| xml.em "Hallo" }</tt>
381
+
382
+ Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel).
383
+
384
+ === Nokogiri Templates
385
+
386
+ Abhängigkeit:: {nokogiri}[http://nokogiri.org/]
387
+ Dateierweiterung:: <tt>.nokogiri</tt>
388
+ Beispiel:: <tt>nokogiri { |xml| xml.em "Hallo" }</tt>
389
+
390
+ Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel).
391
+
392
+ === Sass Templates
393
+
394
+ Abhängigkeit:: {sass}[http://sass-lang.com/]
395
+ Dateierweiterung:: <tt>.sass</tt>
396
+ Beispiel:: <tt>sass :stylesheet, :style => :expanded</tt>
397
+
398
+ === SCSS Templates
399
+
400
+ Abhängigkeit:: {sass}[http://sass-lang.com/]
401
+ Dateierweiterung:: <tt>.scss</tt>
402
+ Beispiel:: <tt>scss :stylesheet, :style => :expanded</tt>
403
+
404
+ === Less Templates
405
+
406
+ Abhängigkeit:: {less}[http://www.lesscss.org/]
407
+ Dateierweiterung:: <tt>.less</tt>
408
+ Beispiel:: <tt>less :stylesheet</tt>
409
+
410
+ === Liquid Templates
411
+
412
+ Abhängigkeit:: {liquid}[http://www.liquidmarkup.org/]
413
+ Dateierweiterung:: <tt>.liquid</tt>
414
+ Beispiel:: <tt>liquid :index, :locals => { :key => 'Wert' }</tt>
415
+
416
+ Da man aus dem Liquid-Template heraus keine Ruby-Methoden aufrufen kann
417
+ (ausgenommen +yield+), wird man üblicherweise locals verwenden wollen, mit
418
+ denen man Variablen weitergibt.
419
+
420
+ === Markdown Templates
421
+
422
+ Abhängigkeit:: {rdiscount}[https://github.com/rtomayko/rdiscount],
423
+ {redcarpet}[https://github.com/vmg/redcarpet],
424
+ {bluecloth}[http://deveiate.org/projects/BlueCloth],
425
+ {kramdown}[http://kramdown.rubyforge.org/] *oder*
426
+ {maruku}[http://maruku.rubyforge.org/]
427
+ Dateierweiterungen:: <tt>.markdown</tt>, <tt>.mkd</tt> und <tt>.md</tt>
428
+ Beispiel:: <tt>markdown :index, :layout_engine => :erb</tt>
429
+
430
+ Da man aus den Markdown-Templates heraus keine Ruby-Methoden aufrufen und auch
431
+ keine locals verwenden kann, wird man Markdown üblicherweise in Kombination mit
432
+ anderen Renderern verwenden wollen:
433
+
434
+ erb :overview, :locals => { :text => markdown(:einfuehrung) }
435
+
436
+ Beachte, dass man die +markdown+-Methode auch aus anderen Templates heraus
437
+ aufrufen kann:
438
+
439
+ %h1 Gruß von Haml!
440
+ %p= markdown(:Grüße)
441
+
442
+ Da man Ruby nicht von Markdown heraus aufrufen kann, können auch Layouts nicht
443
+ in Markdown geschrieben werden. Es ist aber möglich, einen Renderer für die
444
+ Templates zu verwenden und einen anderen für das Layout, indem die
445
+ <tt>:layout_engine</tt>-Option verwendet wird.
446
+
447
+ === Textile Templates
448
+
449
+ Abhängigkeit:: {RedCloth}[http://redcloth.org/]
450
+ Dateierweiterung:: <tt>.textile</tt>
451
+ Beispiel:: <tt>textile :index, :layout_engine => :erb</tt>
452
+
453
+ Da man aus dem Textile-Template heraus keine Ruby-Methoden aufrufen und auch
454
+ keine locals verwenden kann, wird man Textile üblicherweise in Kombination mit
455
+ anderen Renderern verwenden wollen:
456
+
457
+ erb :overview, :locals => { :text => textile(:einfuehrung) }
458
+
459
+ Beachte, dass man die +textile+-Methode auch aus anderen Templates heraus
460
+ aufrufen kann:
461
+
462
+ %h1 Gruß von Haml!
463
+ %p= textile(:Grüße)
464
+
465
+ Da man Ruby nicht von Textile heraus aufrufen kann, können auch Layouts nicht
466
+ in Textile geschrieben werden. Es ist aber möglich, einen Renderer für die
467
+ Templates zu verwenden und einen anderen für das Layout, indem die
468
+ <tt>:layout_engine</tt>-Option verwendet wird.
469
+
470
+ === RDoc Templates
471
+
472
+ Abhängigkeit:: {rdoc}[http://rdoc.rubyforge.org/]
473
+ Dateierweiterung:: <tt>.rdoc</tt>
474
+ Beispiel:: <tt>textile :README, :layout_engine => :erb</tt>
475
+
476
+ Da man aus dem RDoc-Template heraus keine Ruby-Methoden aufrufen und auch
477
+ keine locals verwenden kann, wird man RDoc üblicherweise in Kombination mit
478
+ anderen Renderern verwenden wollen:
479
+
480
+ erb :overview, :locals => { :text => rdoc(:einfuehrung) }
481
+
482
+ Beachte, dass man die +rdoc+-Methode auch aus anderen Templates heraus
483
+ aufrufen kann:
484
+
485
+ %h1 Gruß von Haml!
486
+ %p= rdoc(:Grüße)
487
+
488
+ Da man Ruby nicht von RDoc heraus aufrufen kann, können auch Layouts nicht
489
+ in RDoc geschrieben werden. Es ist aber möglich, einen Renderer für die
490
+ Templates zu verwenden und einen anderen für das Layout, indem die
491
+ <tt>:layout_engine</tt>-Option verwendet wird.
492
+
493
+ === Radius Templates
494
+
495
+ Abhängigkeit:: {radius}[http://radius.rubyforge.org/]
496
+ Dateierweiterung:: <tt>.radius</tt>
497
+ Beispiel:: <tt>radius :index, :locals => { :key => 'Wert' }</tt>
498
+
499
+ Da man aus dem Radius-Template heraus keine Ruby-Methoden aufrufen kann, wird
500
+ man üblicherweise locals verwenden wollen, mit denen man Variablen weitergibt.
501
+
502
+ === Markaby Templates
503
+
504
+ Abhängigkeit:: {markaby}[http://markaby.github.com/]
505
+ Dateierweiterung:: <tt>.mab</tt>
506
+ Beispiel:: <tt>markaby { h1 "Willkommen!" }</tt>
507
+
508
+ Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel).
509
+
510
+ === RABL Templates
511
+
512
+ Abhängigkeit:: {rabl}[https://github.com/nesquena/rabl]
513
+ Dateierweiterung:: <tt>.rabl</tt>
514
+ Beispiel:: <tt>rabl :index</tt>
515
+
516
+ === Slim Templates
517
+
518
+ Abhängigkeit:: {slim}[http://slim-lang.com/]
519
+ Dateierweiterung:: <tt>.slim</tt>
520
+ Beispiel:: <tt>slim :index</tt>
521
+
522
+ === Creole Templates
523
+
524
+ Abhängigkeit:: {creole}[https://github.com/minad/creole]
525
+ Dateierweiterung:: <tt>.creole</tt>
526
+ Beispiel:: <tt>creole :wiki, :layout_engine => :erb</tt>
527
+
528
+ Da man aus dem Creole-Template heraus keine Ruby-Methoden aufrufen und auch
529
+ keine locals verwenden kann, wird man Creole üblicherweise in Kombination mit
530
+ anderen Renderern verwenden wollen:
531
+
532
+ erb :overview, :locals => { :text => creole(:einfuehrung) }
533
+
534
+ Beachte, dass man die +creole+-Methode auch aus anderen Templates heraus
535
+ aufrufen kann:
536
+
537
+ %h1 Gruß von Haml!
538
+ %p= creole(:Grüße)
539
+
540
+ Da man Ruby nicht von Creole heraus aufrufen kann, können auch Layouts nicht
541
+ in Creole geschrieben werden. Es ist aber möglich, einen Renderer für die
542
+ Templates zu verwenden und einen anderen für das Layout, indem die
543
+ <tt>:layout_engine</tt>-Option verwendet wird.
544
+
545
+ === CoffeeScript Templates
546
+
547
+ Abhängigkeit:: {coffee-script}[https://github.com/josh/ruby-coffee-script]
548
+ und eine {Möglichkeit JavaScript auszuführen}[https://github.com/sstephenson/execjs/blob/master/README.md#readme]
549
+ Dateierweiterung:: <tt>.coffee</tt>
550
+ Beispiel:: <tt>coffee :index</tt>
551
+
552
+ === WLang Templates
553
+
554
+ Abhängigkeit:: {wlang}[https://github.com/blambeau/wlang/]
555
+ Dateierweiterung:: <tt>.wlang</tt>
556
+ Beispiel:: <tt>wlang :index, :locals => { :key => 'value' }</tt>
557
+
558
+ Ruby-Methoden in wlang aufzurufen entspricht nicht den idiomatischen Vorgaben
559
+ von wlang, es bietet sich deshalb an, <tt>:locals</tt> zu verwenden.
560
+ Layouts, die wlang und +yield+ verwenden, werden aber trotzdem unterstützt.
561
+
562
+ === Eingebettete Templates
563
+
564
+ get '/' do
565
+ haml '%div.title Hallo Welt'
566
+ end
567
+
568
+ Rendert den eingebetteten Template-String.
569
+
570
+ === Auf Variablen in Templates zugreifen
571
+
572
+ Templates werden in demselben Kontext ausgeführt wie Routen. Instanzvariablen
573
+ in Routen sind auch direkt im Template verfügbar:
574
+
575
+ get '/:id' do
576
+ @foo = Foo.find(params[:id])
577
+ haml '%h1= @foo.name'
578
+ end
579
+
580
+ Oder durch einen expliziten Hash von lokalen Variablen:
581
+
582
+ get '/:id' do
583
+ foo = Foo.find(params[:id])
584
+ haml '%h1= bar.name', :locals => { :bar => foo }
585
+ end
586
+
587
+ Dies wird typischerweise bei Verwendung von Subtemplates (partials) in anderen
588
+ Templates eingesetzt.
589
+
590
+ === Inline-Templates
591
+
592
+ Templates können auch am Ende der Datei definiert werden:
593
+
594
+ require 'sinatra'
595
+
596
+ get '/' do
597
+ haml :index
598
+ end
599
+
600
+ __END__
601
+
602
+ @@ layout
603
+ %html
604
+ = yield
605
+
606
+ @@ index
607
+ %div.title Hallo Welt!!!!!
608
+
609
+ Anmerkung: Inline-Templates, die in der Datei definiert sind, die <tt>require
610
+ 'sinatra'</tt> aufruft, werden automatisch geladen. Um andere Inline-Templates
611
+ in anderen Dateien aufzurufen, muss explizit <tt>enable :inline_templates</tt>
612
+ verwendet werden.
613
+
614
+ === Benannte Templates
615
+
616
+ Templates können auch mit der Top-Level <tt>template</tt>-Methode definiert
617
+ werden:
618
+
619
+ template :layout do
620
+ "%html\n =yield\n"
621
+ end
622
+
623
+ template :index do
624
+ '%div.title Hallo Welt!'
625
+ end
626
+
627
+ get '/' do
628
+ haml :index
629
+ end
630
+
631
+ Wenn ein Template mit dem Namen "layout" existiert, wird es bei jedem Aufruf
632
+ verwendet. Durch <tt>:layout => false</tt> kann das Ausführen verhindert
633
+ werden:
634
+
635
+ get '/' do
636
+ haml :index, :layout => request.xhr?
637
+ end
638
+
639
+ === Dateiendungen zuordnen
640
+
641
+ Um eine Dateiendung einer Template-Engine zuzuordnen, kann
642
+ <tt>Tilt.register</tt> genutzt werden. Wenn etwa die Dateiendung +tt+ für
643
+ Textile-Templates genutzt werden soll, lässt sich dies wie folgt
644
+ bewerkstelligen:
645
+
646
+ Tilt.register :tt, Tilt[:textile]
647
+
648
+ === Eine eigene Template-Engine hinzufügen
649
+
650
+ Zu allererst muss die Engine bei Tilt registriert und danach eine
651
+ Rendering-Methode erstellt werden:
652
+
653
+ Tilt.register :mtt, MeineTolleTemplateEngine
654
+
655
+ helpers do
656
+ def mtt(*args) render(:mtt, *args) end
657
+ end
658
+
659
+ get '/' do
660
+ mtt :index
661
+ end
662
+
663
+ Dieser Code rendert <tt>./views/application.mtt</tt>. Siehe
664
+ github.com/rtomayko/tilt[https://github.com/rtomayko/tilt], um mehr über Tilt
665
+ zu lernen.
666
+
667
+ == Filter
668
+
669
+ Before-Filter werden vor jedem Request in demselben Kontext, wie danach die
670
+ Routen, ausgeführt. So können etwa Request und Antwort geändert werden.
671
+ Gesetzte Instanzvariablen in Filtern können in Routen und Templates verwendet
672
+ werden:
673
+
674
+ before do
675
+ @note = 'Hi!'
676
+ request.path_info = '/foo/bar/baz'
677
+ end
678
+
679
+ get '/foo/*' do
680
+ @note #=> 'Hi!'
681
+ params[:splat] #=> 'bar/baz'
682
+ end
683
+
684
+ After-Filter werden nach jedem Request in demselben Kontext ausgeführt und
685
+ können ebenfalls Request und Antwort ändern. In Before-Filtern gesetzte
686
+ Instanzvariablen können in After-Filtern verwendet werden:
687
+
688
+ after do
689
+ puts response.status
690
+ end
691
+
692
+ Filter können optional auch mit einem Muster ausgestattet werden, welches auf
693
+ den Request-Pfad passen muss, damit der Filter ausgeführt wird:
694
+
695
+ before '/protected/*' do
696
+ authenticate!
697
+ end
698
+
699
+ after '/create/:slug' do |slug|
700
+ session[:last_slug] = slug
701
+ end
702
+
703
+ Ähnlich wie Routen können Filter auch mit weiteren Bedingungen eingeschränkt
704
+ werden:
705
+
706
+ before :agent => /Songbird/ do
707
+ # ...
708
+ end
709
+
710
+ after '/blog/*', :host_name => 'example.com' do
711
+ # ...
712
+ end
713
+
714
+ == Helfer
715
+
716
+ Durch die Top-Level <tt>helpers</tt>-Methode werden sogenannte Helfer-Methoden
717
+ definiert, die in Routen und Templates verwendet werden können:
718
+
719
+ helpers do
720
+ def bar(name)
721
+ "#{name}bar"
722
+ end
723
+ end
724
+
725
+ get '/:name' do
726
+ bar(params[:name])
727
+ end
728
+
729
+ === Sessions verwenden
730
+ Sessions werden verwendet, um Zustände zwischen den Requests zu speichern.
731
+ Sind sie aktiviert, kann ein Session-Hash je Benutzer-Session verwendet werden.
732
+
733
+ enable :sessions
734
+
735
+ get '/' do
736
+ "value = " << session[:value].inspect
737
+ end
738
+
739
+ get '/:value' do
740
+ session[:value] = params[:value]
741
+ end
742
+
743
+ Beachte, dass <tt>enable :sessions</tt> alle Daten in einem Cookie speichert.
744
+ Unter Umständen kann dies negative Effekte haben, z.B. verursachen viele Daten
745
+ höheren, teilweise überflüssigen Traffic. Um das zu vermeiden, kann eine Rack-
746
+ Session-Middleware verwendet werden. Dabei wird auf <tt>enable :sessions</tt>
747
+ verzichtet und die Middleware wie üblich im Programm eingebunden:
748
+
749
+ use Rack::Session::Pool, :expire_after => 2592000
750
+
751
+ get '/' do
752
+ "value = " << session[:value].inspect
753
+ end
754
+
755
+ get '/:value' do
756
+ session[:value] = params[:value]
757
+ end
758
+
759
+ Um die Sicherheit zu erhöhen, werden Cookies, die Session-Daten führen, mit
760
+ einem sogenannten Session-Secret signiert. Da sich dieses Geheimwort bei jedem
761
+ Neustart der Applikation automatisch ändert, ist es sinnvoll, ein eigenes zu
762
+ wählen, damit sich alle Instanzen der Applikation dasselbe Session-Secret
763
+ teilen:
764
+
765
+ set :session_secret, 'super secret'
766
+
767
+ Zur weiteren Konfiguration kann man einen Hash mit Optionen in den +sessions+
768
+ Einstellungen ablegen.
769
+
770
+ set :sessions, :domain => 'foo.com'
771
+
772
+ == Anhalten
773
+
774
+ Zum sofortigen Stoppen eines Request in einem Filter oder einer Route:
775
+
776
+ halt
777
+
778
+ Der Status kann beim Stoppen auch angegeben werden:
779
+
780
+ halt 410
781
+
782
+ Oder auch den Response-Body:
783
+
784
+ halt 'Hier steht der Body'
785
+
786
+ Oder beides:
787
+
788
+ halt 401, 'verschwinde!'
789
+
790
+ Sogar mit Headern:
791
+
792
+ halt 402, {'Content-Type' => 'text/plain'}, 'Rache'
793
+
794
+ Natürlich ist es auch möglich, ein Template mit +halt+ zu verwenden:
795
+
796
+ halt erb(:error)
797
+
798
+ == Weiterspringen
799
+
800
+ Eine Route kann mittels <tt>pass</tt> zu der nächsten passenden Route springen:
801
+
802
+ get '/raten/:wer' do
803
+ pass unless params[:wer] == 'Frank'
804
+ 'Du hast mich!'
805
+ end
806
+
807
+ get '/raten/*' do
808
+ 'Du hast mich nicht!'
809
+ end
810
+
811
+ Der Block wird sofort verlassen und es wird nach der nächsten treffenden Route
812
+ gesucht. Ein 404-Fehler wird zurückgegeben, wenn kein treffendes Routen-Muster
813
+ gefunden wird.
814
+
815
+ === Eine andere Route ansteuern
816
+
817
+ Manchmal entspricht +pass+ nicht den Anforderungen, wenn das Ergebnis einer
818
+ anderen Route gefordert wird. Um das zu erreichen, lässt sich +call+ nutzen:
819
+
820
+ get '/foo' do
821
+ status, headers, body = call env.merge("PATH_INFO" => '/bar')
822
+ [status, headers, body.map(&:upcase)]
823
+ end
824
+
825
+ get '/bar' do
826
+ "bar"
827
+ end
828
+
829
+ Beachte, dass in dem oben angegeben Beispiel die Performance erheblich erhöht
830
+ werden kann, wenn <tt>"bar"</tt> in eine Helfer-Methode umgewandelt wird, auf
831
+ die <tt>/foo</tt> und <tt>/bar</tt> zugreifen können.
832
+
833
+ Wenn der Request innerhalb derselben Applikations-Instanz aufgerufen und keine
834
+ Kopie der Instanz erzeugt werden soll, kann <tt>call!</tt> anstelle von
835
+ +call+ verwendet werden.
836
+
837
+ Die Rack-Spezifikationen enthalten weitere Informationen zu +call+.
838
+
839
+ === Body, Status-Code und Header setzen
840
+
841
+ Es ist möglich und empfohlen, den Status-Code sowie den Response-Body mit einem
842
+ Returnwert in der Route zu setzen. In manchen Situationen kann es jedoch sein,
843
+ dass der Body an irgendeiner anderen Stelle während der Ausführung gesetzt
844
+ wird. Das lässt sich mit der Helfer-Methode +body+ bewerkstelligen. Wird +body+
845
+ verwendet, lässt sich der Body jederzeit über diese Methode aufrufen:
846
+
847
+ get '/foo' do
848
+ body "bar"
849
+ end
850
+
851
+ after do
852
+ puts body
853
+ end
854
+
855
+ Ebenso ist es möglich, einen Block an +body+ weiterzureichen, der dann vom
856
+ Rack-Handler ausgeführt wird (lässt sich z.B. zur Umsetzung von Streaming
857
+ einsetzen, siehe auch "Rückgabewerte").
858
+
859
+ Vergleichbar mit +body+ lassen sich auch Status-Code und Header setzen:
860
+
861
+ get '/foo' do
862
+ status 418
863
+ headers \
864
+ "Allow" => "BREW, POST, GET, PROPFIND, WHEN",
865
+ "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
866
+ halt "Ich bin ein Teekesselchen"
867
+ end
868
+
869
+ Genau wie bei +body+ liest ein Aufrufen von +headers+ oder +status+ ohne
870
+ Argumente den aktuellen Wert aus.
871
+
872
+ === Response-Streams
873
+
874
+ In manchen Situationen sollen Daten bereits an den Client zurückgeschickt
875
+ werden, bevor ein vollständiger Response bereit steht. Manchmal will man die
876
+ Verbindung auch erst dann beenden und Daten so lange an den Client
877
+ zurückschicken, bis er die Verbindung abbricht. Für diese Fälle gibt es die
878
+ +stream+-Helfer-Methode, die es einem erspart eigene Lösungen zu schreiben:
879
+
880
+ get '/' do
881
+ stream do |out|
882
+ out << "Das ist ja mal wieder fanta -\n"
883
+ sleep 0.5
884
+ out << " (bitte warten…) \n"
885
+ sleep 1
886
+ out << "- stisch!\n"
887
+ end
888
+ end
889
+
890
+ Damit lassen sich Streaming-APIs realisieren, sog.
891
+ {Server Sent Events}[http://dev.w3.org/html5/eventsource/] die als Basis für
892
+ {WebSockets}[http://en.wikipedia.org/wiki/WebSocket] dienen. Ebenso können sie
893
+ verwendet werden, um den Durchsatz zu erhöhen, wenn ein Teil der Daten von
894
+ langsamen Ressourcen abhängig ist.
895
+
896
+ Es ist zu beachten, dass das Verhalten beim Streaming, insbesondere die Anzahl
897
+ nebenläufiger Anfragen, stark davon abhängt, welcher Webserver für die
898
+ Applikation verwendet wird. Einige Server, z.B. WEBRick, unterstützen Streaming
899
+ nicht oder nur teilweise. Sollte der Server Streaming nicht unterstützen, wird
900
+ ein vollständiger Response-Body zurückgeschickt, sobald der an +stream+
901
+ weitergegebene Block abgearbeitet ist. Mit Shotgun funktioniert Streaming z.B.
902
+ überhaupt nicht.
903
+
904
+ Ist der optionale Parameter +keep_open+ aktiviert, wird beim gestreamten Objekt
905
+ +close+ nicht aufgerufen und es ist einem überlassen dies an einem beliebigen
906
+ späteren Zeitpunkt nachholen. Die Funktion ist jedoch nur bei Event-gesteuerten
907
+ Serven wie Thin oder Rainbows möglich, andere Server werden trotzdem den Stream
908
+ beenden:
909
+
910
+ set :server, :thin
911
+ connections = []
912
+
913
+ get '/' do
914
+ # Den Stream offen halten
915
+ stream(:keep_open) { |out| connections << out }
916
+ end
917
+
918
+ post '/' do
919
+ # In alle offenen Streams schreiben
920
+ connections.each { |out| out << params[:message] << "\n" }
921
+ "Nachricht verschickt"
922
+ end
923
+
924
+ === Logger
925
+
926
+ Im Geltungsbereich eines Request stellt die +logger+ Helfer-Methode eine
927
+ +Logger+ Instanz zur Verfügung:
928
+
929
+ get '/' do
930
+ logger.info "es passiert gerade etwas"
931
+ # ...
932
+ end
933
+
934
+ Der Logger übernimmt dabei automatisch alle im Rack-Handler eingestellten Log-
935
+ Vorgaben. Ist Loggen ausgeschaltet, gibt die Methode ein Leerobjekt zurück.
936
+ In den Routen und Filtern muss man sich also nicht weiter darum kümmern.
937
+
938
+ Beachte, dass das Loggen standardmäßig nur für <tt>Sinatra::Application</tt>
939
+ voreingestellt ist. Wird über <tt>Sinatra::Base</tt> vererbt, muss es erst
940
+ aktiviert werden:
941
+
942
+ class MyApp < Sinatra::Base
943
+ configure :production, :development do
944
+ enable :logging
945
+ end
946
+ end
947
+
948
+ Damit auch keine Middleware das Logging aktivieren kann, muss die +logging+
949
+ Einstellung auf +nil+ gesetzt werden. Das heißt aber auch, dass +logger+ in
950
+ diesem Fall +nil+ zurückgeben wird. Üblicherweise wird das eingesetzt, wenn ein
951
+ eigener Logger eingerichtet werden soll. Sinatra wird dann verwenden, was in
952
+ <tt>env['rack.logger']</tt> eingetragen ist.
953
+
954
+ == Mime-Types
955
+
956
+ Wenn <tt>send_file</tt> oder statische Dateien verwendet werden, kann es
957
+ vorkommen, dass Sinatra den Mime-Typ nicht kennt. Registriert wird dieser mit
958
+ +mime_type+ per Dateiendung:
959
+
960
+ configure do
961
+ mime_type :foo, 'text/foo'
962
+ end
963
+
964
+ Es kann aber auch der +content_type+-Helfer verwendet werden:
965
+
966
+ get '/' do
967
+ content_type :foo
968
+ "foo foo foo"
969
+ end
970
+
971
+ === URLs generieren
972
+
973
+ Zum Generieren von URLs sollte die +url+-Helfer-Methode genutzen werden, so
974
+ z.B. beim Einsatz von Haml:
975
+
976
+ %a{:href => url('/foo')} foo
977
+
978
+ Soweit vorhanden, wird Rücksicht auf Proxys und Rack-Router genommen.
979
+
980
+ Diese Methode ist ebenso über das Alias +to+ zu erreichen (siehe Beispiel
981
+ unten).
982
+
983
+ === Browser-Umleitung
984
+
985
+ Eine Browser-Umleitung kann mithilfe der +redirect+-Helfer-Methode erreicht
986
+ werden:
987
+
988
+ get '/foo' do
989
+ redirect to('/bar')
990
+ end
991
+
992
+ Weitere Parameter werden wie Argumente der +halt+-Methode behandelt:
993
+
994
+ redirect to('/bar'), 303
995
+ redirect 'http://google.com', 'Hier bist du falsch'
996
+
997
+ Ebenso leicht lässt sich ein Schritt zurück mit dem Alias
998
+ <tt>redirect back</tt> erreichen:
999
+
1000
+ get '/foo' do
1001
+ "<a href='/bar'>mach was</a>"
1002
+ end
1003
+
1004
+ get '/bar' do
1005
+ mach_was
1006
+ redirect back
1007
+ end
1008
+
1009
+ Um Argumente an ein Redirect weiterzugeben, können sie entweder dem Query
1010
+ übergeben:
1011
+
1012
+ redirect to('/bar?summe=42')
1013
+
1014
+ oder eine Session verwendet werden:
1015
+
1016
+ enable :sessions
1017
+
1018
+ get '/foo' do
1019
+ session[:secret] = 'foo'
1020
+ redirect to('/bar')
1021
+ end
1022
+
1023
+ get '/bar' do
1024
+ session[:secret]
1025
+ end
1026
+
1027
+
1028
+ === Cache einsetzen
1029
+
1030
+ Ein sinnvolles Einstellen von Header-Daten ist die Grundlage für ein
1031
+ ordentliches HTTP-Caching.
1032
+
1033
+ Der Cache-Control-Header lässt sich ganz einfach einstellen:
1034
+
1035
+ get '/' do
1036
+ cache_control :public
1037
+ "schon gecached!"
1038
+ end
1039
+
1040
+ Profitipp: Caching im before-Filter aktivieren
1041
+
1042
+ before do
1043
+ cache_control :public, :must_revalidate, :max_age => 60
1044
+ end
1045
+
1046
+ Bei Verwendung der +expires+-Helfermethode zum Setzen des gleichnamigen
1047
+ Headers, wird <tt>Cache-Control</tt> automatisch eigestellt:
1048
+
1049
+ before do
1050
+ expires 500, :public, :must_revalidate
1051
+ end
1052
+
1053
+ Um alles richtig zu machen, sollten auch +etag+ oder +last_modified+ verwendet
1054
+ werden. Es wird empfohlen, dass diese Helfer aufgerufen werden *bevor* die
1055
+ eigentliche Arbeit anfängt, da sie sofort eine Antwort senden, wenn der
1056
+ Client eine aktuelle Version im Cache vorhält:
1057
+
1058
+ get '/article/:id' do
1059
+ @article = Article.find params[:id]
1060
+ last_modified @article.updated_at
1061
+ etag @article.sha1
1062
+ erb :article
1063
+ end
1064
+
1065
+ ebenso ist es möglich einen
1066
+ {schwachen ETag}[http://de.wikipedia.org/wiki/HTTP_ETag] zu verwenden:
1067
+
1068
+ etag @article.sha1, :weak
1069
+
1070
+ Diese Helfer führen nicht das eigentliche Caching aus, sondern geben die dafür
1071
+ notwendigen Informationen an den Cache weiter. Für schnelle Reverse-Proxy
1072
+ Cache-Lösungen bietet sich z.B.
1073
+ {rack-cache}[https://github.com/rtomayko/rack-cache] an:
1074
+
1075
+ require "rack/cache"
1076
+ require "sinatra"
1077
+
1078
+ use Rack::Cache
1079
+
1080
+ get '/' do
1081
+ cache_control :public, :max_age => 36000
1082
+ sleep 5
1083
+ "hello"
1084
+ end
1085
+
1086
+ Um den <tt>Cache-Control</tt>-Header mit Informationen zu versorgen, verwendet
1087
+ man die <tt>:static_cache_control</tt>-Einstellung (s.u.).
1088
+
1089
+ Nach RFC 2616 sollte sich die Anwendung anders verhalten, wenn ein
1090
+ If-Match oder ein If-None_match Header auf <tt>*</tt> gesetzt wird in
1091
+ Abhängigkeit davon, ob die Resource bereits existiert. Sinatra geht
1092
+ davon aus, dass Ressourcen bei sicheren Anfragen (z.B. bei get oder Idempotenten
1093
+ Anfragen wie put) bereits existieren, wobei anderen Ressourcen
1094
+ (besipielsweise bei post), als neue Ressourcen behandelt werden. Dieses
1095
+ Verhalten lässt sich mit der <tt>:new_resource</tt> Option ändern:
1096
+
1097
+ get '/create' do
1098
+ etag '', :new_resource => true
1099
+ Article.create
1100
+ erb :new_article
1101
+ end
1102
+
1103
+ Soll das schwache ETag trotzdem verwendet werden, verwendet man die
1104
+ <tt>:kind</tt> Option:
1105
+
1106
+ etag '', :new_resource => true, :kind => :weak
1107
+
1108
+ === Dateien versenden
1109
+
1110
+ Zum Versenden von Dateien kann die <tt>send_file</tt>-Helfer-Methode verwendet
1111
+ werden:
1112
+
1113
+ get '/' do
1114
+ send_file 'foo.png'
1115
+ end
1116
+
1117
+ Für <tt>send_file</tt> stehen einige Hash-Optionen zur Verfügung:
1118
+
1119
+ send_file 'foo.png', :type => :jpg
1120
+
1121
+ [filename]
1122
+ Dateiname als Response. Standardwert ist der eigentliche Dateiname.
1123
+
1124
+ [last_modified]
1125
+ Wert für den Last-Modified-Header, Standardwert ist +mtime+ der Datei.
1126
+
1127
+ [type]
1128
+ Content-Type, der verwendet werden soll. Wird, wenn nicht angegeben, von der
1129
+ Dateiendung abgeleitet.
1130
+
1131
+ [disposition]
1132
+ Verwendet für Content-Disposition. Mögliche Werte sind: +nil+ (Standard),
1133
+ <tt>:attachment</tt> und <tt>:inline</tt>.
1134
+
1135
+ [length]
1136
+ Content-Length-Header. Standardwert ist die Dateigröße.
1137
+
1138
+ Soweit vom Rack-Handler unterstützt, werden neben der Übertragung über den
1139
+ Ruby-Prozess auch andere Möglichkeiten genutzt. Bei Verwendung der
1140
+ <tt>send_file</tt>-Helfer-Methode kümmert sich Sinatra selbstständig um die
1141
+ Range-Requests.
1142
+
1143
+ == Das Request-Objekt
1144
+
1145
+ Auf das +request+-Objekt der eigehenden Anfrage kann vom Anfrage-Scope aus
1146
+ zugegriffen werden:
1147
+
1148
+ # App läuft unter http://example.com/example
1149
+ get '/foo' do
1150
+ t = %w[text/css text/html application/javascript]
1151
+ request.accept # ['text/html', '*/*']
1152
+ request.accept? 'text/xml' # true
1153
+ request.preferred_type(t) # 'text/html'
1154
+ request.body # Request-Body des Client (siehe unten)
1155
+ request.scheme # "http"
1156
+ request.script_name # "/example"
1157
+ request.path_info # "/foo"
1158
+ request.port # 80
1159
+ request.request_method # "GET"
1160
+ request.query_string # ""
1161
+ request.content_length # Länge des request.body
1162
+ request.media_type # Medientypus von request.body
1163
+ request.host # "example.com"
1164
+ request.get? # true (ähnliche Methoden für andere Verben)
1165
+ request.form_data? # false
1166
+ request["IRGENDEIN_HEADER"] # Wert von IRGENDEIN_HEADER header
1167
+ request.referrer # Der Referrer des Clients oder '/'
1168
+ request.user_agent # User-Agent (verwendet in der :agent Bedingung)
1169
+ request.cookies # Hash des Browser-Cookies
1170
+ request.xhr? # Ist das hier ein Ajax-Request?
1171
+ request.url # "http://example.com/example/foo"
1172
+ request.path # "/example/foo"
1173
+ request.ip # IP-Adresse des Clients
1174
+ request.secure? # false (true wenn SSL)
1175
+ request.forwarded? # true (Wenn es hinter einem Reverse-Proxy verwendet wird)
1176
+ request.env # vollständiger env-Hash von Rack übergeben
1177
+ end
1178
+
1179
+ Manche Optionen, wie etwa <tt>script_name</tt> oder <tt>path_info</tt>, sind
1180
+ auch schreibbar:
1181
+
1182
+ before { request.path_info = "/" }
1183
+
1184
+ get "/" do
1185
+ "Alle Anfragen kommen hier an!"
1186
+ end
1187
+
1188
+ Der <tt>request.body</tt> ist ein IO- oder StringIO-Objekt:
1189
+
1190
+ post "/api" do
1191
+ request.body.rewind # falls schon jemand davon gelesen hat
1192
+ daten = JSON.parse request.body.read
1193
+ "Hallo #{daten['name']}!"
1194
+ end
1195
+
1196
+ === Anhänge
1197
+
1198
+ Damit der Browser erkennt, dass ein Response gespeichert und nicht im Browser
1199
+ angezeigt werden soll, kann der +attachment+-Helfer verwendet werden:
1200
+
1201
+ get '/' do
1202
+ attachment
1203
+ "Speichern!"
1204
+ end
1205
+
1206
+ Ebenso kann eine Dateiname als Parameter hinzugefügt werden:
1207
+
1208
+ get '/' do
1209
+ attachment "info.txt"
1210
+ "Speichern!"
1211
+ end
1212
+
1213
+ === Umgang mit Datum und Zeit
1214
+
1215
+ Sinatra bietet eine <tt>time_for</tt>-Helfer-Methode, die aus einem gegebenen
1216
+ Wert ein Time-Objekt generiert. Ebenso kann sie nach +DateTime+, +Date+ und
1217
+ ähnliche Klassen konvertieren:
1218
+
1219
+ get '/' do
1220
+ pass if Time.now > time_for('Dec 23, 2012')
1221
+ "noch Zeit"
1222
+ end
1223
+
1224
+ Diese Methode wird intern für +expires, +last_modiefied+ und Freunde verwendet.
1225
+ Mit ein paar Handgriffen lässt sich diese Methode also in ihrem Verhalten
1226
+ erweitern, indem man +time_for+ in der eigenen Applikation überschreibt:
1227
+
1228
+ helpers do
1229
+ def time_for(value)
1230
+ case value
1231
+ when :yesterday then Time.now - 24*60*60
1232
+ when :tomorrow then Time.now + 24*60*60
1233
+ else super
1234
+ end
1235
+ end
1236
+ end
1237
+
1238
+ get '/' do
1239
+ last_modified :yesterday
1240
+ expires :tomorrow
1241
+ "Hallo"
1242
+ end
1243
+
1244
+ === Nachschlagen von Template-Dateien
1245
+
1246
+ Die <tt>find_template</tt>-Helfer-Methode wird genutzt, um Template-Dateien zum
1247
+ Rendern aufzufinden:
1248
+
1249
+ find_template settings.views, 'foo', Tilt[:haml] do |file|
1250
+ puts "könnte diese hier sein: #{file}"
1251
+ end
1252
+
1253
+ Das ist zwar nicht wirklich brauchbar, aber wenn man sie überschreibt, kann sie
1254
+ nützlich werden, um eigene Nachschlage-Mechanismen einzubauen. Zum Beispiel
1255
+ dann, wenn mehr als nur ein view-Verzeichnis verwendet werden soll:
1256
+
1257
+ set :views, ['views', 'templates']
1258
+
1259
+ helpers do
1260
+ def find_template(views, name, engine, &block)
1261
+ Array(views).each { |v| super(v, name, engine, &block) }
1262
+ end
1263
+ end
1264
+
1265
+ Ein anderes Beispiel wäre, verschiedene Vereichnisse für verschiedene Engines
1266
+ zu verwenden:
1267
+
1268
+ set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
1269
+
1270
+ helpers do
1271
+ def find_template(views, name, engine, &block)
1272
+ _, folder = views.detect { |k,v| engine == Tilt[k] }
1273
+ folder ||= views[:default]
1274
+ super(folder, name, engine, &block)
1275
+ end
1276
+ end
1277
+
1278
+ Ebensogut könnte eine Extension aber auch geschrieben und mit anderen geteilt
1279
+ werden!
1280
+
1281
+ Beachte, dass <tt>find_template</tt> nicht prüft, ob eine Datei tatsächlich
1282
+ existiert. Es wird lediglich der angegebene Block aufgerufen und nach allen
1283
+ möglichen Pfaden gesucht. Das ergibt kein Performance-Problem, da +render+
1284
+ +block+ verwendet, sobald eine Datei gefunden wurde. Ebenso werden
1285
+ Template-Pfade samt Inhalt gecached, solange nicht im Entwicklungsmodus
1286
+ gearbeitet wird. Das sollte im Hinterkopf behalten werden, wenn irgendwelche
1287
+ verrückten Methoden zusammenbastelt werden.
1288
+
1289
+ == Konfiguration
1290
+
1291
+ Wird einmal beim Starten in jedweder Umgebung ausgeführt:
1292
+
1293
+ configure do
1294
+ # setze eine Option
1295
+ set :option, 'wert'
1296
+
1297
+ # setze mehrere Optionen
1298
+ set :a => 1, :b => 2
1299
+
1300
+ # das gleiche wie `set :option, true`
1301
+ enable :option
1302
+
1303
+ # das gleiche wie `set :option, false`
1304
+ disable :option
1305
+
1306
+ # dynamische Einstellungen mit Blöcken
1307
+ set(:css_dir) { File.join(views, 'css') }
1308
+ end
1309
+
1310
+ Läuft nur, wenn die Umgebung (RACK_ENV-Umgebungsvariable) auf
1311
+ <tt>:production</tt> gesetzt ist:
1312
+
1313
+ configure :production do
1314
+ ...
1315
+ end
1316
+
1317
+ Läuft nur, wenn die Umgebung auf <tt>:production</tt> oder auf <tt>:test</tt>
1318
+ gesetzt ist:
1319
+
1320
+ configure :production, :test do
1321
+ ...
1322
+ end
1323
+
1324
+ Diese Einstellungen sind über +settings+ erreichbar:
1325
+
1326
+ configure do
1327
+ set :foo, 'bar'
1328
+ end
1329
+
1330
+ get '/' do
1331
+ settings.foo? # => true
1332
+ settings.foo # => 'bar'
1333
+ ...
1334
+ end
1335
+
1336
+ === Einstellung des Angriffsschutzes
1337
+
1338
+ Sinatra verwendet
1339
+ {Rack::Protection}[https://github.com/rkh/rack-protection#readme], um die
1340
+ Anwendung vor häufig vorkommenden Angriffen zu schützen. Diese Voreinstellung
1341
+ lässt sich selbstverständlich deaktivieren, der damit verbundene
1342
+ Geschwindigkeitszuwachs steht aber in keinem Verhätnis zu den möglichen
1343
+ Risiken.
1344
+
1345
+ disable :protection
1346
+
1347
+ Um einen bestimmten Schutzmechanismus zu deaktivieren, fügt man +protection+
1348
+ einen Hash mit Optionen hinzu:
1349
+
1350
+ set :protection, :except => :path_traversal
1351
+
1352
+ Neben Strings akzeptiert <tt>:except</tt> auch Arrays, um gleich mehrere
1353
+ Schutzmechanismen zu deaktivieren:
1354
+
1355
+ set :protection, :except => [:path_traversal, :session_hijacking]
1356
+
1357
+ === Mögliche Einstellungen
1358
+
1359
+ [absolute_redirects] Wenn ausgeschaltet, wird Sinatra relative Redirects
1360
+ zulassen. Jedoch ist Sinatra dann nicht mehr mit RFC
1361
+ 2616 (HTTP 1.1) konform, das nur absolute Redirects
1362
+ zulässt.
1363
+
1364
+ Sollte eingeschaltet werden, wenn die Applikation
1365
+ hinter einem Reverse-Proxy liegt, der nicht ordentlich
1366
+ eingerichtet ist. Beachte, dass die
1367
+ +url+-Helfer-Methode nach wie vor absolute URLs
1368
+ erstellen wird, es sei denn, es wird als zweiter
1369
+ Parameter +false+ angegeben.
1370
+
1371
+ Standardmäßig nicht aktiviert.
1372
+
1373
+ [add_charsets] Mime-Types werden hier automatisch der Helfer-Methode
1374
+ <tt>content_type</tt> zugeordnet.
1375
+
1376
+ Es empfielt sich, Werte hinzuzufügen statt sie zu
1377
+ überschreiben:
1378
+
1379
+ settings.add_charsets << "application/foobar"
1380
+
1381
+ [app_file] Pfad zur Hauptdatei der Applikation. Wird verwendet, um
1382
+ das Wurzel-, Inline-, View- und öffentliche Verzeichnis
1383
+ des Projekts festzustellen.
1384
+
1385
+ [bind] IP-Address, an die gebunden wird
1386
+ (Standardwert: 0.0.0.0). Wird nur für den eingebauten
1387
+ Server verwendet.
1388
+
1389
+ [default_encoding] Das Encoding, falls keines angegeben wurde.
1390
+ Standardwert ist <tt>"utf-8"</tt>.
1391
+
1392
+ [dump_errors] Fehler im Log anzeigen.
1393
+
1394
+ [environment] Momentane Umgebung. Standardmäßig auf
1395
+ <tt>content_type</tt> oder <tt>"development"</tt>
1396
+ eingestellt, soweit ersteres nicht vorhanden.
1397
+
1398
+ [logging] Den Logger verwenden.
1399
+
1400
+ [lock] Jeder Request wird gelocked. Es kann nur ein Request
1401
+ pro Ruby-Prozess gleichzeitig verarbeitet werden.
1402
+
1403
+ Eingeschaltet, wenn die Applikation threadsicher ist.
1404
+ Standardmäßig nicht aktiviert.
1405
+
1406
+ [method_override] Verwende <tt>_method</tt>, um put/delete-Formulardaten
1407
+ in Browsern zu verwenden, die dies normalerweise nicht
1408
+ unterstützen.
1409
+
1410
+ [port] Port für die Applikation. Wird nur im internen Server
1411
+ verwendet.
1412
+
1413
+ [prefixed_redirects] Entscheidet, ob <tt>request.script_name</tt> in
1414
+ Redirects eingefügt wird oder nicht, wenn kein
1415
+ absoluter Pfad angegeben ist. Auf diese Weise verhält
1416
+ sich <tt>redirect '/foo'</tt> so, als wäre es ein
1417
+ <tt>redirect to('/foo')</tt>. Standardmäßig nicht
1418
+ aktiviert.
1419
+
1420
+ [protection] Legt fest, ob der Schutzmechanismus für häufig
1421
+ Vorkommende Webangriffe auf Webapplikationen aktiviert
1422
+ wird oder nicht. Weitere Informationen im vorhergehenden
1423
+ Abschnitt.
1424
+
1425
+ [public_folder] Das öffentliche Verzeichnis, aus dem Daten zur
1426
+ Verfügung gestellt werden können. Wird nur dann
1427
+ verwendet, wenn statische Daten zur Verfügung
1428
+ gestellt werden können (s.u. <tt>static</tt>
1429
+ Option). Leitet sich von der <tt>app_file</tt>
1430
+ Einstellung ab, wenn nicht gesetzt.
1431
+
1432
+ [public_dir] Alias für <tt>public_folder</tt>, s.o.
1433
+
1434
+ [reload_templates] Im development-Modus aktiviert.
1435
+
1436
+ [root] Wurzelverzeichnis des Projekts. Leitet sich von der
1437
+ <tt>app_file</tt> Einstellung ab, wenn nicht gesetzt.
1438
+
1439
+ [raise_errors] Einen Ausnahmezustand aufrufen. Beendet die
1440
+ Applikation. Ist automatisch aktiviert, wenn die
1441
+ Umgebung auf <tt>"test"</tt> eingestellt ist.
1442
+ Ansonsten ist diese Option deaktiviert.
1443
+
1444
+ [run] Wenn aktiviert, wird Sinatra versuchen, den Webserver
1445
+ zu starten. Nicht verwenden, wenn Rackup oder anderes
1446
+ verwendet werden soll.
1447
+
1448
+ [running] Läuft der eingebaute Server? Diese Einstellung nicht
1449
+ ändern!
1450
+
1451
+ [server] Server oder Liste von Servern, die als eingebaute
1452
+ Server zur Verfügung stehen.
1453
+ Standardmäßig auf ['thin', 'mongrel', 'webrick']
1454
+ voreingestellt. Die Anordnung gibt die Priorität vor.
1455
+
1456
+ [sessions] Sessions auf Cookiebasis mittels
1457
+ <tt>Rack::Session::Cookie</tt>aktivieren. Für
1458
+ weitere Infos bitte in der Sektion 'Sessions
1459
+ verwenden' nachschauen.
1460
+
1461
+ [show_exceptions] Bei Fehlern einen Stacktrace im Browseranzeigen. Ist
1462
+ automatisch aktiviert, wenn die Umgebung auf
1463
+ <tt>"development"</tt> eingestellt ist. Ansonsten ist
1464
+ diese Option deaktiviert.
1465
+ Kann auch auf <tt>:after_handler</tt> gestellt werden,
1466
+ um eine anwendungsspezifische Fehlerbehandlung
1467
+ auszulösen, bevor der Fehlerverlauf im Browser
1468
+ angezeigt wird.
1469
+
1470
+ [static] Entscheidet, ob Sinatra statische Dateien zur Verfügung
1471
+ stellen soll oder nicht.
1472
+ Sollte nicht aktiviert werden, wenn ein Server
1473
+ verwendet wird, der dies auch selbstständig erledigen
1474
+ kann. Deaktivieren wird die Performance erhöhen.
1475
+ Standardmäßig aktiviert.
1476
+
1477
+ [static_cache_control] Wenn Sinatra statische Daten zur Verfügung stellt,
1478
+ können mit dieser Einstellung die +Cache-Control+
1479
+ Header zu den Responses hinzugefügt werden. Die
1480
+ Einstellung verwendet dazu die +cache_control+
1481
+ Helfer-Methode. Standardmäßig deaktiviert.
1482
+ Ein Array wird verwendet, um mehrere Werte gleichzeitig
1483
+ zu übergeben:
1484
+ <tt>set :static_cache_control, [:public, :max_age => 300]</tt>
1485
+
1486
+ [views] Verzeichnis der Views. Leitet sich von der
1487
+ <tt>app_file</tt> Einstellung ab, wenn nicht gesetzt.
1488
+
1489
+ == Umgebungen
1490
+
1491
+ Es gibt drei voreingestellte Umgebungen in Sinatra: <tt>"development"</tt>,
1492
+ <tt>"production"</tt> und <tt>"test"</tt>. Umgebungen können über die
1493
+ +RACK_ENV+ Umgebungsvariable gesetzt werden. Die Standardeinstellung ist
1494
+ <tt>"development"</tt>. In diesem Modus werden alle Templates zwischen
1495
+ Requests neu geladen. Dazu gibt es besondere Fehlerseiten für 404 Stati
1496
+ und Fehlermeldungen. In <tt>"production"</tt> und <tt>"test"</tt> werden
1497
+ Templates automatisch gecached.
1498
+
1499
+ Um die Anwendung in einer anderen Umgebung auszuführen kann man die
1500
+ <tt>-e</tt> Option verwenden:
1501
+
1502
+ ruby my_app.rb -e [ENVIRONMENT]
1503
+
1504
+ In der Anwendung kann man die die Methoden +development?+, +test?+ und
1505
+ +production?+ verwenden, um die aktuelle Umgebung zu erfahren.
1506
+
1507
+ == Fehlerbehandlung
1508
+
1509
+ Error-Handler laufen in demselben Kontext wie Routen und Filter, was bedeutet,
1510
+ dass alle Goodies wie <tt>haml</tt>, <tt>erb</tt>, <tt>halt</tt>, etc.
1511
+ verwendet werden können.
1512
+
1513
+ === Nicht gefunden
1514
+
1515
+ Wenn eine <tt>Sinatra::NotFound</tt>-Exception geworfen wird oder der
1516
+ Statuscode 404 ist, wird der <tt>not_found</tt>-Handler ausgeführt:
1517
+
1518
+ not_found do
1519
+ 'Seite kann nirgendwo gefunden werden.'
1520
+ end
1521
+
1522
+ === Fehler
1523
+
1524
+ Der +error+-Handler wird immer ausgeführt, wenn eine Exception in einem
1525
+ Routen-Block oder in einem Filter geworfen wurde. Die Exception kann über die
1526
+ <tt>sinatra.error</tt>-Rack-Variable angesprochen werden:
1527
+
1528
+ error do
1529
+ 'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].name
1530
+ end
1531
+
1532
+ Benutzerdefinierte Fehler:
1533
+
1534
+ error MeinFehler do
1535
+ 'Au weia, ' + env['sinatra.error'].message
1536
+ end
1537
+
1538
+ Dann, wenn das passiert:
1539
+
1540
+ get '/' do
1541
+ raise MeinFehler, 'etwas Schlimmes ist passiert'
1542
+ end
1543
+
1544
+ bekommt man dieses:
1545
+
1546
+ Au weia, etwas Schlimmes ist passiert
1547
+
1548
+ Alternativ kann ein Error-Handler auch für einen Status-Code definiert werden:
1549
+
1550
+ error 403 do
1551
+ 'Zugriff verboten'
1552
+ end
1553
+
1554
+ get '/geheim' do
1555
+ 403
1556
+ end
1557
+
1558
+ Oder ein Status-Code-Bereich:
1559
+
1560
+ error 400..510 do
1561
+ 'Hallo?'
1562
+ end
1563
+
1564
+ Sinatra setzt verschiedene <tt>not_found</tt>- und <tt>error</tt>-Handler in
1565
+ der Development-Umgebung.
1566
+
1567
+ == Rack-Middleware
1568
+
1569
+ Sinatra baut auf Rack[http://rack.rubyforge.org/], einem minimalistischen
1570
+ Standard-Interface für Ruby-Webframeworks. Eines der interessantesten
1571
+ Features für Entwickler ist der Support von Middlewares, die zwischen den
1572
+ Server und die Anwendung geschaltet werden und so HTTP-Request und/oder Antwort
1573
+ überwachen und/oder manipulieren können.
1574
+
1575
+ Sinatra macht das Erstellen von Middleware-Verkettungen mit der
1576
+ Top-Level-Methode +use+ zu einem Kinderspiel:
1577
+
1578
+ require 'sinatra'
1579
+ require 'meine_middleware'
1580
+
1581
+ use Rack::Lint
1582
+ use MeineMiddleware
1583
+
1584
+ get '/hallo' do
1585
+ 'Hallo Welt'
1586
+ end
1587
+
1588
+ Die Semantik von +use+ entspricht der gleichnamigen Methode der
1589
+ Rack::Builder[http://rack.rubyforge.org/doc/classes/Rack/Builder.html]-DSL
1590
+ (meist verwendet in Rackup-Dateien). Ein Beispiel dafür ist, dass die
1591
+ +use+-Methode mehrere/verschiedene Argumente und auch Blöcke entgegennimmt:
1592
+
1593
+ use Rack::Auth::Basic do |username, password|
1594
+ username == 'admin' && password == 'geheim'
1595
+ end
1596
+
1597
+ Rack bietet eine Vielzahl von Standard-Middlewares für Logging, Debugging,
1598
+ URL-Routing, Authentifizierung und Session-Verarbeitung. Sinatra verwendet
1599
+ viele von diesen Komponenten automatisch, abhängig von der Konfiguration. So
1600
+ muss +use+ häufig nicht explizit verwendet werden.
1601
+
1602
+ Hilfreiche Middleware gibt es z.B. hier:
1603
+ {rack}[https://github.com/rack/rack/tree/master/lib/rack],
1604
+ {rack-contrib}[https://github.com/rack/rack-contrib#readme],
1605
+ mit {CodeRack}[http://coderack.org/] oder im
1606
+ {Rack wiki}[https://github.com/rack/rack/wiki/List-of-Middleware].
1607
+
1608
+ == Testen
1609
+
1610
+ Sinatra-Tests können mit jedem auf Rack aufbauendem Test-Framework geschrieben
1611
+ werden. {Rack::Test}[http://rdoc.info/github/brynary/rack-test/master/frames]
1612
+ wird empfohlen:
1613
+
1614
+ require 'my_sinatra_app'
1615
+ require 'test/unit'
1616
+ require 'rack/test'
1617
+
1618
+ class MyAppTest < Test::Unit::TestCase
1619
+ include Rack::Test::Methods
1620
+
1621
+ def app
1622
+ Sinatra::Application
1623
+ end
1624
+
1625
+ def test_my_default
1626
+ get '/'
1627
+ assert_equal 'Hallo Welt!', last_response.body
1628
+ end
1629
+
1630
+ def test_with_params
1631
+ get '/meet', :name => 'Frank'
1632
+ assert_equal 'Hallo Frank!', last_response.body
1633
+ end
1634
+
1635
+ def test_with_rack_env
1636
+ get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
1637
+ assert_equal "Du verwendest Songbird!", last_response.body
1638
+ end
1639
+ end
1640
+
1641
+ == Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen
1642
+
1643
+ Das Definieren einer Top-Level-Anwendung funktioniert gut für
1644
+ Mikro-Anwendungen, hat aber Nachteile, wenn wiederverwendbare Komponenten wie
1645
+ Middleware, Rails Metal, einfache Bibliotheken mit Server-Komponenten oder auch
1646
+ Sinatra-Erweiterungen geschrieben werden sollen.
1647
+
1648
+ Das Top-Level geht von einer Konfiguration für eine Mikro-Anwendung aus
1649
+ (wie sie z.B. bei einer einzelnen Anwendungsdatei, <tt>./public</tt>
1650
+ und <tt>./views</tt> Ordner, Logging, Exception-Detail-Seite, usw.). Genau
1651
+ hier kommt <tt>Sinatra::Base</tt> ins Spiel:
1652
+
1653
+ require 'sinatra/base'
1654
+
1655
+ class MyApp < Sinatra::Base
1656
+ set :sessions, true
1657
+ set :foo, 'bar'
1658
+
1659
+ get '/' do
1660
+ 'Hallo Welt!'
1661
+ end
1662
+ end
1663
+
1664
+ Die MyApp-Klasse ist eine unabhängige Rack-Komponente, die als Middleware,
1665
+ Endpunkt oder via Rails Metal verwendet werden kann. Verwendet wird sie durch
1666
+ +use+ oder +run+ von einer Rackup-<tt>config.ru</tt>-Datei oder als
1667
+ Server-Komponente einer Bibliothek:
1668
+
1669
+ MyApp.run! :host => 'localhost', :port => 9090
1670
+
1671
+ Die Methoden der <tt>Sinatra::Base</tt>-Subklasse sind genau dieselben wie die
1672
+ der Top-Level-DSL. Die meisten Top-Level-Anwendungen können mit nur zwei
1673
+ Veränderungen zu <tt>Sinatra::Base</tt> konvertiert werden:
1674
+
1675
+ * Die Datei sollte <tt>require 'sinatra/base'</tt> anstelle von
1676
+ <tt>require 'sinatra/base'</tt> aufrufen, ansonsten werden alle von
1677
+ Sinatras DSL-Methoden in den Top-Level-Namespace importiert.
1678
+ * Alle Routen, Error-Handler, Filter und Optionen der Applikation müssen in
1679
+ einer Subklasse von <tt>Sinatra::Base</tt> definiert werden.
1680
+
1681
+ <tt>Sinatra::Base</tt> ist ein unbeschriebenes Blatt. Die meisten Optionen sind
1682
+ per Standard deaktiviert. Das betrifft auch den eingebauten Server. Siehe
1683
+ {Optionen und Konfiguration}[http://sinatra.github.com/configuration.html] für
1684
+ Details über mögliche Optionen.
1685
+
1686
+ === Modularer vs. klassischer Stil
1687
+
1688
+ Entgegen häufiger Meinungen gibt es nichts gegen den klassischen Stil
1689
+ einzuwenden. Solange es die Applikation nicht beeinträchtigt, besteht kein
1690
+ Grund, eine modulare Applikation zu erstellen.
1691
+
1692
+ Der größte Nachteil der klassischen Sinatra Anwendung gegenüber einer modularen
1693
+ ist die Einschränkung auf eine Sinatra Anwendung pro Ruby-Prozess. Sollen
1694
+ mehrere zum Einsatz kommen, muss auf den modularen Stil umgestiegen werden.
1695
+ Dabei ist es kein Problem klassische und modulare Anwendungen miteinander zu
1696
+ vermischen.
1697
+
1698
+ Bei einem Umstieg, sollten einige Unterschiede in den Einstellungen beachtet
1699
+ werden:
1700
+
1701
+ Szenario Classic Modular
1702
+
1703
+ app_file sinatra ladende Datei Sinatra::Base subklassierende Datei
1704
+ run $0 == app_file false
1705
+ logging true false
1706
+ method_override true false
1707
+ inline_templates true false
1708
+
1709
+ === Eine modulare Applikation bereitstellen
1710
+
1711
+ Es gibt zwei übliche Wege, eine modulare Anwendung zu starten. Zum einen über
1712
+ <tt>run!</tt>:
1713
+
1714
+ # mein_app.rb
1715
+ require 'sinatra/base'
1716
+
1717
+ class MeinApp < Sinatra::Base
1718
+ # ... Anwendungscode hierhin ...
1719
+
1720
+ # starte den Server, wenn die Ruby-Datei direkt ausgeführt wird
1721
+ run! if app_file == $0
1722
+ end
1723
+
1724
+ Starte mit:
1725
+
1726
+ ruby mein_app.rb
1727
+
1728
+ Oder über eine <tt>config.ru</tt>-Datei, die es erlaubt, einen beliebigen
1729
+ Rack-Handler zu verwenden:
1730
+
1731
+ # config.ru
1732
+ require './mein_app'
1733
+ run MeineApp
1734
+
1735
+ Starte:
1736
+
1737
+ rackup -p 4567
1738
+
1739
+ === Eine klassische Anwendung mit einer config.ru verwenden
1740
+
1741
+ Schreibe eine Anwendungsdatei:
1742
+
1743
+ # app.rb
1744
+ require 'sinatra'
1745
+
1746
+ get '/' do
1747
+ 'Hallo Welt!'
1748
+ end
1749
+
1750
+ sowie eine dazugehörige <tt>config.ru</tt>-Datei:
1751
+
1752
+ require './app'
1753
+ run Sinatra::Application
1754
+
1755
+ === Wann sollte eine config.ru-Datei verwendet werden?
1756
+
1757
+ Anzeichen dafür, dass eine <tt>config.ru</tt>-Datei gebraucht wird:
1758
+
1759
+ * Es soll ein anderer Rack-Handler verwendet werden (Passenger, Unicorn,
1760
+ Heroku, ...).
1761
+ * Es gibt mehr als nur eine Subklasse von <tt>Sinatra::Base</tt>.
1762
+ * Sinatra soll als Middleware verwendet werden, nicht als Endpunkt.
1763
+
1764
+ <b>Es gibt keinen Grund, eine <tt>config.ru</tt>-Datei zu verwenden, nur weil
1765
+ eine Anwendung im modularen Stil betrieben werden soll. Ebenso wird keine
1766
+ Anwendung mit modularem Stil benötigt, um eine <tt>config.ru</tt>-Datei zu
1767
+ verwenden.</b>
1768
+
1769
+ === Sinatra als Middleware nutzen
1770
+
1771
+ Es ist nicht nur möglich, andere Rack-Middleware mit Sinatra zu nutzen, es kann
1772
+ außerdem jede Sinatra-Anwendung selbst als Middleware vor jeden beliebigen
1773
+ Rack-Endpunkt gehangen werden. Bei diesem Endpunkt muss es sich nicht um eine
1774
+ andere Sinatra-Anwendung handeln, es kann jede andere Rack-Anwendung sein
1775
+ (Rails/Ramaze/Camping/...):
1776
+
1777
+ require 'sinatra/base'
1778
+
1779
+ class LoginScreen < Sinatra::Base
1780
+ enable :sessions
1781
+
1782
+ get('/login') { haml :login }
1783
+
1784
+ post('/login') do
1785
+ if params[:name] == 'admin' && params[:password] == 'admin'
1786
+ session['user_name'] = params[:name]
1787
+ else
1788
+ redirect '/login'
1789
+ end
1790
+ end
1791
+ end
1792
+
1793
+ class MyApp < Sinatra::Base
1794
+ # Middleware wird vor Filtern ausgeführt
1795
+ use LoginScreen
1796
+
1797
+ before do
1798
+ unless session['user_name']
1799
+ halt "Zugriff verweigert, bitte <a href='/login'>einloggen</a>."
1800
+ end
1801
+ end
1802
+
1803
+ get('/') { "Hallo #{session['user_name']}." }
1804
+ end
1805
+
1806
+ === Dynamische Applikationserstellung
1807
+
1808
+ Manche Situationen erfordern die Erstellung neuer Applikationen zur Laufzeit,
1809
+ ohne dass sie einer Konstanten zugeordnet werden. Dies lässt sich mit
1810
+ <tt>Sinatra.new</tt> erreichen:
1811
+
1812
+ require 'sinatra/base'
1813
+ my_app = Sinatra.new { get('/') { "hallo" } }
1814
+ my_app.run!
1815
+
1816
+ Die Applikation kann mit Hilfe eines optionalen Parameters erstellt werden:
1817
+
1818
+ # config.ru
1819
+ require 'sinatra/base'
1820
+
1821
+ controller = Sinatra.new do
1822
+ enable :logging
1823
+ helpers MyHelpers
1824
+ end
1825
+
1826
+ map('/a') do
1827
+ run Sinatra.new(controller) { get('/') { 'a' } }
1828
+ end
1829
+
1830
+ map('/b') do
1831
+ run Sinatra.new(controller) { get('/') { 'b' } }
1832
+ end
1833
+
1834
+ Das ist besonders dann interessant, wenn Sinatra-Erweiterungen getestet werden
1835
+ oder Sinatra in einer Bibliothek Verwendung findet.
1836
+
1837
+ Ebenso lassen sich damit hervorragend Sinatra-Middlewares erstellen:
1838
+
1839
+ require 'sinatra/base'
1840
+
1841
+ use Sinatra do
1842
+ get('/') { ... }
1843
+ end
1844
+
1845
+ run RailsProject::Application
1846
+
1847
+ == Geltungsbereich und Bindung
1848
+
1849
+ Der Geltungsbereich (Scope) legt fest, welche Methoden und Variablen zur
1850
+ Verfügung stehen.
1851
+
1852
+ === Anwendungs- oder Klassen-Scope
1853
+
1854
+ Jede Sinatra-Anwendung entspricht einer <tt>Sinatra::Base</tt>-Subklasse. Falls
1855
+ die Top- Level-DSL verwendet wird (<tt>require 'sinatra'</tt>), handelt es sich
1856
+ um <tt>Sinatra::Application</tt>, andernfalls ist es jene Subklasse, die
1857
+ explizit angelegt wurde. Auf Klassenebene stehen Methoden wie +get+ oder
1858
+ +before+ zur Verfügung, es gibt aber keinen Zugriff auf das +request+-Object
1859
+ oder die +session+, da nur eine einzige Klasse für alle eingehenden Anfragen
1860
+ genutzt wird.
1861
+
1862
+ Optionen, die via +set+ gesetzt werden, sind Methoden auf Klassenebene:
1863
+
1864
+ class MyApp < Sinatra::Base
1865
+ # Hey, ich bin im Anwendungsscope!
1866
+ set :foo, 42
1867
+ foo # => 42
1868
+
1869
+ get '/foo' do
1870
+ # Hey, ich bin nicht mehr im Anwendungs-Scope!
1871
+ end
1872
+ end
1873
+
1874
+ Im Anwendungs-Scope befindet man sich:
1875
+
1876
+ * In der Anwendungs-Klasse.
1877
+ * In Methoden, die von Erweiterungen definiert werden.
1878
+ * Im Block, der an +helpers+ übergeben wird.
1879
+ * In Procs und Blöcken, die an +set+ übergeben werden.
1880
+ * Der an <tt>Sinatra.new</tt> übergebene Block
1881
+
1882
+ Auf das Scope-Objekt (die Klasse) kann wie folgt zugegriffen werden:
1883
+
1884
+ * Über das Objekt, das an den +configure+-Block übergeben wird (<tt>configure
1885
+ { |c| ... }</tt>).
1886
+ * +settings+ aus den anderen Scopes heraus.
1887
+
1888
+ === Anfrage- oder Instanz-Scope
1889
+
1890
+ Für jede eingehende Anfrage wird eine neue Instanz der Anwendungs-Klasse
1891
+ erstellt und alle Handler in diesem Scope ausgeführt. Aus diesem Scope
1892
+ heraus kann auf +request+ oder +session+ zugegriffen und Methoden wie +erb+
1893
+ oder +haml+ aufgerufen werden. Außerdem kann mit der +settings+-Method auf den
1894
+ Anwendungs-Scope zugegriffen werden:
1895
+
1896
+ class MyApp < Sinatra::Base
1897
+ # Hey, ich bin im Anwendungs-Scope!
1898
+ get '/neue_route/:name' do
1899
+ # Anfrage-Scope für '/neue_route/:name'
1900
+ @value = 42
1901
+
1902
+ settings.get "/#{params[:name]}" do
1903
+ # Anfrage-Scope für "/#{params[:name]}"
1904
+ @value # => nil (nicht dieselbe Anfrage)
1905
+ end
1906
+
1907
+ "Route definiert!"
1908
+ end
1909
+ end
1910
+
1911
+ Im Anfrage-Scope befindet man sich:
1912
+
1913
+ * In get/head/post/put/delete-Blöcken
1914
+ * In before/after-Filtern
1915
+ * In Helfer-Methoden
1916
+ * In Templates
1917
+
1918
+ === Delegation-Scope
1919
+
1920
+ Vom Delegation-Scope aus werden Methoden einfach an den Klassen-Scope
1921
+ weitergeleitet. Dieser verhält sich jedoch nicht 100%ig wie der Klassen-Scope,
1922
+ da man nicht die Bindung der Klasse besitzt: Nur Methoden, die explizit als
1923
+ delegierbar markiert wurden, stehen hier zur Verfügung und es kann nicht auf
1924
+ die Variablen des Klassenscopes zugegriffen werden (mit anderen Worten: es gibt
1925
+ ein anderes +self+). Weitere Delegationen können mit
1926
+ <tt>Sinatra::Delegator.delegate :methoden_name</tt> hinzugefügt werden.
1927
+
1928
+ Im Delegation-Scop befindet man sich:
1929
+
1930
+ * Im Top-Level, wenn <tt>require 'sinatra'</tt> aufgerufen wurde.
1931
+ * In einem Objekt, das mit dem <tt>Sinatra::Delegator</tt>-Mixin erweitert
1932
+ wurde.
1933
+
1934
+ Schau am besten im Code nach: Hier ist
1935
+ {Sinatra::Delegator mixin}[http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064]
1936
+ definiert und wird in den
1937
+ {globalen Namespace eingebunden}[http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb#L25].
1938
+
1939
+ == Kommandozeile
1940
+
1941
+ Sinatra-Anwendungen können direkt von der Kommandozeile aus gestartet werden:
1942
+
1943
+ ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-h HOST] [-s HANDLER]
1944
+
1945
+ Die Optionen sind:
1946
+
1947
+ -h # Hilfe
1948
+ -p # Port setzen (Standard ist 4567)
1949
+ -h # Host setzen (Standard ist 0.0.0.0)
1950
+ -e # Umgebung setzen (Standard ist development)
1951
+ -s # Rack-Server/Handler setzen (Standard ist thin)
1952
+ -x # Mutex-Lock einschalten (Standard ist off)
1953
+
1954
+ == Systemanforderungen
1955
+
1956
+ Die folgenden Versionen werden offiziell unterstützt:
1957
+
1958
+ [ Ruby 1.8.7 ]
1959
+ 1.8.7 wird vollständig unterstützt, aber solange nichts dagegen spricht,
1960
+ wird ein Update auf 1.9.2 oder ein Umstieg auf JRuby/Rubinius empfohlen.
1961
+ Unterstützung für 1.8.7 wird es mindestens bis Sinatra 2.0 und Ruby 2.0 geben,
1962
+ es sei denn, dass der unwahrscheinliche Fall eintritt und 1.8.8 rauskommt.
1963
+ Doch selbst dann ist es eher wahrscheinlich, dass 1.8.7 weiterhin unterstützt
1964
+ wird. <b>Ruby 1.8.6 wird nicht mehr unterstützt.</b> Soll Sinatra unter 1.8.6
1965
+ eingesetzt werden, muss Sinatra 1.2 verwendet werden, dass noch bis zum
1966
+ Release von Sinatra 1.4.0 fortgeführt wird.
1967
+
1968
+ [ Ruby 1.9.2 ]
1969
+ 1.9.2 wird voll unterstützt und empfohlen. Version 1.9.2p0 sollte nicht
1970
+ verwendet werden, da unter Sinatra immer wieder Segfaults auftreten.
1971
+ Unterstützung wird es mindestens bis zum Release von Ruby 1.9.4/2.0 geben und
1972
+ das letzte Sinatra Release für 1.9 wird so lange unterstützt, wie das Ruby
1973
+ Core-Team 1.9 pflegt.
1974
+
1975
+ [ Ruby 1.9.3 ]
1976
+ 1.9.3 wird vollständig unterstützt und empfohlen. Achtung, bei einem Wechsel
1977
+ zu 1.9.3 werden alle Sessions ungültig.
1978
+
1979
+ [ Rubinius ]
1980
+ Rubinius (rbx >= 1.2.4) wird offiziell unter Einbezug aller Templates
1981
+ unterstützt. Die kommende 2.0 Version wird ebenfalls unterstützt, samt 1.9
1982
+ Modus.
1983
+
1984
+ [ JRuby ]
1985
+ JRuby wird offiziell unterstützt (JRuby >= 1.6.7). Probleme mit Template-
1986
+ Bibliotheken Dritter sind nicht bekannt. Falls JRuby zum Einsatz kommt,
1987
+ sollte aber darauf geachtet werden, dass ein JRuby-Rack-Handler zum Einsatz
1988
+ kommt – die Thin und Mongrel Web-Server werden bisher nicht unterstütz. JRubys
1989
+ Unterstützung für C-Erweiterungen sind zur Zeit ebenfalls experimenteller
1990
+ Natur, betrifft im Moment aber nur die RDiscount, Redcarpet, RedCloth und
1991
+ Yajl Templates.
1992
+
1993
+
1994
+ Weiterhin werden wir die kommende Ruby-Versionen im Auge behalten.
1995
+
1996
+ Die nachfolgend aufgeführten Ruby-Implementierungen werden offiziell nicht von
1997
+ Sinatra unterstützt, funktionieren aber normalerweise:
1998
+
1999
+ * Ruby Enterprise Edition
2000
+ * Ältere Versionen von JRuby und Rubinius
2001
+ * MacRuby, Maglev, IronRuby
2002
+ * Ruby 1.9.0 und 1.9.1 (wird jedoch nicht empfohlen, s.o.)
2003
+
2004
+ Nicht offiziell unterstützt bedeutet, dass wenn Sachen nicht funktionieren,
2005
+ wir davon ausgehen, dass es nicht an Sinatra sondern an der jeweiligen
2006
+ Implentierung liegt.
2007
+
2008
+ Im Rahmen unserer CI (Kontinuierlichen Integration) wird bereits ruby-head
2009
+ (das kommende Ruby 2.0.0) und 1.9.4 mit eingebunden. Da noch alles im Fluss ist,
2010
+ kann zur Zeit für nichts garantiert werden. Es kann aber erwartet werden, dass
2011
+ Ruby 2.0.0p0 und 1.9.4p0 von Sinatra unterstützt werden wird.
2012
+
2013
+ Sinatra sollte auf jedem Betriebssystem laufen, dass den gewählten Ruby-
2014
+ Interpreter unterstützt.
2015
+
2016
+ Sinatra wird aktuell nicht unter Cardinal, SmallRuby, BleuRuby oder irgendeiner
2017
+ Version von Ruby vor 1.8.7 laufen.
2018
+
2019
+ == Der neuste Stand (The Bleeding Edge)
2020
+
2021
+ Um auf dem neusten Stand zu bleiben, kann der Master-Branch verwendet werden.
2022
+ Er sollte recht stabil sein. Ebenso gibt es von Zeit zu Zeit prerelease Gems,
2023
+ die so installiert werden:
2024
+
2025
+ gem install sinatra --pre
2026
+
2027
+ === Mit Bundler
2028
+
2029
+ Wenn die Applikation mit der neuesten Version von Sinatra und
2030
+ {Bundler}[http://gembundler.com/] genutzt werden soll, empfehlen wir den
2031
+ nachfolgenden Weg.
2032
+
2033
+ Soweit Bundler noch nicht installiert ist:
2034
+
2035
+ gem install bundler
2036
+
2037
+ Anschließend wird eine +Gemfile+-Datei im Projektverzeichnis mit folgendem
2038
+ Inhalt erstellt:
2039
+
2040
+ source :rubygems
2041
+ gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
2042
+
2043
+ # evtl. andere Abhängigkeiten
2044
+ gem 'haml' # z.B. wenn du Haml verwendest...
2045
+ gem 'activerecord', '~> 3.0' # ...oder ActiveRecord 3.x
2046
+
2047
+ Beachte: Hier sollten alle Abhängigkeiten eingetragen werden. Sinatras eigene,
2048
+ direkte Abhängigkeiten (Tilt und Rack) werden von Bundler automatisch aus dem
2049
+ Gemfile von Sinatra hinzugefügt.
2050
+
2051
+ Jetzt kannst du deine Applikation starten:
2052
+
2053
+ bundle exec ruby myapp.rb
2054
+
2055
+ === Eigenes Repository
2056
+ Um auf dem neuesten Stand von Sinatras Code zu sein, kann eine lokale Kopie
2057
+ angelegt werden. Gestartet wird in der Anwendung mit dem <tt>sinatra/lib</tt>-
2058
+ Ordner im <tt>LOAD_PATH</tt>:
2059
+
2060
+ cd myapp
2061
+ git clone git://github.com/sinatra/sinatra.git
2062
+ ruby -Isinatra/lib myapp.rb
2063
+
2064
+ Alternativ kann der <tt>sinatra/lib</tt>-Ordner zum <tt>LOAD_PATH</tt> in
2065
+ der Anwendung hinzugefügt werden:
2066
+
2067
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
2068
+ require 'rubygems'
2069
+ require 'sinatra'
2070
+
2071
+ get '/ueber' do
2072
+ "Ich laufe auf Version " + Sinatra::VERSION
2073
+ end
2074
+
2075
+ Um Sinatra-Code von Zeit zu Zeit zu aktualisieren:
2076
+
2077
+ cd myproject/sinatra
2078
+ git pull
2079
+
2080
+ === Gem erstellen
2081
+
2082
+ Aus der eigenen lokalen Kopie kann nun auch ein globales Gem gebaut werden:
2083
+
2084
+ git clone git://github.com/sinatra/sinatra.git
2085
+ cd sinatra
2086
+ rake sinatra.gemspec
2087
+ rake install
2088
+
2089
+ Falls Gems als Root installiert werden sollen, sollte die letzte Zeile
2090
+ folgendermaßen lauten:
2091
+
2092
+ sudo rake install
2093
+
2094
+ == Versions-Verfahren
2095
+
2096
+ Sinatra folgt dem sogenannten {Semantic Versioning}[http://semver.org/], d.h.
2097
+ SemVer und SemVerTag.
2098
+
2099
+ == Mehr
2100
+
2101
+ * {Projekt-Website}[http://sinatra.github.com/] - Ergänzende Dokumentation,
2102
+ News und Links zu anderen Ressourcen.
2103
+ * {Mitmachen}[http://sinatra.github.com/contributing.html] - Einen
2104
+ Fehler gefunden? Brauchst du Hilfe? Hast du einen Patch?
2105
+ * {Issue-Tracker}[http://github.com/sinatra/sinatra/issues]
2106
+ * {Twitter}[http://twitter.com/sinatra]
2107
+ * {Mailing-Liste}[http://groups.google.com/group/sinatrarb]
2108
+ * {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] auf http://freenode.net
2109
+ * {Sinatra Book}[http://sinatra-book.gittr.com] Kochbuch Tutorial
2110
+ * {Sinatra Recipes}[http://recipes.sinatrarb.com/] Sinatra-Rezepte aus
2111
+ der Community
2112
+ * API Dokumentation für die {aktuelle Version}[http://rubydoc.info/gems/sinatra]
2113
+ oder für {HEAD}[http://rubydoc.info/github/sinatra/sinatra] auf
2114
+ http://rubydoc.info
2115
+ * {CI Server}[http://travis-ci.org/sinatra/sinatra]
2116
+