devcenter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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]