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,2033 @@
1
+ = Sinatra
2
+ <i>Внимание: Этот документ является переводом английской версии и может быть устаревшим</i>
3
+
4
+ Sinatra — это предметно-ориентированный каркас ({DSL}[http://ru.wikipedia.org/wiki/Предметно-ориентированный_язык_программирования]) для быстрого создания функциональных
5
+ веб-приложений на Ruby с минимумом усилий:
6
+
7
+ # myapp.rb
8
+ require 'sinatra'
9
+
10
+ get '/' do
11
+ 'Hello world!'
12
+ end
13
+
14
+ Установите gem:
15
+
16
+ gem install sinatra
17
+
18
+ и запустите приложение с помощью:
19
+
20
+ ruby -rubygems myapp.rb
21
+
22
+ Оцените результат: http://localhost:4567
23
+
24
+ Рекомендуется также установить Thin, сделать это можно командой: <tt>gem install thin</tt>.
25
+ Thin — это более производительный и функциональный сервер для разработки приложений на Sinatra.
26
+
27
+ == Маршруты
28
+
29
+ В Sinatra маршрут — это пара: <HTTP метод> и <шаблон URL>.
30
+ Каждый маршрут связан с блоком кода:
31
+
32
+ get '/' do
33
+ .. что-то показать ..
34
+ end
35
+
36
+ post '/' do
37
+ .. что-то создать ..
38
+ end
39
+
40
+ put '/' do
41
+ .. что-то заменить ..
42
+ end
43
+
44
+ patch '/' do
45
+ .. что-то изменить ..
46
+ end
47
+
48
+ delete '/' do
49
+ .. что-то удалить ..
50
+ end
51
+
52
+ options '/' do
53
+ .. что-то ответить ..
54
+ end
55
+
56
+ Маршруты сверяются с запросом в порядке очередности их записи в файле приложения.
57
+ Первый же совпавший с запросом маршрут и будет вызван.
58
+
59
+ Шаблоны маршрутов могут включать в себя именованные параметры доступные
60
+ в xэше <tt>params</tt>:
61
+
62
+ get '/hello/:name' do
63
+ # соответствует "GET /hello/foo" и "GET /hello/bar",
64
+ # где params[:name] 'foo' или 'bar'
65
+ "Hello #{params[:name]}!"
66
+ end
67
+
68
+ Также можно использовать именованные параметры в качестве переменных
69
+ блока:
70
+
71
+ get '/hello/:name' do |n|
72
+ "Hello #{n}!"
73
+ end
74
+
75
+ Шаблоны маршрутов также могут включать в себя splat (или '*'
76
+ маску, обозначающую любой символ) параметры доступные в массиве
77
+ <tt>params[:splat]</tt>:
78
+
79
+ get '/say/*/to/*' do
80
+ # соответствует /say/hello/to/world
81
+ params[:splat] # => ["hello", "world"]
82
+ end
83
+
84
+ get '/download/*.*' do
85
+ # соответствует /download/path/to/file.xml
86
+ params[:splat] # => ["path/to/file", "xml"]
87
+ end
88
+
89
+ Или с параметрами блока:
90
+
91
+ get '/download/*.*' do |path, ext|
92
+ [path, ext] # => ["path/to/file", "xml"]
93
+ end
94
+
95
+ Регулярные выражения в качестве шаблонов маршрутов:
96
+
97
+ get %r{/hello/([\w]+)} do
98
+ "Hello, #{params[:captures].first}!"
99
+ end
100
+
101
+ Или с параметром блока:
102
+
103
+ get %r{/hello/([\w]+)} do |c|
104
+ "Hello, #{c}!"
105
+ end
106
+
107
+ Шаблоны маршрутов могут иметь необязательные параметры:
108
+
109
+ get '/posts.?:format?' do
110
+ # соответствует "GET /posts", "GET /posts.json", "GET /posts.xml" и т.д.
111
+ end
112
+
113
+ Кстати, если вы не отключите защиту от обратного пути в директориях
114
+ (path traversal, см. ниже), путь запроса может быть изменен до начала
115
+ поиска подходящего маршрута.
116
+
117
+ === Условия
118
+
119
+ Маршруты могут включать различные условия совпадений, например,
120
+ клиентское приложение (user agent):
121
+
122
+ get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
123
+ "You're using Songbird version #{params[:agent][0]}"
124
+ end
125
+
126
+ get '/foo' do
127
+ # соответствует не-songbird браузерам
128
+ end
129
+
130
+ Другими доступными условиями являются +host_name+ и +provides+:
131
+
132
+ get '/', :host_name => /^admin\./ do
133
+ "Admin Area, Access denied!"
134
+ end
135
+
136
+ get '/', :provides => 'html' do
137
+ haml :index
138
+ end
139
+
140
+ get '/', :provides => ['rss', 'atom', 'xml'] do
141
+ builder :feed
142
+ end
143
+
144
+ Вы можете задать собственные условия:
145
+
146
+ set(:probability) { |value| condition { rand <= value } }
147
+
148
+ get '/win_a_car', :probability => 0.1 do
149
+ "You won!"
150
+ end
151
+
152
+ get '/win_a_car' do
153
+ "Sorry, you lost."
154
+ end
155
+
156
+ Для условия, которое принимает несколько параметров, используйте
157
+ звездочку:
158
+
159
+ set(:auth) do |*roles| # <- обратите внимание на звездочку
160
+ condition do
161
+ unless logged_in? && roles.any? {|role| current_user.in_role? role }
162
+ redirect "/login/", 303
163
+ end
164
+ end
165
+ end
166
+
167
+ get "/my/account/", :auth => [:user, :admin] do
168
+ "Your Account Details"
169
+ end
170
+
171
+ get "/only/admin/", :auth => :admin do
172
+ "Only admins are allowed here!"
173
+ end
174
+
175
+ === Возвращаемые значения
176
+
177
+ Возвращаемое значение блока маршрута ограничивается телом ответа, которое будет передано HTTP клиенту,
178
+ или следующей "прослойкой" (middleware) в Rack стеке. Чаще всего это строка, как в примерах выше.
179
+ Но и другие значения также приемлемы.
180
+
181
+ Вы можете вернуть любой объект, который будет либо корректным Rack ответом, объектом Rack body,
182
+ либо кодом состояния HTTP:
183
+
184
+ * массив с тремя переменными: <tt>[код (Fixnum), заголовки (Hash), тело ответа (должно отвечать на #each)]</tt>;
185
+ * массив с двумя переменными: <tt>[код (Fixnum), тело ответа (должно отвечать на #each)]</tt>;
186
+ * объект, отвечающий на <tt>#each</tt>, который передает только строковые типы данных в этот блок;
187
+ * Fixnum, представляющий код состояния HTTP.
188
+
189
+ Таким образом, легко можно реализовать, например, поточный пример:
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
+ Вы также можете использовать метод +stream+ (описываемый ниже), чтобы
200
+ уменьшить количество дублируемого кода и держать логику стриминга прямо в маршруте.
201
+
202
+ === Собственные детекторы совпадений для маршрутов
203
+
204
+ Как показано выше, Sinatra поставляется со встроенной поддержкой строк и
205
+ регулярных выражений в качестве шаблонов URL. Но и это еще не все. Вы можете
206
+ легко определить свои собственные детекторы совпадений (matchers) для маршрутов:
207
+
208
+ class AllButPattern
209
+ Match = Struct.new(:captures)
210
+
211
+ def initialize(except)
212
+ @except = except
213
+ @captures = Match.new([])
214
+ end
215
+
216
+ def match(str)
217
+ @captures unless @except === str
218
+ end
219
+ end
220
+
221
+ def all_but(pattern)
222
+ AllButPattern.new(pattern)
223
+ end
224
+
225
+ get all_but("/index") do
226
+ # ...
227
+ end
228
+
229
+ Заметьте, что предыдущий пример возможно чересчур усложнен, потому что
230
+ он может быть реализован так:
231
+
232
+ get // do
233
+ pass if request.path_info == "/index"
234
+ # ...
235
+ end
236
+
237
+ Или с использованием негативного просмотра вперед:
238
+
239
+ get %r{^(?!/index$)} do
240
+ # ...
241
+ end
242
+
243
+ == Статические файлы
244
+
245
+ Статические файлы отдаются из <tt>./public</tt> директории. Вы можете
246
+ указать другое место, используя опцию <tt>:public_folder</tt>:
247
+
248
+ set :public_folder, File.dirname(__FILE__) + '/static'
249
+
250
+ Учтите, что имя директории со статическими файлами не включено в URL. Например, файл
251
+ <tt>./public/css/style.css</tt> будет доступен как
252
+ <tt>http://example.com/css/style.css</tt>.
253
+
254
+ Используйте опцию <tt>:static_cache_control</tt> (см. ниже), чтобы
255
+ добавить заголовок <tt>Cache-Control</tt>.
256
+
257
+ == Представления / Шаблоны
258
+
259
+ Каждый шаблонизатор представлен своим собственным методом. Эти методы
260
+ попросту возвращают строку:
261
+
262
+ get '/' do
263
+ erb :index
264
+ end
265
+
266
+ Отобразит <tt>views/index.erb</tt>.
267
+
268
+ Вместо имени шаблона вы так же можете передавать непосредственно само
269
+ содержимое шаблона:
270
+
271
+ get '/' do
272
+ code = "<%= Time.now %>"
273
+ erb code
274
+ end
275
+
276
+ Эти методы принимают второй аргумент, хеш с опциями:
277
+
278
+ get '/' do
279
+ erb :index, :layout => :post
280
+ end
281
+
282
+ Отобразит <tt>views/index.erb</tt>, вложенным в
283
+ <tt>views/post.erb</tt> (по умолчанию: <tt>views/layout.erb</tt>, если существует).
284
+
285
+ Любые опции, не понимаемые Sinatra, будут переданы в шаблонизатор:
286
+
287
+ get '/' do
288
+ haml :index, :format => :html5
289
+ end
290
+
291
+ Вы также можете задавать опции для шаблонизаторов в общем:
292
+
293
+ set :haml, :format => :html5
294
+
295
+ get '/' do
296
+ haml :index
297
+ end
298
+
299
+ Опции, переданные в метод, переопределяют опции, заданные с помощью
300
+ +set+.
301
+
302
+ Доступные опции:
303
+
304
+ [locals]
305
+ Список локальных переменных, передаваемых в документ.
306
+ Например: <tt>erb "<%= foo %>", :locals => {:foo => "bar"}</tt>
307
+
308
+ [default_encoding]
309
+ Кодировка, которую следует использовать, если не удалось определить
310
+ оригинальную. По умолчанию: <tt>settings.default_encoding</tt>.
311
+
312
+ [views]
313
+ Директория с шаблонами. По умолчанию: <tt>settings.views</tt>.
314
+
315
+ [layout]
316
+ Использовать или нет лэйаут (+true+ или +false+). Если же значение Symbol,
317
+ то указывает, какой шаблон использовать в качестве лэйаута. Например:
318
+ <tt>erb :index, :layout => !request.xhr?</tt>
319
+
320
+ [content_type]
321
+ Content-Type отображенного шаблона. По умолчанию: задается шаблонизатором.
322
+
323
+ [scope]
324
+ Область видимости, в которой рендерятся шаблоны. По умолчанию: экземпляр
325
+ приложения. Если вы измените эту опцию, то переменные экземпляра и
326
+ методы-помощники станут недоступными в ваших шаблонах.
327
+
328
+ [layout_engine]
329
+ Шаблонизатор, который следует использовать для отображения лэйаута. Полезная
330
+ опция для шаблонизаторов, в которых нет никакой поддержки лэйаутов. По
331
+ умолчанию: тот же шаблонизатор, что используется и для самого шаблона.
332
+ Пример: <tt>set :rdoc, :layout_engine => :erb</tt>
333
+
334
+ По умолчанию считается, что шаблоны находятся в директории <tt>./views</tt>.
335
+ Чтобы использовать другую директорию с шаблонами:
336
+
337
+ set :views, settings.root + '/templates'
338
+
339
+ Важное замечание: вы всегда должны ссылаться на шаблоны с помощью символов
340
+ (Symbol), даже когда они в поддиректории (в этом случае используйте
341
+ <tt>:'subdir/template'</tt>). Вы должны использовать символы, потому что
342
+ иначе шаблонизаторы попросту отображают любые строки, переданные им.
343
+
344
+ === Доступные шаблонизаторы
345
+
346
+ Некоторые языки шаблонов имеют несколько реализаций. Чтобы указать, какую
347
+ реализацию использовать, вам следует просто подключить нужную
348
+ библиотеку:
349
+
350
+ require 'rdiscount' # или require 'bluecloth'
351
+ get('/') { markdown :index }
352
+
353
+ === Haml шаблоны
354
+
355
+ Зависимости:: {haml}[http://haml.info/]
356
+ Расширения файлов:: <tt>.haml</tt>
357
+ Пример:: <tt>haml :index, :format => :html5</tt>
358
+
359
+ === Erb шаблоны
360
+
361
+ Зависимости:: {erubis}[http://www.kuwata-lab.com/erubis/] или erb (включен в Ruby)
362
+ Расширения файлов:: <tt>.erb</tt>, <tt>.rhtml</tt> или <tt>.erubis</tt> (только Erubis)
363
+ Пример:: <tt>erb :index</tt>
364
+
365
+ === Builder шаблоны
366
+
367
+ Зависимости:: {builder}[http://builder.rubyforge.org/]
368
+ Расширения файлов:: <tt>.builder</tt>
369
+ Пример:: <tt>builder { |xml| xml.em "hi" }</tt>
370
+
371
+ Блок также используется и для встроенных шаблонов (см. пример).
372
+
373
+ === Nokogiri шаблоны
374
+
375
+ Зависимости:: {nokogiri}[http://nokogiri.org/]
376
+ Расширения файлов:: <tt>.nokogiri</tt>
377
+ Пример:: <tt>nokogiri { |xml| xml.em "hi" }</tt>
378
+
379
+ Блок также используется и для встроенных шаблонов (см. пример).
380
+
381
+ === Sass шаблоны
382
+
383
+ Зависимости:: {sass}[http://sass-lang.com/]
384
+ Расширения файлов:: <tt>.sass</tt>
385
+ Пример:: <tt>sass :stylesheet, :style => :expanded</tt>
386
+
387
+ === SCSS шаблоны
388
+
389
+ Зависимости:: {sass}[http://sass-lang.com/]
390
+ Расширения файлов:: <tt>.scss</tt>
391
+ Пример:: <tt>scss :stylesheet, :style => :expanded</tt>
392
+
393
+ === Less шаблоны
394
+
395
+ Зависимости:: {less}[http://www.lesscss.org/]
396
+ Расширения файлов:: <tt>.less</tt>
397
+ Пример:: <tt>less :stylesheet</tt>
398
+
399
+ === Liquid шаблоны
400
+
401
+ Зависимости:: {liquid}[http://www.liquidmarkup.org/]
402
+ Расширения файлов:: <tt>.liquid</tt>
403
+ Пример:: <tt>liquid :index, :locals => { :key => 'value' }</tt>
404
+
405
+ Так как в Liquid шаблонах невозможно вызывать методы из Ruby (кроме yield), то
406
+ вы почти всегда будете передавать в шаблон локальные переменные.
407
+
408
+ === Markdown шаблоны
409
+
410
+ Зависимости:: {rdiscount}[https://github.com/rtomayko/rdiscount], {redcarpet}[https://github.com/vmg/redcarpet], {bluecloth}[http://deveiate.org/projects/BlueCloth], {kramdown}[http://kramdown.rubyforge.org/] или {maruku}[http://maruku.rubyforge.org/]
411
+ Расширения файлов:: <tt>.markdown</tt>, <tt>.mkd</tt> and <tt>.md</tt>
412
+ Пример:: <tt>markdown :index, :layout_engine => :erb</tt>
413
+
414
+ В Markdown невозможно вызывать методы или передавать локальные переменные.
415
+ Следовательно, вам, скорее всего, придется использовать этот шаблон совместно
416
+ с другим шаблонизатором:
417
+
418
+ erb :overview, :locals => { :text => markdown(:introduction) }
419
+
420
+ Заметьте, что вы можете вызывать метод +markdown+ из других шаблонов:
421
+
422
+ %h1 Hello From Haml!
423
+ %p= markdown(:greetings)
424
+
425
+ Вы не можете вызывать Ruby из Markdown, соответственно, вы не можете
426
+ использовать лэйауты на Markdown. Тем не менее, есть возможность использовать
427
+ один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью
428
+ опции <tt>:layout_engine</tt>.
429
+
430
+ === Textile шаблоны
431
+
432
+ Зависимости:: {RedCloth}[http://redcloth.org/]
433
+ Расширения файлов:: <tt>.textile</tt>
434
+ Пример:: <tt>textile :index, :layout_engine => :erb</tt>
435
+
436
+ В Textile невозможно вызывать методы или передавать локальные переменные.
437
+ Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с
438
+ другим шаблонизатором:
439
+
440
+ erb :overview, :locals => { :text => textile(:introduction) }
441
+
442
+ Заметьте, что вы можете вызывать метод +textile+ из других шаблонов:
443
+
444
+ %h1 Hello From Haml!
445
+ %p= textile(:greetings)
446
+
447
+ Вы не можете вызывать Ruby из Textile, соответственно, вы не можете
448
+ использовать лэйауты на Textile. Тем не менее, есть возможность использовать
449
+ один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью
450
+ опции <tt>:layout_engine</tt>.
451
+
452
+ === RDoc шаблоны
453
+
454
+ Зависимости:: {rdoc}[http://rdoc.rubyforge.org/]
455
+ Расширения файлов:: <tt>.rdoc</tt>
456
+ Пример:: <tt>rdoc :README, :layout_engine => :erb</tt>
457
+
458
+ В RDoc невозможно вызывать методы или передавать локальные переменные.
459
+ Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с
460
+ другим шаблонизатором:
461
+
462
+ erb :overview, :locals => { :text => rdoc(:introduction) }
463
+
464
+ Заметьте, что вы можете вызывать метод +rdoc+ из других шаблонов:
465
+
466
+ %h1 Hello From Haml!
467
+ %p= rdoc(:greetings)
468
+
469
+ Вы не можете вызывать Ruby из RDoc, соответственно, вы не можете
470
+ использовать лэйауты на RDoc. Тем не менее, есть возможность использовать
471
+ один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью
472
+ опции <tt>:layout_engine</tt>.
473
+
474
+ === Radius шаблоны
475
+
476
+ Зависимости:: {radius}[http://radius.rubyforge.org/]
477
+ Расширения файлов:: <tt>.radius</tt>
478
+ Пример:: <tt>radius :index, :locals => { :key => 'value' }</tt>
479
+
480
+ Так как в Radius шаблонах невозможно вызывать методы из Ruby напрямую, то
481
+ вы почти всегда будете передавать в шаблон локальные переменные.
482
+
483
+ === Markaby шаблоны
484
+
485
+ Зависимости:: {markaby}[http://markaby.github.com/]
486
+ Расширения файлов:: <tt>.mab</tt>
487
+ Пример:: <tt>markaby { h1 "Welcome!" }</tt>
488
+
489
+ Блок также используется и для встроенных шаблонов (см. пример).
490
+
491
+ === RABL шаблоны
492
+
493
+ Зависимости:: {rabl}[https://github.com/nesquena/rabl]
494
+ Расширения файлов:: <tt>.rabl</tt>
495
+ Пример:: <tt>rabl :index</tt>
496
+
497
+ === Slim шаблоны
498
+
499
+ Зависимости:: {slim}[http://slim-lang.com/]
500
+ Расширения файлов:: <tt>.slim</tt>
501
+ Пример:: <tt>slim :index</tt>
502
+
503
+ === Creole шаблоны
504
+
505
+ Зависимости:: {creole}[https://github.com/minad/creole]
506
+ Расширения файлов:: <tt>.creole</tt>
507
+ Пример:: <tt>creole :wiki, :layout_engine => :erb</tt>
508
+
509
+ В Creole невозможно вызывать методы или передавать локальные переменные.
510
+ Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с
511
+ другим шаблонизатором:
512
+
513
+ erb :overview, :locals => { :text => creole(:introduction) }
514
+
515
+ Заметьте, что вы можете вызывать метод +creole+ из других шаблонов:
516
+
517
+ %h1 Hello From Haml!
518
+ %p= creole(:greetings)
519
+
520
+ Вы не можете вызывать Ruby из Creole, соответственно, вы не можете
521
+ использовать лэйауты на Creole. Тем не менее, есть возможность использовать
522
+ один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью
523
+ опции <tt>:layout_engine</tt>.
524
+
525
+ === CoffeeScript шаблоны
526
+
527
+ Зависимости:: {coffee-script}[https://github.com/josh/ruby-coffee-script] и {способ запускать javascript}[https://github.com/sstephenson/execjs/blob/master/README.md#readme]
528
+ Расширения файлов:: <tt>.coffee</tt>
529
+ Пример:: <tt>coffee :index</tt>
530
+
531
+ === Yajl шаблоны
532
+
533
+ Зависимости:: {yajl-ruby}[https://github.com/brianmario/yajl-ruby]
534
+ Расширения файлов:: <tt>.yajl</tt>
535
+ Пример:: <tt>yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource' </tt>
536
+
537
+ Содержимое шаблона интерпретируется как код на Ruby, а результирующая переменная json затем
538
+ конвертируется с помощью #to_json.
539
+
540
+ json = { :foo => 'bar' }
541
+ json[:baz] = key
542
+
543
+ Опции <tt>:callback</tt> и <tt>:variable</tt> используются для "декорирования" итогового объекта.
544
+
545
+ var resource = {"foo":"bar","baz":"qux"}; present(resource);
546
+
547
+ === WLang шаблоны
548
+
549
+ Зависимости:: {wlang}[https://github.com/blambeau/wlang/]
550
+ Расширения файлов:: <tt>.wlang</tt>
551
+ Пример:: <tt>wlang :index, :locals => { :key => 'value' }</tt>
552
+
553
+ Так как в WLang шаблонах невозможно вызывать методы из Ruby напрямую (за исключением +yield+), то
554
+ вы почти всегда будете передавать в шаблон локальные переменные.
555
+
556
+ === Встроенные шаблоны
557
+
558
+ get '/' do
559
+ haml '%div.title Hello World'
560
+ end
561
+
562
+ Отобразит встроенный шаблон, переданный строкой.
563
+
564
+ === Доступ к переменным в шаблонах
565
+
566
+ Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов. Переменные экземпляра,
567
+ установленные в процессе обработки маршрутов, будут доступны напрямую в шаблонах:
568
+
569
+ get '/:id' do
570
+ @foo = Foo.find(params[:id])
571
+ haml '%h1= @foo.name'
572
+ end
573
+
574
+ Либо установите их через хеш локальных переменных:
575
+
576
+ get '/:id' do
577
+ foo = Foo.find(params[:id])
578
+ haml '%h1= bar.name', :locals => { :bar => foo }
579
+ end
580
+
581
+ Это обычный подход, когда шаблоны рендерятся как части других шаблонов.
582
+
583
+ === Вложенные шаблоны
584
+
585
+ Шаблоны также могут быть определены в конце исходного файла:
586
+
587
+ require 'sinatra'
588
+
589
+ get '/' do
590
+ haml :index
591
+ end
592
+
593
+ __END__
594
+
595
+ @@ layout
596
+ %html
597
+ = yield
598
+
599
+ @@ index
600
+ %div.title Hello world.
601
+
602
+ Заметьте: вложенные шаблоны, определенные в исходном файле, который подключила Sinatra, будут
603
+ загружены автоматически. Вызовите <tt>enable :inline_templates</tt> напрямую, если
604
+ используете вложенные шаблоны в других файлах.
605
+
606
+ === Именованные шаблоны
607
+
608
+ Шаблоны также могут быть определены при помощи <tt>template</tt> метода:
609
+
610
+ template :layout do
611
+ "%html\n =yield\n"
612
+ end
613
+
614
+ template :index do
615
+ '%div.title Hello World!'
616
+ end
617
+
618
+ get '/' do
619
+ haml :index
620
+ end
621
+
622
+ Если шаблон с именем "layout" существует, то он будет использоваться каждый раз
623
+ при рендеринге. Вы можете отключать лэйаут в каждом конкретном случае с помощью
624
+ <tt>:layout => false</tt> или отключить его для всего приложения:
625
+ <tt>set :haml, :layout => false</tt>:
626
+
627
+ get '/' do
628
+ haml :index, :layout => !request.xhr?
629
+ end
630
+
631
+ === Привязка файловых расширений
632
+
633
+ Чтобы связать расширение файла с движком рендеринга, используйте
634
+ <tt>Tilt.register</tt>. Например, если вы хотите использовать расширение +tt+
635
+ для шаблонов Textile:
636
+
637
+ Tilt.register :tt, Tilt[:textile]
638
+
639
+ === Добавление собственного движка рендеринга
640
+
641
+ Сначала зарегистрируйте свой движок в Tilt, а затем создайте метод,
642
+ отвечающий за рендеринг:
643
+
644
+ Tilt.register :myat, MyAwesomeTemplateEngine
645
+
646
+ helpers do
647
+ def myat(*args) render(:myat, *args) end
648
+ end
649
+
650
+ get '/' do
651
+ myat :index
652
+ end
653
+
654
+ Отобразит <tt>./views/index.myat</tt>. Чтобы узнать больше о Tilt,
655
+ смотрите https://github.com/rtomayko/tilt
656
+
657
+ == Фильтры
658
+
659
+ +before+-фильтры выполняются перед каждым запросом в том же контексте, что и маршруты. Фильтры могут изменять
660
+ как запрос, так и ответ на него. Переменные экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах:
661
+
662
+ before do
663
+ @note = 'Hi!'
664
+ request.path_info = '/foo/bar/baz'
665
+ end
666
+
667
+ get '/foo/*' do
668
+ @note #=> 'Hi!'
669
+ params[:splat] #=> 'bar/baz'
670
+ end
671
+
672
+ +after+-фильтры выполняются после каждого запроса в том же контексте, что и пути. Фильтры могут изменять
673
+ как запрос, так и ответ на него. Переменные экземпляра, установленные в +before+-фильтрах и маршрутах,
674
+ будут доступны в +after+-фильтрах:
675
+
676
+ after do
677
+ puts response.status
678
+ end
679
+
680
+ Заметьте: если вы используете метод +body+, а не просто возвращаете строку из
681
+ маршрута, то тело ответа не будет доступно в +after+-фильтрах, так как оно будет сгенерировано позднее.
682
+
683
+ Фильтры могут использовать шаблоны URL и будут интерпретированы только если путь запроса совпадет с этим шаблоном:
684
+
685
+ before '/protected/*' do
686
+ authenticate!
687
+ end
688
+
689
+ after '/create/:slug' do |slug|
690
+ session[:last_slug] = slug
691
+ end
692
+
693
+ Как и маршруты, фильтры могут использовать условия:
694
+
695
+ before :agent => /Songbird/ do
696
+ # ...
697
+ end
698
+
699
+ after '/blog/*', :host_name => 'example.com' do
700
+ # ...
701
+ end
702
+
703
+ == Методы-помощники
704
+
705
+ Используйте метод <tt>helpers</tt>, чтобы определить методы-помощники, которые
706
+ в дальнейшем можно будет использовать в обработчиках маршрутов и шаблонах:
707
+
708
+ helpers do
709
+ def bar(name)
710
+ "#{name}bar"
711
+ end
712
+ end
713
+
714
+ get '/:name' do
715
+ bar(params[:name])
716
+ end
717
+
718
+ Также методы-помощники могут быть заданы в отдельных модулях:
719
+
720
+ module FooUtils
721
+ def foo(name) "#{name}foo" end
722
+ end
723
+
724
+ module BarUtils
725
+ def bar(name) "#{name}bar" end
726
+ end
727
+
728
+ helpers FooUtils, BarUtils
729
+
730
+ Эффект равносилен включению модулей в класс приложения.
731
+
732
+ === Использование сессий
733
+
734
+ Сессия используется, чтобы сохранять состояние между запросами. Если эта опция
735
+ включена, то у вас будет один хеш сессии на одну пользовательскую сессию:
736
+
737
+ enable :sessions
738
+
739
+ get '/' do
740
+ "value = " << session[:value].inspect
741
+ end
742
+
743
+ get '/:value' do
744
+ session[:value] = params[:value]
745
+ end
746
+
747
+ Заметьте, что при использовании <tt>enable :sessions</tt> все данные
748
+ сохраняются в куках (cookies). Это может быть не совсем то, что вы хотите
749
+ (например, сохранение больших объемов данных увеличит ваш трафик). В таком случае
750
+ вы можете использовать альтернативную Rack "прослойку" (middleware), реализующую
751
+ механизм сессий. Для этого *не надо* вызывать <tt>enable :sessions</tt>,
752
+ вместо этого следует подключить ее так же, как и любую другую "прослойку":
753
+
754
+ use Rack::Session::Pool, :expire_after => 2592000
755
+
756
+ get '/' do
757
+ "value = " << session[:value].inspect
758
+ end
759
+
760
+ get '/:value' do
761
+ session[:value] = params[:value]
762
+ end
763
+
764
+ Для повышения безопасности данные сессии в куках подписываются секретным
765
+ ключом. Секретный ключ генерируется Sinatra. Тем не менее, так как этот
766
+ ключ будет меняться с каждым запуском приложения, вы, возможно, захотите
767
+ установить ключ вручную, чтобы у всех экземпляров вашего приложения
768
+ был один и тот же ключ:
769
+
770
+ set :session_secret, 'super secret'
771
+
772
+ Если вы хотите больше настроек для сессий, вы можете задать их, передав хеш опций в параметр +sessions+:
773
+
774
+ set :sessions, :domain => 'foo.com'
775
+
776
+ === Прерывание
777
+
778
+ Чтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута, используйте:
779
+
780
+ halt
781
+
782
+ Можно также указать статус при прерывании:
783
+
784
+ halt 410
785
+
786
+ Тело:
787
+
788
+ halt 'this will be the body'
789
+
790
+ И то, и другое:
791
+
792
+ halt 401, 'go away!'
793
+
794
+ Можно указать заголовки:
795
+
796
+ halt 402, {'Content-Type' => 'text/plain'}, 'revenge'
797
+
798
+ И, конечно, можно использовать шаблоны с +halt+:
799
+
800
+ halt erb(:error)
801
+
802
+ === Передача
803
+
804
+ Маршрут может передать обработку запроса следующему совпадающему маршруту, используя <tt>pass</tt>:
805
+
806
+ get '/guess/:who' do
807
+ pass unless params[:who] == 'Frank'
808
+ 'You got me!'
809
+ end
810
+
811
+ get '/guess/*' do
812
+ 'You missed!'
813
+ end
814
+
815
+ Блок маршрута сразу же прерывается, и контроль переходит к следующему совпадающему маршруту.
816
+ Если соответствующий маршрут не найден, то ответом на запрос будет 404.
817
+
818
+ === Вызов другого маршрута
819
+
820
+ Иногда +pass+ не подходит, например, если вы хотите получить результат
821
+ вызова другого обработчика маршрута. В таком случае просто используйте +call+:
822
+
823
+ get '/foo' do
824
+ status, headers, body = call env.merge("PATH_INFO" => '/bar')
825
+ [status, headers, body.map(&:upcase)]
826
+ end
827
+
828
+ get '/bar' do
829
+ "bar"
830
+ end
831
+
832
+ Заметьте, что в предыдущем примере можно облегчить тестирование и повысить
833
+ производительность, перенеся <tt>"bar"</tt> в метод-помощник, используемый
834
+ и в <tt>/foo</tt>, и в <tt>/bar</tt>.
835
+
836
+ Если вы хотите, чтобы запрос был отправлен в тот же экземпляр приложения, а не
837
+ в его копию, используйте <tt>call!</tt> вместо <tt>call</tt>.
838
+
839
+ Если хотите узнать больше о <tt>call</tt>, смотрите спецификацию Rack.
840
+
841
+ === Задание тела, кода и заголовков ответа
842
+
843
+ Хорошим тоном является установка кода состояния HTTP и тела ответа в возвращаемом
844
+ значении обработчика маршрута. Тем не менее, в некоторых ситуациях вам, возможно,
845
+ понадобится задать тело ответа в произвольной точке потока исполнения. Вы можете
846
+ сделать это с помощью метода-помощника +body+. Если вы задействуете метод +body+,
847
+ то вы можете использовать его и в дальнейшем, чтобы получить доступ к телу ответа.
848
+
849
+ get '/foo' do
850
+ body "bar"
851
+ end
852
+
853
+ after do
854
+ puts body
855
+ end
856
+
857
+ Также можно передать блок в метод +body+, который затем будет вызван
858
+ обработчиком Rack (такой подход может быть использован для реализации поточного
859
+ ответа, см. "Возвращаемые значения").
860
+
861
+ Аналогично вы можете установить код ответа и его заголовки:
862
+
863
+ get '/foo' do
864
+ status 418
865
+ headers \
866
+ "Allow" => "BREW, POST, GET, PROPFIND, WHEN",
867
+ "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
868
+ body "I'm a tea pot!"
869
+ end
870
+
871
+ Как и +body+, методы +headers+ и +status+, вызванные без аргументов, возвращают
872
+ свои текущие значения.
873
+
874
+ === Стриминг ответов
875
+
876
+ Иногда требуется начать отправлять данные клиенту прямо в процессе генерирования
877
+ частей этих данных. В особых случаях требуется постоянно отправлять данные до тех
878
+ пор, пока клиент не закроет соединение. Вы можете использовать метод +stream+
879
+ вместо написания собственных "оберток".
880
+
881
+ get '/' do
882
+ stream do |out|
883
+ out << "It's gonna be legen -\n"
884
+ sleep 0.5
885
+ out << " (wait for it) \n"
886
+ sleep 1
887
+ out << "- dary!\n"
888
+ end
889
+ end
890
+
891
+ Что позволяет вам реализовать стриминговые API,
892
+ {Server Sent Events}[http://dev.w3.org/html5/eventsource/],
893
+ и может служить основой для {WebSockets}[http://en.wikipedia.org/wiki/WebSocket].
894
+ Также такой подход можно использовать для увеличения производительности в случае,
895
+ когда какая-то часть контента зависит от медленного ресурса.
896
+
897
+ Заметьте, что возможности стриминга, особенно количество одновременно обслуживаемых
898
+ запросов, очень сильно зависят от используемого веб-сервера. Некоторые сервера,
899
+ например, WEBRick, могут и вовсе не поддерживать стриминг. Если сервер не
900
+ поддерживает стриминг, то все данные будут отправлены за один раз сразу после того,
901
+ как блок, переданный в +stream+, завершится. Стриминг вообще не работает при
902
+ использовании Shotgun.
903
+
904
+ Если метод используется с параметром +keep_open+, то он не будет вызывать +close+
905
+ у объекта потока, что позволит вам закрыть его позже в любом другом месте. Это
906
+ работает только с событийными серверами, например, с Thin и Rainbows.
907
+ Другие же сервера все равно будут закрывать поток:
908
+
909
+ set :server, :thin
910
+ connections = []
911
+
912
+ get '/' do
913
+ # держать все потоки открытыми
914
+ stream(:keep_open) { |out| connections << out }
915
+ end
916
+
917
+ post '/' do
918
+ # написать во все открытые потоки
919
+ connections.each { |out| out << params[:message] << "\n" }
920
+ "message sent"
921
+ end
922
+
923
+ === Логирование
924
+
925
+ В области видимости запроса метод +logger+ предоставляет доступ к экземпляру +Logger+:
926
+
927
+ get '/' do
928
+ logger.info "loading data"
929
+ # ...
930
+ end
931
+
932
+ Этот логер автоматически учитывает ваши настройки логирования в Rack. Если
933
+ логирование выключено, то этот метод вернет пустой (dummy) объект, поэтому вы можете
934
+ смело использовать его в маршрутах и фильтрах.
935
+
936
+ Заметьте, что логирование включено по умолчанию только для <tt>Sinatra::Application</tt>,
937
+ а если ваше приложение -- подкласс <tt>Sinatra::Base</tt>, то вы, наверное, захотите включить
938
+ его вручную:
939
+
940
+ class MyApp < Sinatra::Base
941
+ configure :production, :development do
942
+ enable :logging
943
+ end
944
+ end
945
+
946
+ Чтобы избежать использования любой логирующей "прослойки", задайте опции
947
+ +logging+ значение +nil+. Тем не менее, не забывайте, что в такой ситуации
948
+ +logger+ вернет +nil+. Чаще всего так делают, когда задают свой собственный
949
+ логер. Sinatra будет использовать то, что находится в <tt>env['rack.logger']</tt>.
950
+
951
+ === Mime-типы
952
+
953
+ Когда вы используете <tt>send_file</tt> или статические файлы, у вас могут быть mime-типы, которые Sinatra
954
+ не понимает по умолчанию. Используйте +mime_type+ для их регистрации по расширению файла:
955
+
956
+ configure do
957
+ mime_type :foo, 'text/foo'
958
+ end
959
+
960
+ Вы также можете использовать это в +content_type+ методе-помощнике:
961
+
962
+ get '/' do
963
+ content_type :foo
964
+ "foo foo foo"
965
+ end
966
+
967
+ === Генерирование URL
968
+
969
+ Чтобы сформировать URL вам следует использовать метод +url+, например, в Haml:
970
+
971
+ %a{:href => url('/foo')} foo
972
+
973
+ Этот метод учитывает обратные прокси и маршрутизаторы Rack, если они присутствуют.
974
+
975
+ Наряду с +url+ вы можете использовать +to+ (смотрите пример ниже).
976
+
977
+ === Перенаправление (редирект)
978
+
979
+ Вы можете перенаправить браузер пользователя с помощью метода +redirect+:
980
+
981
+ get '/foo' do
982
+ redirect to('/bar')
983
+ end
984
+
985
+ Любые дополнительные параметры используются по аналогии с аргументами метода +halt+:
986
+
987
+ redirect to('/bar'), 303
988
+ redirect 'http://google.com', 'wrong place, buddy'
989
+
990
+ Вы также можете перенаправить пользователя обратно, на страницу с которой он пришел,
991
+ с помощью <tt>redirect back</tt>:
992
+
993
+ get '/foo' do
994
+ "<a href='/bar'>do something</a>"
995
+ end
996
+
997
+ get '/bar' do
998
+ do_something
999
+ redirect back
1000
+ end
1001
+
1002
+ Чтобы передать какие-либо параметры вместе с перенаправлением, либо добавьте их в строку запроса:
1003
+
1004
+ redirect to('/bar?sum=42')
1005
+
1006
+ либо используйте сессию:
1007
+
1008
+ enable :sessions
1009
+
1010
+ get '/foo' do
1011
+ session[:secret] = 'foo'
1012
+ redirect to('/bar')
1013
+ end
1014
+
1015
+ get '/bar' do
1016
+ session[:secret]
1017
+ end
1018
+
1019
+ === Управление кэшированием
1020
+
1021
+ Установка корректных заголовков — основа правильного HTTP кэширования.
1022
+
1023
+ Вы можете легко выставить заголовок Cache-Control таким образом:
1024
+
1025
+ get '/' do
1026
+ cache_control :public
1027
+ "cache it!"
1028
+ end
1029
+
1030
+ Совет: задавайте кэширование в +before+-фильтре:
1031
+
1032
+ before do
1033
+ cache_control :public, :must_revalidate, :max_age => 60
1034
+ end
1035
+
1036
+ Если вы используете метод +expires+ для задания соответствующего заголовка,
1037
+ то <tt>Cache-Control</tt> будет выставлен автоматически:
1038
+
1039
+ before do
1040
+ expires 500, :public, :must_revalidate
1041
+ end
1042
+
1043
+ Чтобы как следует использовать кэширование, вам следует подумать об использовании
1044
+ +etag+ или +last_modified+. Рекомендуется использовать эти методы-помощники *до*
1045
+ выполнения ресурсоемких вычислений, так как они немедленно отправят ответ клиенту,
1046
+ если текущая версия уже есть в их кэше:
1047
+
1048
+ get '/article/:id' do
1049
+ @article = Article.find params[:id]
1050
+ last_modified @article.updated_at
1051
+ etag @article.sha1
1052
+ erb :article
1053
+ end
1054
+
1055
+ Также вы можете использовать
1056
+ {weak ETag}[http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation]:
1057
+
1058
+ etag @article.sha1, :weak
1059
+
1060
+ Эти методы-помощники не станут ничего кэшировать для вас, но они дадут
1061
+ необходимую информацию для вашего кэша. Если вы ищите легкое решение для
1062
+ кэширования, попробуйте {rack-cache}[https://github.com/rtomayko/rack-cache]:
1063
+
1064
+ require 'rack/cache'
1065
+ require 'sinatra'
1066
+
1067
+ use Rack::Cache
1068
+
1069
+ get '/' do
1070
+ cache_control :public, :max_age => 36000
1071
+ sleep 5
1072
+ "hello"
1073
+ end
1074
+
1075
+ Используйте опцию <tt>:static_cache_control</tt> (см. ниже), чтобы
1076
+ добавить заголовок <tt>Cache-Control</tt> к статическим файлам.
1077
+
1078
+ В соответствии с RFC 2616 ваше приложение должно вести себя по-разному,
1079
+ когда заголовки If-Match или If-None-Match имеют значение <tt>*</tt>, в
1080
+ зависимости от того, существует или нет запрашиваемый ресурс. Sinatra
1081
+ предполагает, что ресурсы, к которым обращаются с помощью безопасных
1082
+ (GET) и идемпотентных (PUT) методов, уже существуют, а остальные ресурсы
1083
+ (к которым обращаются, например, с помощью POST) считает новыми. Вы
1084
+ можете изменить данное поведение с помощью опции <tt>:new_resource</tt>:
1085
+
1086
+ get '/create' do
1087
+ etag '', :new_resource => true
1088
+ Article.create
1089
+ erb :new_article
1090
+ end
1091
+
1092
+ Если вы хотите использовать weak ETag, задайте опцию <tt>:kind</tt>:
1093
+
1094
+ etag '', :new_resource => true, :kind => :weak
1095
+
1096
+ === Отправка файлов
1097
+
1098
+ Для отправки файлов пользователю вы можете использовать метод <tt>send_file</tt>:
1099
+
1100
+ get '/' do
1101
+ send_file 'foo.png'
1102
+ end
1103
+
1104
+ Этот метод имеет несколько опций:
1105
+
1106
+ send_file 'foo.png', :type => :jpg
1107
+
1108
+ Возможные опции:
1109
+
1110
+ [filename]
1111
+ имя файла, по умолчанию: реальное имя файла.
1112
+
1113
+ [last_modified]
1114
+ значение для заголовка Last-Modified, по умолчанию: mtime (время изменения) файла.
1115
+
1116
+ [type]
1117
+ тип файла, по умолчанию: предполагается по расширению файла.
1118
+
1119
+ [disposition]
1120
+ используется для заголовка Content-Disposition, возможные значения:
1121
+ +nil+ (по умолчанию), <tt>:attachment</tt> и <tt>:inline</tt>.
1122
+
1123
+ [length]
1124
+ значения для заголовка Content-Length, по умолчанию: размер файла.
1125
+
1126
+ [status]
1127
+ Код ответа. Полезно, когда отдается статический файл в качестве страницы
1128
+ с сообщением об ошибке.
1129
+
1130
+ Этот метод будет использовать возможности Rack сервера для отправки файлов, если они
1131
+ доступны, а в противном случае, будет напрямую отдавать файл из Ruby процесса.
1132
+ Метод <tt>send_file</tt> также обеспечивает автоматическую обработку частичных (range)
1133
+ запросов с помощью Sinatra.
1134
+
1135
+ === Доступ к объекту запроса
1136
+
1137
+ Объект входящего запроса доступен на уровне обработки запроса (в фильтрах, маршрутах,
1138
+ обработчиках ошибок) с помощью <tt>request</tt> метода:
1139
+
1140
+ # приложение запущено на http://example.com/example
1141
+ get '/foo' do
1142
+ t = %w[text/css text/html application/javascript]
1143
+ request.accept # ['text/html', '*/*']
1144
+ request.accept? 'text/xml' # true
1145
+ request.preferred_type(t) # 'text/html'
1146
+ request.body # тело запроса, посланное клиентом (см. ниже)
1147
+ request.scheme # "http"
1148
+ request.script_name # "/example"
1149
+ request.path_info # "/foo"
1150
+ request.port # 80
1151
+ request.request_method # "GET"
1152
+ request.query_string # ""
1153
+ request.content_length # длина тела запроса
1154
+ request.media_type # медиатип тела запроса
1155
+ request.host # "example.com"
1156
+ request.get? # true (есть аналоги для других методов HTTP)
1157
+ request.form_data? # false
1158
+ request["some_param"] # значение параметра some_param. Шорткат для хеша params
1159
+ request.referrer # источник запроса клиента либо '/'
1160
+ request.user_agent # user agent (используется для :agent условия)
1161
+ request.cookies # хеш, содержащий cookies браузера
1162
+ request.xhr? # является ли запрос ajax запросом?
1163
+ request.url # "http://example.com/example/foo"
1164
+ request.path # "/example/foo"
1165
+ request.ip # IP-адрес клиента
1166
+ request.secure? # false (true, если запрос сделан через SSL)
1167
+ request.forwarded? # true (если сервер работает за обратным прокси)
1168
+ request.env # "сырой" env хеш, полученный Rack
1169
+ end
1170
+
1171
+ Некоторые опции, такие как <tt>script_name</tt> или <tt>path_info</tt> доступны для изменения:
1172
+
1173
+ before { request.path_info = "/" }
1174
+
1175
+ get "/" do
1176
+ "all requests end up here"
1177
+ end
1178
+
1179
+ <tt>request.body</tt> является IO или StringIO объектом:
1180
+
1181
+ post "/api" do
1182
+ request.body.rewind # в случае, если кто-то уже прочитал тело запроса
1183
+ data = JSON.parse request.body.read
1184
+ "Hello #{data['name']}!"
1185
+ end
1186
+
1187
+ === Вложения
1188
+
1189
+ Вы можете использовать метод +attachment+, чтобы сказать браузеру, что ответ
1190
+ сервера должен быть сохранен на диск, а не отображен:
1191
+
1192
+ get '/' do
1193
+ attachment
1194
+ "store it!"
1195
+ end
1196
+
1197
+ Вы также можете указать имя файла:
1198
+
1199
+ get '/' do
1200
+ attachment "info.txt"
1201
+ "store it!"
1202
+ end
1203
+
1204
+ === Работа с временем и датами
1205
+
1206
+ Sinatra предлагает метод-помощник +time_for+, который из заданного значения
1207
+ создает объект Time. Он также может конвертировать +DateTime+, +Date+ и
1208
+ подобные классы:
1209
+
1210
+ get '/' do
1211
+ pass if Time.now > time_for('Dec 23, 2012')
1212
+ "still time"
1213
+ end
1214
+
1215
+ Этот метод используется внутри Sinatra методами +expires+, +last_modified+ и им
1216
+ подобными. Поэтому вы легко можете расширить функционал этих методов,
1217
+ переопределив +time_for+ в своем приложении:
1218
+
1219
+ helpers do
1220
+ def time_for(value)
1221
+ case value
1222
+ when :yesterday then Time.now - 24*60*60
1223
+ when :tomorrow then Time.now + 24*60*60
1224
+ else super
1225
+ end
1226
+ end
1227
+ end
1228
+
1229
+ get '/' do
1230
+ last_modified :yesterday
1231
+ expires :tomorrow
1232
+ "hello"
1233
+ end
1234
+
1235
+ === Поиск шаблонов
1236
+
1237
+ Для поиска шаблонов и их последующего рендеринга используется метод <tt>find_template</tt>:
1238
+
1239
+ find_template settings.views, 'foo', Tilt[:haml] do |file|
1240
+ puts "could be #{file}"
1241
+ end
1242
+
1243
+ Это не слишком полезный пример. Зато полезен тот факт, что вы можете переопределить
1244
+ этот метод, чтобы использовать свой собственный механизм поиска. Например, если вы
1245
+ хотите, чтобы можно было использовать несколько директорий с шаблонами:
1246
+
1247
+ set :views, ['views', 'templates']
1248
+
1249
+ helpers do
1250
+ def find_template(views, name, engine, &block)
1251
+ Array(views).each { |v| super(v, name, engine, &block) }
1252
+ end
1253
+ end
1254
+
1255
+ Другой пример, в котором используются разные директории для движков рендеринга:
1256
+
1257
+ set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'
1258
+
1259
+ helpers do
1260
+ def find_template(views, name, engine, &block)
1261
+ _, folder = views.detect { |k,v| engine == Tilt[k] }
1262
+ folder ||= views[:default]
1263
+ super(folder, name, engine, &block)
1264
+ end
1265
+ end
1266
+
1267
+ Вы можете легко вынести этот код в расширение и поделиться им с остальными!
1268
+
1269
+ Заметьте, что <tt>find_template</tt> не проверяет, существует ли файл на самом деле,
1270
+ а вызывает заданный блок для всех возможных путей. Дело тут не в производительности,
1271
+ дело в том, что +render+ вызовет +break+, как только файл не будет найден.
1272
+ Содержимое и местонахождение шаблонов будет закэшировано, если приложение запущено не
1273
+ в режиме разработки (set :environment, :development). Вы должны помнить об этих нюансах,
1274
+ если пишите по-настоящему "сумасшедший" метод.
1275
+
1276
+ == Конфигурация
1277
+
1278
+ Этот блок исполняется один раз при старте в любом окружении, режиме (environment):
1279
+
1280
+ configure do
1281
+ # задание одной опции
1282
+ set :option, 'value'
1283
+
1284
+ # устанавливаем несколько опций
1285
+ set :a => 1, :b => 2
1286
+
1287
+ # то же самое, что и `set :option, true`
1288
+ enable :option
1289
+
1290
+ # то же самое, что и `set :option, false`
1291
+ disable :option
1292
+
1293
+ # у вас могут быть "динамические" опции с блоками
1294
+ set(:css_dir) { File.join(views, 'css') }
1295
+ end
1296
+
1297
+ Будет запущено, когда окружение (RACK_ENV переменная) <tt>:production</tt>:
1298
+
1299
+ configure :production do
1300
+ ...
1301
+ end
1302
+
1303
+ Будет запущено, когда окружение <tt>:production</tt> или <tt>:test</tt>:
1304
+
1305
+ configure :production, :test do
1306
+ ...
1307
+ end
1308
+
1309
+ Вы можете получить доступ к этим опциям с помощью <tt>settings</tt>:
1310
+
1311
+ configure do
1312
+ set :foo, 'bar'
1313
+ end
1314
+
1315
+ get '/' do
1316
+ settings.foo? # => true
1317
+ settings.foo # => 'bar'
1318
+ ...
1319
+ end
1320
+
1321
+ === Настройка защиты от атак
1322
+
1323
+ Sinatra использует
1324
+ {Rack::Protection}[https://github.com/rkh/rack-protection#readme] для
1325
+ защиты приложения от простых атак. Вы можете легко выключить эту
1326
+ защиту (что сделает ваше приложение чрезвычайно уязвимым):
1327
+
1328
+ disable :protection
1329
+
1330
+ Чтобы пропустить какой-либо уровень защиты, передайте хеш опций
1331
+ в параметр +protection+:
1332
+
1333
+ set :protection, :except => :path_traversal
1334
+
1335
+ Вы также можете отключить сразу несколько уровней защиты:
1336
+
1337
+ set :protection, :except => [:path_traversal, :session_hijacking]
1338
+
1339
+ === Доступные настройки
1340
+
1341
+ [absolute_redirects] если отключено, то Sinatra будет позволять использование
1342
+ относительных перенаправлений, но при этом перестанет
1343
+ соответствовать RFC 2616 (HTTP 1.1), который разрешает только
1344
+ абсолютные перенаправления.
1345
+
1346
+ Включайте эту опцию, если ваше приложение работает за обратным прокси,
1347
+ который настроен не совсем корректно. Обратите внимание, метод +url+
1348
+ все равно будет генерировать абсолютные URL, если вы не передадите
1349
+ +false+ вторым аргументом.
1350
+
1351
+ Отключено по умолчанию.
1352
+
1353
+ [add_charsets] mime-типы, к которым метод <tt>content_type</tt> будет автоматически
1354
+ добавлять информацию о кодировке.
1355
+
1356
+ Вам следует добавлять значения к этой опции вместо ее переопределения:
1357
+
1358
+ settings.add_charsets << "application/foobar"
1359
+
1360
+ [app_file] путь к главному файлу приложения, используется для нахождения корневой
1361
+ директории проекта, директорий с шаблонами и статическими файлами,
1362
+ вложенных шаблонов.
1363
+
1364
+ [bind] используемый IP-адрес (по умолчанию: 0.0.0.0). Используется только
1365
+ встроенным сервером.
1366
+
1367
+ [default_encoding] кодировка, если неизвестна (по умолчанию: <tt>"utf-8"</tt>).
1368
+
1369
+ [dump_errors] отображать ошибки в логе.
1370
+
1371
+ [environment] текущее окружение, по умолчанию, значение <tt>ENV['RACK_ENV']</tt>
1372
+ или <tt>"development"</tt>, если <tt>ENV['RACK_ENV']</tt> не доступна.
1373
+
1374
+ [logging] использовать логер.
1375
+
1376
+ [lock] создает блокировку для каждого запроса, которая гарантирует обработку
1377
+ только одного запроса в текущий момент времени в Ruby процессе.
1378
+
1379
+ Включайте, если ваше приложение не потоко-безопасно (thread-safe).
1380
+ Отключено по умолчанию.
1381
+
1382
+ [method_override] использовать "магический" параметр <tt>_method</tt>, чтобы позволить
1383
+ использование PUT/DELETE форм в браузерах, которые не поддерживают
1384
+ эти методы.
1385
+
1386
+ [port] порт, на котором будет работать сервер. Используется только
1387
+ встроенным сервером.
1388
+
1389
+ [prefixed_redirects] добавлять или нет параметр <tt>request.script_name</tt> к редиректам,
1390
+ если не задан абсолютный путь. Таким образом, <tt>redirect '/foo'</tt>
1391
+ будет вести себя как <tt>redirect to('/foo')</tt>. Отключено по умолчанию.
1392
+
1393
+ [protection] включена или нет защита от атак. Смотрите секцию выше.
1394
+
1395
+ [public_folder] путь к директории, откуда будут раздаваться статические файлы.
1396
+ Используется только, если включена раздача статических файлов
1397
+ (см. опцию <tt>static</tt> ниже).
1398
+
1399
+ [reload_templates] перезагружать или нет шаблоны на каждый запрос.
1400
+ Включено в режиме разработки.
1401
+
1402
+ [root] путь к корневой директории проекта.
1403
+
1404
+ [raise_errors] выбрасывать исключения (будет останавливать приложение). По умолчанию
1405
+ включено только в окружении <tt>test</tt>.
1406
+
1407
+ [run] если включено, Sinatra будет самостоятельно запускать веб-сервер.
1408
+ Не включайте, если используете rackup или аналогичные средства.
1409
+
1410
+ [running] работает ли сейчас встроенный сервер?
1411
+ Не меняйте эту опцию!
1412
+
1413
+ [server] сервер или список серверов, которые следует использовать в качестве
1414
+ встроенного сервера. По умолчанию: ['thin', 'mongrel', 'webrick'],
1415
+ порядок задает приоритет.
1416
+
1417
+ [sessions] включить сессии на основе кук (cookie) на базе
1418
+ <tt>Rack::Session::Cookie</tt>. Смотрите секцию "Использование сессий"
1419
+ выше.
1420
+
1421
+ [show_exceptions] показывать исключения/стек вызовов (stack trace) в браузере.
1422
+ По умолчанию включено только в окружении <tt>development</tt>.
1423
+ Может быть установлено в <tt>:after_handler</tt> для запуска специфичной
1424
+ для приложения обработки ошибок, прежде чем показывать трассировку
1425
+ стека в браузере.
1426
+
1427
+ [static] должна ли Sinatra осуществлять раздачу статических файлов.
1428
+ Отключите, когда используете какой-либо веб-сервер для этой цели.
1429
+ Отключение значительно улучшит производительность приложения.
1430
+ По умолчанию включено в классических и отключено в модульных
1431
+ приложениях.
1432
+
1433
+ [static_cache_control] когда Sinatra отдает статические файлы, используйте эту опцию,
1434
+ чтобы добавить им заголовок <tt>Cache-Control</tt>. Для этого
1435
+ используется метод-помощник +cache_control+. По умолчанию отключено.
1436
+ Используйте массив, когда надо задать несколько значений:
1437
+ <tt>set :static_cache_control, [:public, :max_age => 300]</tt>
1438
+
1439
+ [threaded] если включено, то Thin будет использовать
1440
+ <tt>EventMachine.defer</tt> для обработки запросов.
1441
+
1442
+ [views] путь к директории с шаблонами.
1443
+
1444
+ == Режим, окружение
1445
+
1446
+ Есть 3 предопределенных режима, окружения: <tt>"development"</tt>,
1447
+ <tt>"production"</tt> и <tt>"test"</tt>. Режим может быть
1448
+ задан через переменную окружения +RACK_ENV+. Значение по умолчанию
1449
+ — <tt>"development"</tt>. В этом режиме работы, все шаблоны
1450
+ перезагружаются между запросами. А также задаются специальные
1451
+ обработчики <tt>not_found</tt> и <tt>error</tt>, чтобы вы могли
1452
+ увидеть стек вызовов. В окружениях <tt>"production"</tt> и
1453
+ <tt>"test"</tt> шаблоны по умолчанию кэшируются.
1454
+
1455
+ Для запуска приложения в определенном окружении, используйте
1456
+ ключ <tt>-e</tt>
1457
+
1458
+ ruby my_app.rb -e [ENVIRONMENT]
1459
+
1460
+ Вы можете использовать предопределенные методы +development?+, +test?+
1461
+ и +production?, чтобы определить текущее окружение.
1462
+
1463
+ == Обработка ошибок
1464
+
1465
+ Обработчики ошибок исполняются в том же контексте, что и маршруты, и +before+-фильтры, а это означает, что всякие
1466
+ прелести вроде <tt>haml</tt>, <tt>erb</tt>, <tt>halt</tt> и т.д. доступны и им.
1467
+
1468
+ === Not Found
1469
+
1470
+ Когда выброшено исключение <tt>Sinatra::NotFound</tt>, или кодом ответа является 404,
1471
+ то будет вызван <tt>not_found</tt> обработчик:
1472
+
1473
+ not_found do
1474
+ 'This is nowhere to be found.'
1475
+ end
1476
+
1477
+ === Ошибки
1478
+
1479
+ Обработчик ошибок +error+ будет вызван, когда исключение выброшено из блока маршрута, либо из фильтра.
1480
+ Объект-исключение доступен как переменная <tt>sinatra.error</tt> в Rack:
1481
+
1482
+ error do
1483
+ 'Sorry there was a nasty error - ' + env['sinatra.error'].name
1484
+ end
1485
+
1486
+ Частные ошибки:
1487
+
1488
+ error MyCustomError do
1489
+ 'So what happened was...' + env['sinatra.error'].message
1490
+ end
1491
+
1492
+ Тогда, если это произошло:
1493
+
1494
+ get '/' do
1495
+ raise MyCustomError, 'something bad'
1496
+ end
1497
+
1498
+ То вы получите:
1499
+
1500
+ So what happened was... something bad
1501
+
1502
+ Также вы можете установить обработчик ошибок для кода состояния HTTP:
1503
+
1504
+ error 403 do
1505
+ 'Access forbidden'
1506
+ end
1507
+
1508
+ get '/secret' do
1509
+ 403
1510
+ end
1511
+
1512
+ Либо набора кодов:
1513
+
1514
+ error 400..510 do
1515
+ 'Boom'
1516
+ end
1517
+
1518
+ Sinatra устанавливает специальные <tt>not_found</tt> и <tt>error</tt> обработчики, когда приложение запущено в режиме
1519
+ разработки (окружение <tt>:development</tt>).
1520
+
1521
+ == Rack "прослойки"
1522
+
1523
+ Sinatra использует Rack[http://rack.rubyforge.org/], минимальный стандартный
1524
+ интерфейс для веб-фреймворков на Ruby. Одной из самых интересных для разработчиков возможностей Rack
1525
+ является поддержка "прослоек" ("middleware") — компонентов,
1526
+ находящихся "между" сервером и вашим приложением, которые отслеживают и/или манипулируют
1527
+ HTTP запросами/ответами для предоставления различной функциональности.
1528
+
1529
+ В Sinatra очень просто использовать такие "прослойки" с помощью метода +use+:
1530
+
1531
+ require 'sinatra'
1532
+ require 'my_custom_middleware'
1533
+
1534
+ use Rack::Lint
1535
+ use MyCustomMiddleware
1536
+
1537
+ get '/hello' do
1538
+ 'Hello World'
1539
+ end
1540
+
1541
+ Семантика +use+ идентична той, что определена для
1542
+ Rack::Builder[http://rack.rubyforge.org/doc/classes/Rack/Builder.html] DSL
1543
+ (чаще всего используется в rackup файлах). Например, метод +use+ принимает
1544
+ как множественные переменные, так и блоки:
1545
+
1546
+ use Rack::Auth::Basic do |username, password|
1547
+ username == 'admin' && password == 'secret'
1548
+ end
1549
+
1550
+ Rack распространяется с различными стандартными "прослойками"
1551
+ для логирования, отладки, маршрутизации URL, аутентификации, обработки сессий. Sinatra использует
1552
+ многие из этих компонентов автоматически, основываясь на конфигурации, чтобы вам не приходилось
1553
+ подключать (+use+) их вручную.
1554
+
1555
+ Вы можете найти полезные прослойки в
1556
+ {rack}[https://github.com/rack/rack/tree/master/lib/rack],
1557
+ {rack-contrib}[https://github.com/rack/rack-contrib#readme],
1558
+ {CodeRack}[http://coderack.org/] или в
1559
+ {Rack wiki}[https://github.com/rack/rack/wiki/List-of-Middleware].
1560
+
1561
+ == Тестирование
1562
+
1563
+ Тесты для Sinatra приложений могут быть написаны с помощью библиотек, фреймворков, поддерживающих
1564
+ тестирование Rack. {Rack::Test}[http://rdoc.info/github/brynary/rack-test/master/frames] рекомендован:
1565
+
1566
+ require 'my_sinatra_app'
1567
+ require 'test/unit'
1568
+ require 'rack/test'
1569
+
1570
+ class MyAppTest < Test::Unit::TestCase
1571
+ include Rack::Test::Methods
1572
+
1573
+ def app
1574
+ Sinatra::Application
1575
+ end
1576
+
1577
+ def test_my_default
1578
+ get '/'
1579
+ assert_equal 'Hello World!', last_response.body
1580
+ end
1581
+
1582
+ def test_with_params
1583
+ get '/meet', :name => 'Frank'
1584
+ assert_equal 'Hello Frank!', last_response.body
1585
+ end
1586
+
1587
+ def test_with_rack_env
1588
+ get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
1589
+ assert_equal "You're using Songbird!", last_response.body
1590
+ end
1591
+ end
1592
+
1593
+ == Sinatra::Base — "прослойки", библиотеки и модульные приложения
1594
+
1595
+ Описание своего приложения самым простейшим способом (с помощью DSL верхнего уровня,
1596
+ классический стиль) отлично работает для крохотных приложений. В таких случаях
1597
+ используется конфигурация, рассчитанная на микро-приложения
1598
+ (единственный файл приложения, <tt>./public</tt> и <tt>./views</tt> директории,
1599
+ логирование, страница информации об исключении и т.д.). Тем не менее,
1600
+ такой метод имеет множество недостатков при создании компонентов, таких как
1601
+ Rack middleware ("прослоек"), Rails metal, простых библиотек с серверными компонентами,
1602
+ расширений Sinatra. И тут на помощь приходит <tt>Sinatra::Base</tt>:
1603
+
1604
+ require 'sinatra/base'
1605
+
1606
+ class MyApp < Sinatra::Base
1607
+ set :sessions, true
1608
+ set :foo, 'bar'
1609
+
1610
+ get '/' do
1611
+ 'Hello world!'
1612
+ end
1613
+ end
1614
+
1615
+ Методы, доступные <tt>Sinatra::Base</tt> подклассам идентичны тем, что доступны
1616
+ приложениям в DSL верхнего уровня. Большинство таких приложений могут быть
1617
+ конвертированы в <tt>Sinatra::Base</tt> компоненты с помощью двух модификаций:
1618
+
1619
+ * Вы должны подключать <tt>sinatra/base</tt> вместо +sinatra+,
1620
+ иначе все методы, предоставляемые Sinatra, будут импортированы в глобальное пространство имен.
1621
+ * Поместите все маршруты, обработчики ошибок, фильтры и опции в подкласс <tt>Sinatra::Base</tt>.
1622
+
1623
+ <tt>Sinatra::Base</tt> — это чистый лист. Большинство опций, включая встроенный сервер, по умолчанию отключены.
1624
+ Смотрите {Опции и конфигурация}[http://www.sinatrarb.com/configuration.html] для детальной информации
1625
+ об опциях и их поведении.
1626
+
1627
+ === Модульные приложения против классических
1628
+
1629
+ Вопреки всеобщему убеждению, в классическом стиле (самом простом) нет ничего плохого.
1630
+ Если этот стиль подходит вашему приложению, вы не обязаны переписывать его в модульное
1631
+ приложение.
1632
+
1633
+ Основным недостатком классического стиля является тот факт, что у вас может
1634
+ быть только одно приложение Sinatra на один процесс Ruby. Если вы планируете
1635
+ использовать больше, переключайтесь на модульный стиль. Вы можете смело
1636
+ смешивать модульный и классический стили.
1637
+
1638
+ Переходя с одного стиля на другой, примите во внимание следующие изменения в настройках:
1639
+
1640
+ Опция Классический Модульный
1641
+
1642
+ app_file файл с приложением файл с подклассом Sinatra::Base
1643
+ run $0 == app_file false
1644
+ logging true false
1645
+ method_override true false
1646
+ inline_templates true false
1647
+ static true false
1648
+
1649
+
1650
+ === Запуск модульных приложений
1651
+
1652
+ Есть два общепринятых способа запускать модульные приложения: запуск напрямую с помощью <tt>run!</tt>:
1653
+
1654
+ # my_app.rb
1655
+ require 'sinatra/base'
1656
+
1657
+ class MyApp < Sinatra::Base
1658
+ # ... здесь код приложения ...
1659
+
1660
+ # запускаем сервер, если исполняется текущий файл
1661
+ run! if app_file == $0
1662
+ end
1663
+
1664
+ И запускаем с помощью:
1665
+
1666
+ ruby my_app.rb
1667
+
1668
+ Или с помощью конфигурационного файла <tt>config.ru</tt>, который позволяет использовать любой
1669
+ Rack-совместимый сервер приложений.
1670
+
1671
+ # config.ru
1672
+ require './my_app'
1673
+ run MyApp
1674
+
1675
+ Запускаем:
1676
+
1677
+ rackup -p 4567
1678
+
1679
+ === Запуск классических приложений с config.ru
1680
+
1681
+ Файл приложения:
1682
+
1683
+ # app.rb
1684
+ require 'sinatra'
1685
+
1686
+ get '/' do
1687
+ 'Hello world!'
1688
+ end
1689
+
1690
+ И соответствующий <tt>config.ru</tt>:
1691
+
1692
+ require './app'
1693
+ run Sinatra::Application
1694
+
1695
+ === Когда использовать config.ru?
1696
+
1697
+ Вот несколько причин, по которым вы, возможно, захотите использовать <tt>config.ru</tt>:
1698
+
1699
+ * вы хотите разворачивать свое приложение на различных Rack-совместимых серверах (Passenger, Unicorn,
1700
+ Heroku, ...);
1701
+ * вы хотите использовать более одного подкласса <tt>Sinatra::Base</tt>;
1702
+ * вы хотите использовать Sinatra только в качестве "прослойки" Rack.
1703
+
1704
+ <b>Совсем необязательно переходить на использование <tt>config.ru</tt> лишь потому, что вы стали
1705
+ использовать модульный стиль приложения. И необязательно использовать модульный стиль, чтобы
1706
+ запускать приложение с помощью <tt>config.ru</tt>.</b>
1707
+
1708
+ === Использование Sinatra в качестве "прослойки"
1709
+
1710
+ Не только сама Sinatra может использовать "прослойки" Rack, но и любое Sinatra приложение
1711
+ само может быть добавлено к любому Rack endpoint в качестве "прослойки". Этим endpoint (конечной точкой)
1712
+ может быть другое Sinatra приложение, или приложение, основанное на Rack (Rails/Ramaze/Camping/...):
1713
+
1714
+ require 'sinatra/base'
1715
+
1716
+ class LoginScreen < Sinatra::Base
1717
+ enable :sessions
1718
+
1719
+ get('/login') { haml :login }
1720
+
1721
+ post('/login') do
1722
+ if params[:name] == 'admin' && params[:password] == 'admin'
1723
+ session['user_name'] = params[:name]
1724
+ else
1725
+ redirect '/login'
1726
+ end
1727
+ end
1728
+ end
1729
+
1730
+ class MyApp < Sinatra::Base
1731
+ # "прослойка" будет запущена перед фильтрами
1732
+ use LoginScreen
1733
+
1734
+ before do
1735
+ unless session['user_name']
1736
+ halt "Access denied, please <a href='/login'>login</a>."
1737
+ end
1738
+ end
1739
+
1740
+ get('/') { "Hello #{session['user_name']}." }
1741
+ end
1742
+
1743
+ === Создание приложений "на лету"
1744
+
1745
+ Иногда требуется создавать Sinatra приложения "на лету" (например,
1746
+ из другого приложения). Это возможно с помощью <tt>Sinatra.new</tt>:
1747
+
1748
+ require 'sinatra/base'
1749
+ my_app = Sinatra.new { get('/') { "hi" } }
1750
+ my_app.run!
1751
+
1752
+ Этот метод может принимать аргументом приложение, от которого
1753
+ следует наследоваться:
1754
+
1755
+ # config.ru
1756
+ require 'sinatra/base'
1757
+
1758
+ controller = Sinatra.new do
1759
+ enable :logging
1760
+ helpers MyHelpers
1761
+ end
1762
+
1763
+ map('/a') do
1764
+ run Sinatra.new(controller) { get('/') { 'a' } }
1765
+ end
1766
+
1767
+ map('/b') do
1768
+ run Sinatra.new(controller) { get('/') { 'b' } }
1769
+ end
1770
+
1771
+ Это особенно полезно для тестирования расширений Sinatra и при
1772
+ использовании Sinatra внутри вашей библиотеки.
1773
+
1774
+ Благодаря этому, использовать Sinatra как "прослойку" очень просто:
1775
+
1776
+ require 'sinatra/base'
1777
+
1778
+ use Sinatra do
1779
+ get('/') { ... }
1780
+ end
1781
+
1782
+ run RailsProject::Application
1783
+
1784
+
1785
+ == Области видимости и привязка
1786
+
1787
+ Текущая область видимости определяет методы и переменные, доступные
1788
+ в данный момент.
1789
+
1790
+ === Область видимости приложения / класса
1791
+
1792
+ Любое Sinatra приложение соответствует подклассу <tt>Sinatra::Base</tt>. Если вы
1793
+ используете DSL верхнего уровня (<tt>require 'sinatra'</tt>), то этим классом будет
1794
+ <tt>Sinatra::Application</tt>, иначе это будет подкласс, который вы создали вручную.
1795
+ На уровне класса вам будут доступны такие методы, как +get+ или +before+, но вы
1796
+ не сможете получить доступ к объектам +request+ или +session+, так как существует
1797
+ только один класс приложения для всех запросов.
1798
+
1799
+ Опции, созданные с помощью +set+, являются методами уровня класса:
1800
+
1801
+ class MyApp < Sinatra::Base
1802
+ # Я в области видимости приложения!
1803
+ set :foo, 42
1804
+ foo # => 42
1805
+
1806
+ get '/foo' do
1807
+ # Я больше не в области видимости приложения!
1808
+ end
1809
+ end
1810
+
1811
+ У вас будет область видимости приложения внутри:
1812
+
1813
+ * тела вашего класса приложения;
1814
+ * методов, определенных расширениями;
1815
+ * блока, переданного в +helpers+;
1816
+ * блоков, использованных как значения для +set+;
1817
+ * блока, переданного в <tt>Sinatra.new</tt>.
1818
+
1819
+ Вы можете получить доступ к объекту области видимости (классу приложения) следующими способами:
1820
+
1821
+ * через объект, переданный блокам конфигурации (<tt>configure { |c| ... }</tt>);
1822
+ * +settings+ внутри области видимости запроса.
1823
+
1824
+ === Область видимости запроса/экземпляра
1825
+
1826
+ Для каждого входящего запроса будет создан новый экземпляр вашего приложения,
1827
+ и все блоки обработчика будут запущены в этом контексте. В этой области
1828
+ видимости вам доступны +request+ и +session+ объекты, вызовы методов
1829
+ рендеринга, такие как +erb+ или +haml+. Вы можете получить доступ к
1830
+ области видимости приложения из контекста запроса, используя метод-помощник +settings+:
1831
+
1832
+ class MyApp < Sinatra::Base
1833
+ # Я в области видимости приложения!
1834
+ get '/define_route/:name' do
1835
+ # Область видимости запроса '/define_route/:name'
1836
+ @value = 42
1837
+
1838
+ settings.get("/#{params[:name]}") do
1839
+ # Область видимости запроса "/#{params[:name]}"
1840
+ @value # => nil (другой запрос)
1841
+ end
1842
+
1843
+ "Route defined!"
1844
+ end
1845
+ end
1846
+
1847
+ У вас будет область видимости запроса в:
1848
+
1849
+ * get/head/post/put/delete/options блоках;
1850
+ * before/after фильтрах;
1851
+ * методах-помощниках;
1852
+ * шаблонах/отображениях.
1853
+
1854
+ === Область видимости делегирования
1855
+
1856
+ Область видимости делегирования просто перенаправляет методы в область видимости класса.
1857
+ Однако, она не полностью ведет себя как область видимости класса, так как у вас нет
1858
+ привязки к классу. Только методы, явно помеченные для делегирования, будут доступны,
1859
+ а переменных/состояний области видимости класса не будет (иначе говоря,
1860
+ у вас будет другой +self+ объект). Вы можете
1861
+ непосредственно добавить методы делегирования, используя
1862
+ <tt>Sinatra::Delegator.delegate :method_name</tt>.
1863
+
1864
+ У вас будет контекст делегирования внутри:
1865
+
1866
+ * привязки верхнего уровня, если вы сделали <tt>require 'sinatra'</tt>;
1867
+ * объекта, расширенного с помощью <tt>Sinatra::Delegator</tt>.
1868
+
1869
+ Посмотрите сами в код: вот
1870
+ {Sinatra::Delegator примесь}[https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633]
1871
+ {расширяет главный объект}[https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30].
1872
+
1873
+ == Командная строка
1874
+
1875
+ Sinatra приложения могут быть запущены напрямую:
1876
+
1877
+ ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
1878
+
1879
+ Опции включают:
1880
+
1881
+ -h # раздел помощи
1882
+ -p # указание порта (по умолчанию 4567)
1883
+ -o # указание хоста (по умолчанию 0.0.0.0)
1884
+ -e # указание окружения, режима (по умолчанию development)
1885
+ -s # указание rack сервера/обработчика (по умолчанию thin)
1886
+ -x # включить мьютекс-блокировку (по умолчанию выключена)
1887
+
1888
+ == Системные требования
1889
+
1890
+ Следующие версии Ruby официально поддерживаются:
1891
+
1892
+ [ Ruby 1.8.7 ]
1893
+ 1.8.7 полностью поддерживается, тем не менее, если вас ничто не держит на
1894
+ этой версии, рекомендуем обновиться до 1.9.2 или перейти на JRuby или Rubinius.
1895
+ Поддержка 1.8.7 не будет прекращена до выхода Sinatra 2.0 и Ruby 2.0,
1896
+ разве что в случае релиза 1.8.8 (что мало вероятно). Но даже тогда, возможно,
1897
+ поддержка не будет прекращена. <b>Ruby 1.8.6 больше не поддерживается.</b>
1898
+ Если вы хотите использовать 1.8.6, откатитесь до Sinatra 1.2, которая будет
1899
+ получать все исправления ошибок до тех пор, пока не будет выпущена
1900
+ Sinatra 1.4.0.
1901
+
1902
+ [ Ruby 1.9.2 ]
1903
+ 1.9.2 полностью поддерживается и рекомендована к использованию. Заметьте,
1904
+ что Radius и Markaby пока несовместимы с 1.9. Не используйте 1.9.2p0,
1905
+ известно, что эта версия очень нестабильна при использовании Sinatra.
1906
+ Эта версия будет поддерживаться по крайней мере до выхода Ruby 1.9.4/2.0,
1907
+ а поддержка последней версии 1.9 будет осуществляться до тех пор, пока
1908
+ она поддерживается командой разработчиков Ruby.
1909
+
1910
+ [ Ruby 1.9.3 ]
1911
+ 1.9.3 полностью поддерживается. Рекомендуем подождать релиза версии с
1912
+ большим уровнем патчсета (текущий — p0) перед тем, как использовать ее
1913
+ в рабочем окружении. Заметьте, что переход на 1.9.3 с ранних версий
1914
+ сделает недействительными все сессии.
1915
+
1916
+ [ Rubinius ]
1917
+ Rubinius официально поддерживается (Rubinius >= 1.2.4), всё, включая все
1918
+ языки шаблонов, работает. Предстоящий релиз 2.0 также поддерживается.
1919
+
1920
+ [ JRuby ]
1921
+ JRuby официально поддерживается (JRuby >= 1.6.5). Нет никаких проблем с
1922
+ использованием альтернативных шаблонов. Тем не менее, если вы выбираете
1923
+ JRuby, то, пожалуйста, посмотрите на JRuby Rack-сервера, так как Thin не
1924
+ поддерживается полностью на JRuby. Поддержка расширений на C в JRuby все
1925
+ еще экспериментальная, что на данный момент затрагивает только RDiscount,
1926
+ Redcarpet и RedCloth.
1927
+
1928
+ Мы также следим за предстоящими к выходу версиями Ruby.
1929
+
1930
+ Следующие реализации Ruby не поддерживаются официально, но известно, что на
1931
+ них запускается Sinatra:
1932
+
1933
+ * старые версии JRuby и Rubinius;
1934
+ * Ruby Enterprise Edition;
1935
+ * MacRuby, Maglev, IronRuby;
1936
+ * Ruby 1.9.0 и 1.9.1 (настоятельно не рекомендуются к использованию).
1937
+
1938
+ То, что версия официально не поддерживается, означает, что, если что-то не
1939
+ работает на этой версии, а на поддерживаемой работает — это не наша проблема, а их.
1940
+
1941
+ Мы также запускаем наши CI-тесты на версии Ruby, находящейся в разработке
1942
+ (предстоящей 2.0.0), и на 1.9.4, но мы не можем ничего гарантировать, так как
1943
+ они находятся в разработке. Предполагается, что 1.9.4p0 и 2.0.0p0 будут поддерживаться.
1944
+
1945
+ Sinatra должна работать на любой операционной системе, в которой есть одна из указанных выше версий Ruby.
1946
+
1947
+ Пока невозможно запустить Sinatra на Cardinal, SmallRuby, BlueRuby и на любой
1948
+ версии Ruby до 1.8.7.
1949
+
1950
+ == На острие
1951
+
1952
+ Если вы хотите использовать самый последний код Sinatra, не бойтесь запускать
1953
+ свое приложение вместе с кодом из master ветки Sinatra, она весьма стабильна.
1954
+
1955
+ Мы также время от времени выпускаем предварительные версии, так что вы можете делать так:
1956
+
1957
+ gem install sinatra --pre
1958
+
1959
+ Чтобы воспользоваться некоторыми самыми последними возможностями.
1960
+
1961
+ === С помощью Bundler
1962
+
1963
+ Если вы хотите запускать свое приложение с последней версией Sinatra, то
1964
+ рекомендуем использовать {Bundler}[http://gembundler.com/].
1965
+
1966
+ Сначала установите Bundler, если у вас его еще нет:
1967
+
1968
+ gem install bundler
1969
+
1970
+ Затем создайте файл +Gemfile+ в директории вашего проекта:
1971
+
1972
+ source :rubygems
1973
+ gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"
1974
+
1975
+ # другие зависимости
1976
+ gem 'haml' # например, если используете haml
1977
+ gem 'activerecord', '~> 3.0' # может быть, вам нужен и ActiveRecord 3.x
1978
+
1979
+ Обратите внимание, вам нужно будет указывать все зависимости вашего приложения
1980
+ в этом файле. Однако, непосредственные зависимости Sinatra (Rack и Tilt) Bundler
1981
+ автоматически скачает и добавит.
1982
+
1983
+ Теперь вы можете запускать свое приложение так:
1984
+
1985
+ bundle exec ruby myapp.rb
1986
+
1987
+ === Вручную
1988
+
1989
+ Создайте локальный клон репозитория и запускайте свое приложение с <tt>sinatra/lib</tt>
1990
+ директорией в <tt>$LOAD_PATH</tt>:
1991
+
1992
+ cd myapp
1993
+ git clone git://github.com/sinatra/sinatra.git
1994
+ ruby -Isinatra/lib myapp.rb
1995
+
1996
+ Чтобы обновить исходники Sinatra:
1997
+
1998
+ cd myapp/sinatra
1999
+ git pull
2000
+
2001
+ === Установка глобально
2002
+
2003
+ Вы можете самостоятельно собрать gem:
2004
+
2005
+ git clone git://github.com/sinatra/sinatra.git
2006
+ cd sinatra
2007
+ rake sinatra.gemspec
2008
+ rake install
2009
+
2010
+ Если вы устанавливаете пакеты (gem) от пользователя root, то вашим следующим шагом должна быть команда
2011
+
2012
+ sudo rake install
2013
+
2014
+ == Версии
2015
+
2016
+ Sinatra использует {Semantic Versioning}[http://semver.org/], SemVer и
2017
+ SemVerTag.
2018
+
2019
+ == Дальнейшее чтение
2020
+
2021
+ * {Веб-сайт проекта}[http://www.sinatrarb.com/] — Дополнительная документация,
2022
+ новости и ссылки на другие ресурсы.
2023
+ * {Участие в проекте}[http://www.sinatrarb.com/contributing] — Обнаружили баг? Нужна помощь? Написали патч?
2024
+ * {Слежение за проблемами/ошибками}[http://github.com/sinatra/sinatra/issues]
2025
+ * {Twitter}[http://twitter.com/sinatra]
2026
+ * {Группы рассылки}[http://groups.google.com/group/sinatrarb/topics]
2027
+ * {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] на http://freenode.net
2028
+ * {Sinatra Book}[http://sinatra-book.gittr.com] учебник и сборник рецептов
2029
+ * {Sinatra Recipes}[http://recipes.sinatrarb.com/] сборник рецептов
2030
+ * API документация к {последнему релизу}[http://rubydoc.info/gems/sinatra]
2031
+ или {текущему HEAD}[http://rubydoc.info/github/sinatra/sinatra] на
2032
+ http://rubydoc.info
2033
+ * {Сервер непрерывной интеграции}[http://travis-ci.org/sinatra/sinatra]