nano-sharp-gem 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.
- checksums.yaml +7 -0
- data/cuba-4.0.3/CHANGELOG +71 -0
- data/cuba-4.0.3/CONTRIBUTING +19 -0
- data/cuba-4.0.3/LICENSE +23 -0
- data/cuba-4.0.3/README.md +781 -0
- data/cuba-4.0.3/cuba.gemspec +18 -0
- data/cuba-4.0.3/examples/config.ru +18 -0
- data/cuba-4.0.3/examples/measure.rb +17 -0
- data/cuba-4.0.3/examples/rack-response.ru +21 -0
- data/cuba-4.0.3/examples/views/home.mote +7 -0
- data/cuba-4.0.3/examples/views/layout.mote +11 -0
- data/cuba-4.0.3/lib/cuba/capybara.rb +13 -0
- data/cuba-4.0.3/lib/cuba/render.rb +63 -0
- data/cuba-4.0.3/lib/cuba/safe/csrf.rb +47 -0
- data/cuba-4.0.3/lib/cuba/safe/secure_headers.rb +43 -0
- data/cuba-4.0.3/lib/cuba/safe.rb +23 -0
- data/cuba-4.0.3/lib/cuba/test.rb +11 -0
- data/cuba-4.0.3/lib/cuba.rb +436 -0
- data/cuba-4.0.3/makefile +4 -0
- data/cuba-4.0.3/test/accept.rb +77 -0
- data/cuba-4.0.3/test/captures.rb +162 -0
- data/cuba-4.0.3/test/composition.rb +103 -0
- data/cuba-4.0.3/test/cookie.rb +34 -0
- data/cuba-4.0.3/test/csrf.rb +140 -0
- data/cuba-4.0.3/test/extension.rb +21 -0
- data/cuba-4.0.3/test/helper.rb +11 -0
- data/cuba-4.0.3/test/host.rb +29 -0
- data/cuba-4.0.3/test/integration.rb +114 -0
- data/cuba-4.0.3/test/match.rb +86 -0
- data/cuba-4.0.3/test/middleware.rb +47 -0
- data/cuba-4.0.3/test/number.rb +36 -0
- data/cuba-4.0.3/test/on.rb +157 -0
- data/cuba-4.0.3/test/param.rb +66 -0
- data/cuba-4.0.3/test/path.rb +86 -0
- data/cuba-4.0.3/test/plugin.rb +68 -0
- data/cuba-4.0.3/test/rack.rb +22 -0
- data/cuba-4.0.3/test/redirect.rb +21 -0
- data/cuba-4.0.3/test/render.rb +128 -0
- data/cuba-4.0.3/test/root.rb +83 -0
- data/cuba-4.0.3/test/run.rb +23 -0
- data/cuba-4.0.3/test/safe.rb +74 -0
- data/cuba-4.0.3/test/segment.rb +45 -0
- data/cuba-4.0.3/test/session.rb +21 -0
- data/cuba-4.0.3/test/settings.rb +52 -0
- data/cuba-4.0.3/test/views/about.erb +1 -0
- data/cuba-4.0.3/test/views/about.str +1 -0
- data/cuba-4.0.3/test/views/content-yield.erb +1 -0
- data/cuba-4.0.3/test/views/custom/abs_path.mote +1 -0
- data/cuba-4.0.3/test/views/frag.mote +1 -0
- data/cuba-4.0.3/test/views/home.erb +2 -0
- data/cuba-4.0.3/test/views/home.mote +1 -0
- data/cuba-4.0.3/test/views/home.str +2 -0
- data/cuba-4.0.3/test/views/layout-alternative.erb +2 -0
- data/cuba-4.0.3/test/views/layout-yield.erb +3 -0
- data/cuba-4.0.3/test/views/layout.erb +2 -0
- data/cuba-4.0.3/test/views/layout.mote +2 -0
- data/cuba-4.0.3/test/views/layout.str +2 -0
- data/cuba-4.0.3/test/views/test.erb +1 -0
- data/cuba-4.0.3/test/with.rb +42 -0
- data/nano-sharp-gem.gemspec +11 -0
- metadata +99 -0
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
Cuba
|
|
2
|
+
====
|
|
3
|
+
|
|
4
|
+
_n_. a microframework for web development.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
Community
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
Meet us on IRC: [#cuba.rb][irc] on [freenode.net][freenode].
|
|
12
|
+
|
|
13
|
+
[irc]: irc://chat.freenode.net/#cuba.rb
|
|
14
|
+
[freenode]: http://freenode.net/
|
|
15
|
+
|
|
16
|
+
Description
|
|
17
|
+
-----------
|
|
18
|
+
|
|
19
|
+
Cuba is a microframework for web development originally inspired
|
|
20
|
+
by [Rum][rum], a tiny but powerful mapper for [Rack][rack]
|
|
21
|
+
applications.
|
|
22
|
+
|
|
23
|
+
It integrates many templates via [Tilt][tilt], and testing via
|
|
24
|
+
[Cutest][cutest] and [Capybara][capybara].
|
|
25
|
+
|
|
26
|
+
[rum]: http://github.com/chneukirchen/rum
|
|
27
|
+
[rack]: http://github.com/rack/rack
|
|
28
|
+
[tilt]: http://github.com/rtomayko/tilt
|
|
29
|
+
[cutest]: http://github.com/djanowski/cutest
|
|
30
|
+
[capybara]: http://github.com/jnicklas/capybara
|
|
31
|
+
[rack-test]: https://github.com/brynary/rack-test
|
|
32
|
+
|
|
33
|
+
Installation
|
|
34
|
+
------------
|
|
35
|
+
|
|
36
|
+
``` console
|
|
37
|
+
$ gem install cuba
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Usage
|
|
41
|
+
-----
|
|
42
|
+
|
|
43
|
+
Here's a simple application:
|
|
44
|
+
|
|
45
|
+
``` ruby
|
|
46
|
+
# cat hello_world.rb
|
|
47
|
+
require "cuba"
|
|
48
|
+
require "cuba/safe"
|
|
49
|
+
|
|
50
|
+
Cuba.use Rack::Session::Cookie, :secret => "__a_very_long_string__"
|
|
51
|
+
|
|
52
|
+
Cuba.plugin Cuba::Safe
|
|
53
|
+
|
|
54
|
+
Cuba.define do
|
|
55
|
+
on get do
|
|
56
|
+
on "hello" do
|
|
57
|
+
res.write "Hello world!"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
on root do
|
|
61
|
+
res.redirect "/hello"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
And the test file:
|
|
68
|
+
|
|
69
|
+
``` ruby
|
|
70
|
+
# cat hello_world_test.rb
|
|
71
|
+
require "cuba/test"
|
|
72
|
+
require "./hello_world"
|
|
73
|
+
|
|
74
|
+
scope do
|
|
75
|
+
test "Homepage" do
|
|
76
|
+
get "/"
|
|
77
|
+
|
|
78
|
+
follow_redirect!
|
|
79
|
+
|
|
80
|
+
assert_equal "Hello world!", last_response.body
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
To run it, you can create a `config.ru` file:
|
|
86
|
+
|
|
87
|
+
``` ruby
|
|
88
|
+
# cat config.ru
|
|
89
|
+
require "./hello_world"
|
|
90
|
+
|
|
91
|
+
run Cuba
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
You can now run `rackup` and enjoy what you have just created.
|
|
95
|
+
|
|
96
|
+
Matchers
|
|
97
|
+
--------
|
|
98
|
+
|
|
99
|
+
Here's an example showcasing how different matchers work:
|
|
100
|
+
|
|
101
|
+
``` ruby
|
|
102
|
+
require "cuba"
|
|
103
|
+
require "cuba/safe"
|
|
104
|
+
|
|
105
|
+
Cuba.use Rack::Session::Cookie, :secret => "__a_very_long_string__"
|
|
106
|
+
|
|
107
|
+
Cuba.plugin Cuba::Safe
|
|
108
|
+
|
|
109
|
+
Cuba.define do
|
|
110
|
+
|
|
111
|
+
# only GET requests
|
|
112
|
+
on get do
|
|
113
|
+
|
|
114
|
+
# /
|
|
115
|
+
on root do
|
|
116
|
+
res.write "Home"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# /about
|
|
120
|
+
on "about" do
|
|
121
|
+
res.write "About"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# /styles/basic.css
|
|
125
|
+
on "styles", extension("css") do |file|
|
|
126
|
+
res.write "Filename: #{file}" #=> "Filename: basic"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# /post/2011/02/16/hello
|
|
130
|
+
on "post/:y/:m/:d/:slug" do |y, m, d, slug|
|
|
131
|
+
res.write "#{y}-#{m}-#{d} #{slug}" #=> "2011-02-16 hello"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# /username/foobar
|
|
135
|
+
on "username/:username" do |username|
|
|
136
|
+
user = User.find_by_username(username) # username == "foobar"
|
|
137
|
+
|
|
138
|
+
# /username/foobar/posts
|
|
139
|
+
on "posts" do
|
|
140
|
+
|
|
141
|
+
# You can access `user` here, because the `on` blocks
|
|
142
|
+
# are closures.
|
|
143
|
+
res.write "Total Posts: #{user.posts.size}" #=> "Total Posts: 6"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# /username/foobar/following
|
|
147
|
+
on "following" do
|
|
148
|
+
res.write user.following.size #=> "1301"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# /search?q=barbaz
|
|
153
|
+
on "search", param("q") do |query|
|
|
154
|
+
res.write "Searched for #{query}" #=> "Searched for barbaz"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# only POST requests
|
|
159
|
+
on post do
|
|
160
|
+
on "login" do
|
|
161
|
+
|
|
162
|
+
# POST /login, user: foo, pass: baz
|
|
163
|
+
on param("user"), param("pass") do |user, pass|
|
|
164
|
+
res.write "#{user}:#{pass}" #=> "foo:baz"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# If the params `user` and `pass` are not provided, this
|
|
168
|
+
# block will get executed.
|
|
169
|
+
on true do
|
|
170
|
+
res.write "You need to provide user and pass!"
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Note that once an `on` block matches, processing halts at the conclusion of that block.
|
|
178
|
+
|
|
179
|
+
Status codes
|
|
180
|
+
------------
|
|
181
|
+
|
|
182
|
+
If you don't assign a status code and you don't write to the `res`
|
|
183
|
+
object, the status will be set as `404`. The method `not_found` is
|
|
184
|
+
in charge of setting the proper status code, and you can redefine
|
|
185
|
+
it if you want to render a template or configure custom headers.
|
|
186
|
+
|
|
187
|
+
For example:
|
|
188
|
+
|
|
189
|
+
``` ruby
|
|
190
|
+
Cuba.define do
|
|
191
|
+
on get do
|
|
192
|
+
on "hello" do
|
|
193
|
+
res.write "hello world"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Requests:
|
|
199
|
+
#
|
|
200
|
+
# GET / # 404
|
|
201
|
+
# GET /hello # 200
|
|
202
|
+
# GET /hello/world # 200
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
As you can see, as soon as something was written to the response,
|
|
206
|
+
the status code was changed to 200.
|
|
207
|
+
|
|
208
|
+
If you want to match just "hello", but not "hello/world", you can do
|
|
209
|
+
as follows:
|
|
210
|
+
|
|
211
|
+
``` ruby
|
|
212
|
+
Cuba.define do
|
|
213
|
+
on get do
|
|
214
|
+
on "hello" do
|
|
215
|
+
on root do
|
|
216
|
+
res.write "hello world"
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Requests:
|
|
223
|
+
#
|
|
224
|
+
# GET / # 404
|
|
225
|
+
# GET /hello # 200
|
|
226
|
+
# GET /hello/world # 404
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
You can also use a regular expression to match the end of line:
|
|
230
|
+
|
|
231
|
+
``` ruby
|
|
232
|
+
Cuba.define do
|
|
233
|
+
on get do
|
|
234
|
+
on /hello\/?\z/ do
|
|
235
|
+
res.write "hello world"
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Requests:
|
|
241
|
+
#
|
|
242
|
+
# GET / # 404
|
|
243
|
+
# GET /hello # 200
|
|
244
|
+
# GET /hello/world # 404
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
This last example is not a common usage pattern. It's here only to
|
|
248
|
+
illustrate how Cuba can be adapted for different use cases.
|
|
249
|
+
|
|
250
|
+
If you need this behavior, you can create a helper:
|
|
251
|
+
|
|
252
|
+
``` ruby
|
|
253
|
+
module TerminalMatcher
|
|
254
|
+
def terminal(path)
|
|
255
|
+
/#{path}\/?\z/
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
Cuba.plugin TerminalMatcher
|
|
260
|
+
|
|
261
|
+
Cuba.define do
|
|
262
|
+
on get do
|
|
263
|
+
on terminal("hello") do
|
|
264
|
+
res.write "hello world"
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Security
|
|
271
|
+
--------
|
|
272
|
+
|
|
273
|
+
The most important security consideration is to use `https` for all
|
|
274
|
+
requests. If that's not the case, any attempt to secure the application
|
|
275
|
+
could be in vain. The rest of this section assumes `https` is
|
|
276
|
+
enforced.
|
|
277
|
+
|
|
278
|
+
When building a web application, you need to include a security
|
|
279
|
+
layer. Cuba ships with the `Cuba::Safe` plugin, which applies several
|
|
280
|
+
security related headers to prevent attacks like clickjacking and
|
|
281
|
+
cross-site scripting, among others. It is not included by default
|
|
282
|
+
because there are legitimate uses for plain Cuba (for instance,
|
|
283
|
+
when designing an API).
|
|
284
|
+
|
|
285
|
+
Here's how to include it:
|
|
286
|
+
|
|
287
|
+
```ruby
|
|
288
|
+
require "cuba/safe"
|
|
289
|
+
|
|
290
|
+
Cuba.plugin Cuba::Safe
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
You should also always set a session secret to some undisclosed
|
|
294
|
+
value. Keep in mind that the content in the session cookie is
|
|
295
|
+
*not* encrypted.
|
|
296
|
+
|
|
297
|
+
``` ruby
|
|
298
|
+
Cuba.use(Rack::Session::Cookie, :secret => "__a_very_long_string__")
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
In the end, your application should look like this:
|
|
302
|
+
|
|
303
|
+
```ruby
|
|
304
|
+
require "cuba"
|
|
305
|
+
require "cuba/safe"
|
|
306
|
+
|
|
307
|
+
Cuba.use Rack::Session::Cookie, :secret => "__a_very_long_string__"
|
|
308
|
+
|
|
309
|
+
Cuba.plugin Cuba::Safe
|
|
310
|
+
|
|
311
|
+
Cuba.define do
|
|
312
|
+
on csrf.unsafe? do
|
|
313
|
+
csrf.reset!
|
|
314
|
+
|
|
315
|
+
res.status = 403
|
|
316
|
+
res.write("Not authorized")
|
|
317
|
+
|
|
318
|
+
halt(res.finish)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Now your app is protected against a wide range of attacks.
|
|
322
|
+
...
|
|
323
|
+
end
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The `Cuba::Safe` plugin is composed of two modules:
|
|
327
|
+
|
|
328
|
+
* `Cuba::Safe::SecureHeaders`
|
|
329
|
+
* `Cuba::Safe::CSRF`
|
|
330
|
+
|
|
331
|
+
You can include them individually, but while the modularity is good
|
|
332
|
+
for development, it's very common to use them in tandem. As that's
|
|
333
|
+
the normal use case, including `Cuba::Safe` is the preferred way.
|
|
334
|
+
|
|
335
|
+
Cross-Site Request Forgery
|
|
336
|
+
--------------------------
|
|
337
|
+
|
|
338
|
+
The `Cuba::Safe::CSRF` plugin provides a `csrf` object with the
|
|
339
|
+
following methods:
|
|
340
|
+
|
|
341
|
+
* `token`: the current security token.
|
|
342
|
+
* `reset!`: forces the token to be recreated.
|
|
343
|
+
* `safe?`: returns `true` if the request is safe.
|
|
344
|
+
* `unsafe?`: returns `true` if the request is unsafe.
|
|
345
|
+
* `form_tag`: returns a string with the `csrf_token` hidden input tag.
|
|
346
|
+
* `meta_tag`: returns a string with the `csrf_token` meta tag.
|
|
347
|
+
|
|
348
|
+
Here's an example of how to use it:
|
|
349
|
+
|
|
350
|
+
```ruby
|
|
351
|
+
require "cuba"
|
|
352
|
+
require "cuba/safe"
|
|
353
|
+
|
|
354
|
+
Cuba.use Rack::Session::Cookie, :secret => "__a_very_long_string__"
|
|
355
|
+
|
|
356
|
+
Cuba.plugin Cuba::Safe
|
|
357
|
+
|
|
358
|
+
Cuba.define do
|
|
359
|
+
on csrf.unsafe? do
|
|
360
|
+
csrf.reset!
|
|
361
|
+
|
|
362
|
+
res.status = 403
|
|
363
|
+
res.write("Forbidden")
|
|
364
|
+
|
|
365
|
+
halt(res.finish)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Here comes the rest of your application
|
|
369
|
+
# ...
|
|
370
|
+
end
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
You have to include `csrf.form_tag` in your forms and `csrf.meta_tag`
|
|
374
|
+
among your meta tags. Here's an example that assumes you are using
|
|
375
|
+
`Cuba::Mote` from `cuba-contrib`:
|
|
376
|
+
|
|
377
|
+
```html
|
|
378
|
+
<!DOCTYPE html>
|
|
379
|
+
<html>
|
|
380
|
+
<head>
|
|
381
|
+
{{ app.csrf.meta_tag }}
|
|
382
|
+
...
|
|
383
|
+
</head>
|
|
384
|
+
...
|
|
385
|
+
<body>
|
|
386
|
+
<form action="/foo" method="POST">
|
|
387
|
+
{{ app.csrf.form_tag }}
|
|
388
|
+
...
|
|
389
|
+
</form>
|
|
390
|
+
...
|
|
391
|
+
</body>
|
|
392
|
+
</html>
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
HTTP Verbs
|
|
396
|
+
----------
|
|
397
|
+
|
|
398
|
+
There are matchers defined for the following HTTP Verbs: `get`,
|
|
399
|
+
`post`, `put`, `patch`, `delete`, `head`, `options`, `link`, `unlink`
|
|
400
|
+
and `trace`. As you have the whole request available via the `req`
|
|
401
|
+
object, you can also query it with helper methods like `req.options?`
|
|
402
|
+
or `req.head?`, or you can even go to a lower level and inspect the
|
|
403
|
+
environment via the `env` object, and check for example if
|
|
404
|
+
`env["REQUEST_METHOD"]` equals the verb `PATCH`.
|
|
405
|
+
|
|
406
|
+
What follows is an example of different ways of saying the same thing:
|
|
407
|
+
|
|
408
|
+
``` ruby
|
|
409
|
+
on env["REQUEST_METHOD"] == "GET", "api" do ... end
|
|
410
|
+
|
|
411
|
+
on req.get?, "api" do ... end
|
|
412
|
+
|
|
413
|
+
on get, "api" do ... end
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Actually, `get` is syntax sugar for `req.get?`, which in turn is syntax sugar
|
|
417
|
+
for `env["REQUEST_METHOD"] == "GET"`.
|
|
418
|
+
|
|
419
|
+
Headers
|
|
420
|
+
-------
|
|
421
|
+
|
|
422
|
+
You can set the headers by assigning values to the hash `req.headers`.
|
|
423
|
+
If you want to inspect the incoming headers, you have to read from
|
|
424
|
+
the `env` hash. For example, if you want to know the referrer you
|
|
425
|
+
can check `env["HTTP_REFERER"]`.
|
|
426
|
+
|
|
427
|
+
Request and Response
|
|
428
|
+
--------------------
|
|
429
|
+
|
|
430
|
+
You may have noticed we use `req` and `res` a lot. Those variables are
|
|
431
|
+
instances of [Rack::Request][request] and `Cuba::Response` respectively, and
|
|
432
|
+
`Cuba::Response` is just an optimized version of
|
|
433
|
+
[Rack::Response][response].
|
|
434
|
+
|
|
435
|
+
[request]: http://www.rubydoc.info/github/rack/rack/Rack/Request
|
|
436
|
+
[response]: http://www.rubydoc.info/github/rack/rack/Rack/Response
|
|
437
|
+
|
|
438
|
+
Those objects are helpers for accessing the request and for building
|
|
439
|
+
the response. Most of the time, you will just use `res.write`.
|
|
440
|
+
|
|
441
|
+
If you want to use custom `Request` or `Response` objects, you can
|
|
442
|
+
set the new values as follows:
|
|
443
|
+
|
|
444
|
+
``` ruby
|
|
445
|
+
Cuba.settings[:req] = MyRequest
|
|
446
|
+
Cuba.settings[:res] = MyResponse
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Make sure to provide classes compatible with those from Rack.
|
|
450
|
+
|
|
451
|
+
Captures
|
|
452
|
+
--------
|
|
453
|
+
|
|
454
|
+
You may have noticed that some matchers yield a value to the block. The rules
|
|
455
|
+
for determining if a matcher will yield a value are simple:
|
|
456
|
+
|
|
457
|
+
1. Regex captures: `"posts/(\\d+)-(.*)"` will yield two values, corresponding to each capture.
|
|
458
|
+
2. Placeholders: `"users/:id"` will yield the value in the position of :id.
|
|
459
|
+
3. Symbols: `:foobar` will yield if a segment is available.
|
|
460
|
+
4. File extensions: `extension("css")` will yield the basename of the matched file.
|
|
461
|
+
5. Parameters: `param("user")` will yield the value of the parameter user, if present.
|
|
462
|
+
|
|
463
|
+
The first case is important because it shows the underlying effect of regex
|
|
464
|
+
captures.
|
|
465
|
+
|
|
466
|
+
In the second case, the substring `:id` gets replaced by `([^\\/]+)` and the
|
|
467
|
+
string becomes `"users/([^\\/]+)"` before performing the match, thus it reverts
|
|
468
|
+
to the first form we saw.
|
|
469
|
+
|
|
470
|
+
In the third case, the symbol--no matter what it says--gets replaced
|
|
471
|
+
by `"([^\\/]+)"`, and again we are in presence of case 1.
|
|
472
|
+
|
|
473
|
+
The fourth case, again, reverts to the basic matcher: it generates the string
|
|
474
|
+
`"([^\\/]+?)\.#{ext}\\z"` before performing the match.
|
|
475
|
+
|
|
476
|
+
The fifth case is different: it checks if the the parameter supplied is present
|
|
477
|
+
in the request (via POST or QUERY_STRING) and it pushes the value as a capture.
|
|
478
|
+
|
|
479
|
+
Composition
|
|
480
|
+
-----------
|
|
481
|
+
|
|
482
|
+
You can mount a Cuba app, along with middlewares, inside another Cuba app:
|
|
483
|
+
|
|
484
|
+
``` ruby
|
|
485
|
+
class API < Cuba; end
|
|
486
|
+
|
|
487
|
+
API.use SomeMiddleware
|
|
488
|
+
|
|
489
|
+
API.define do
|
|
490
|
+
on param("url") do |url|
|
|
491
|
+
...
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
Cuba.define do
|
|
496
|
+
on "api" do
|
|
497
|
+
run API
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
If you need to pass information to one sub-app, you can use the
|
|
503
|
+
`with` method and access it with `vars`:
|
|
504
|
+
|
|
505
|
+
```ruby
|
|
506
|
+
class Platforms < Cuba
|
|
507
|
+
define do
|
|
508
|
+
platform = vars[:platform]
|
|
509
|
+
|
|
510
|
+
on default do
|
|
511
|
+
res.write(platform) # => "heroku" or "salesforce"
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
Cuba.define do
|
|
517
|
+
on "(heroku|salesforce)" do |platform|
|
|
518
|
+
with(platform: platform) do
|
|
519
|
+
run(Platforms)
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
## Embedding routes from other modules
|
|
526
|
+
|
|
527
|
+
While the `run` command allows you to handle over the control to a
|
|
528
|
+
sub app, sometimes you may want to just embed routes defined in
|
|
529
|
+
another module. There's no built-in method to do it, but if you are
|
|
530
|
+
willing to experiment you can try the following.
|
|
531
|
+
|
|
532
|
+
Let's say you have defined routes in modules `A` and `B`, and you
|
|
533
|
+
want to mount those routes in your application.
|
|
534
|
+
|
|
535
|
+
First, you will have to extend Cuba with this code:
|
|
536
|
+
|
|
537
|
+
```ruby
|
|
538
|
+
class Cuba
|
|
539
|
+
def mount(app)
|
|
540
|
+
result = app.call(req.env)
|
|
541
|
+
halt result if result[0] != 404
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
It doesn't matter where you define it as long as Cuba has already
|
|
547
|
+
been required. For instance, you could extract that to a plugin and
|
|
548
|
+
it would work just fine.
|
|
549
|
+
|
|
550
|
+
Then, in your application, you can use it like this:
|
|
551
|
+
|
|
552
|
+
```ruby
|
|
553
|
+
Cuba.define do
|
|
554
|
+
on default do
|
|
555
|
+
mount A
|
|
556
|
+
mount B
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
It should halt the request only if the resulting status from calling
|
|
562
|
+
the mounted app is not 404. If you run into some unexpected behavior,
|
|
563
|
+
let me know by creating an issue and we'll look at how to workaround
|
|
564
|
+
any difficulties.
|
|
565
|
+
|
|
566
|
+
Testing
|
|
567
|
+
-------
|
|
568
|
+
|
|
569
|
+
Given that Cuba is essentially Rack, it is very easy to test with
|
|
570
|
+
`Rack::Test`, `Webrat` or `Capybara`. Cuba's own tests are written
|
|
571
|
+
with a combination of [Cutest][cutest] and [Rack::Test][rack-test],
|
|
572
|
+
and if you want to use the same for your tests it is as easy as
|
|
573
|
+
requiring `cuba/test`:
|
|
574
|
+
|
|
575
|
+
``` ruby
|
|
576
|
+
require "cuba/test"
|
|
577
|
+
require "your/app"
|
|
578
|
+
|
|
579
|
+
scope do
|
|
580
|
+
test "Homepage" do
|
|
581
|
+
get "/"
|
|
582
|
+
|
|
583
|
+
assert_equal "Hello world!", last_response.body
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
If you prefer to use [Capybara][capybara], instead of requiring
|
|
589
|
+
`cuba/test` you can require `cuba/capybara`:
|
|
590
|
+
|
|
591
|
+
``` ruby
|
|
592
|
+
require "cuba/capybara"
|
|
593
|
+
require "your/app"
|
|
594
|
+
|
|
595
|
+
scope do
|
|
596
|
+
test "Homepage" do
|
|
597
|
+
visit "/"
|
|
598
|
+
|
|
599
|
+
assert has_content?("Hello world!")
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
To read more about testing, check the documentation for
|
|
605
|
+
[Cutest][cutest], [Rack::Test][rack-test] and [Capybara][capybara].
|
|
606
|
+
|
|
607
|
+
Settings
|
|
608
|
+
--------
|
|
609
|
+
|
|
610
|
+
Each Cuba app can store settings in the `Cuba.settings` hash. The settings are
|
|
611
|
+
inherited if you happen to subclass `Cuba`
|
|
612
|
+
|
|
613
|
+
``` ruby
|
|
614
|
+
Cuba.settings[:layout] = "guest"
|
|
615
|
+
|
|
616
|
+
class Users < Cuba; end
|
|
617
|
+
class Admin < Cuba; end
|
|
618
|
+
|
|
619
|
+
Admin.settings[:layout] = "admin"
|
|
620
|
+
|
|
621
|
+
assert_equal "guest", Users.settings[:layout]
|
|
622
|
+
assert_equal "admin", Admin.settings[:layout]
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Feel free to store whatever you find convenient.
|
|
626
|
+
|
|
627
|
+
Rendering
|
|
628
|
+
---------
|
|
629
|
+
|
|
630
|
+
Cuba includes a plugin called `Cuba::Render` that provides a couple of helper
|
|
631
|
+
methods for rendering templates. This plugin uses [Tilt][tilt], which serves as
|
|
632
|
+
an interface to a bunch of different Ruby template engines (ERB, Haml, Sass,
|
|
633
|
+
CoffeeScript, etc.), so you can use the template engine of your choice.
|
|
634
|
+
|
|
635
|
+
To set up `Cuba::Render`, do:
|
|
636
|
+
|
|
637
|
+
```ruby
|
|
638
|
+
require "cuba"
|
|
639
|
+
require "cuba/render"
|
|
640
|
+
require "erb"
|
|
641
|
+
|
|
642
|
+
Cuba.plugin Cuba::Render
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
This example uses ERB, a template engine that comes with Ruby. If you want to
|
|
646
|
+
use another template engine, one [supported by Tilt][templates], you need to
|
|
647
|
+
install the required gem and change the `template_engine` setting as shown
|
|
648
|
+
below.
|
|
649
|
+
|
|
650
|
+
```ruby
|
|
651
|
+
Cuba.settings[:render][:template_engine] = "haml"
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
The plugin provides three helper methods for rendering templates: `partial`,
|
|
655
|
+
`view` and `render`.
|
|
656
|
+
|
|
657
|
+
```ruby
|
|
658
|
+
Cuba.define do
|
|
659
|
+
on "about" do
|
|
660
|
+
# `partial` renders a template called `about.erb` without a layout.
|
|
661
|
+
res.write partial("about")
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
on "home" do
|
|
665
|
+
# Opposed to `partial`, `view` renders the same template
|
|
666
|
+
# within a layout called `layout.erb`.
|
|
667
|
+
res.write view("about")
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
on "contact" do
|
|
671
|
+
# `render` is a shortcut to `res.write view(...)`
|
|
672
|
+
render("contact")
|
|
673
|
+
end
|
|
674
|
+
end
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
By default, `Cuba::Render` assumes that all templates are placed in a folder
|
|
678
|
+
named `views` and that they use the proper extension for the chosen template
|
|
679
|
+
engine. Also for the `view` and `render` methods, it assumes that the layout
|
|
680
|
+
template is called `layout`.
|
|
681
|
+
|
|
682
|
+
The defaults can be changed through the `Cuba.settings` method:
|
|
683
|
+
|
|
684
|
+
```ruby
|
|
685
|
+
Cuba.settings[:render][:template_engine] = "haml"
|
|
686
|
+
Cuba.settings[:render][:views] = "./views/admin/"
|
|
687
|
+
Cuba.settings[:render][:layout] = "admin"
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
NOTE: Cuba doesn't ship with Tilt. You need to install it (`gem install tilt`).
|
|
691
|
+
|
|
692
|
+
[templates]: https://github.com/rtomayko/tilt/blob/master/docs/TEMPLATES.md
|
|
693
|
+
|
|
694
|
+
Plugins
|
|
695
|
+
-------
|
|
696
|
+
|
|
697
|
+
Cuba provides a way to extend its functionality with plugins.
|
|
698
|
+
|
|
699
|
+
### How to create plugins
|
|
700
|
+
|
|
701
|
+
Authoring your own plugins is pretty straightforward.
|
|
702
|
+
|
|
703
|
+
``` ruby
|
|
704
|
+
module MyOwnHelper
|
|
705
|
+
def markdown(str)
|
|
706
|
+
BlueCloth.new(str).to_html
|
|
707
|
+
end
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
Cuba.plugin MyOwnHelper
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
That's the simplest kind of plugin you'll write. In fact, that's exactly how
|
|
714
|
+
the `markdown` helper is written in `Cuba::TextHelpers`.
|
|
715
|
+
|
|
716
|
+
A more complicated plugin can make use of `Cuba.settings` to provide default
|
|
717
|
+
values. In the following example, note that if the module has a `setup` method, it will
|
|
718
|
+
be called as soon as it is included:
|
|
719
|
+
|
|
720
|
+
``` ruby
|
|
721
|
+
module Render
|
|
722
|
+
def self.setup(app)
|
|
723
|
+
app.settings[:template_engine] = "erb"
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
def partial(template, locals = {})
|
|
727
|
+
render("#{template}.#{settings[:template_engine]}", locals)
|
|
728
|
+
end
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
Cuba.plugin Render
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
This sample plugin actually resembles how `Cuba::Render` works.
|
|
735
|
+
|
|
736
|
+
Finally, if a module called `ClassMethods` is present, `Cuba` will be extended
|
|
737
|
+
with it.
|
|
738
|
+
|
|
739
|
+
``` ruby
|
|
740
|
+
module GetSetter
|
|
741
|
+
module ClassMethods
|
|
742
|
+
def set(key, value)
|
|
743
|
+
settings[key] = value
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
def get(key)
|
|
747
|
+
settings[key]
|
|
748
|
+
end
|
|
749
|
+
end
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
Cuba.plugin GetSetter
|
|
753
|
+
|
|
754
|
+
Cuba.set(:foo, "bar")
|
|
755
|
+
|
|
756
|
+
assert_equal "bar", Cuba.get(:foo)
|
|
757
|
+
assert_equal "bar", Cuba.settings[:foo]
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
Contributing
|
|
761
|
+
------------
|
|
762
|
+
|
|
763
|
+
A good first step is to meet us on IRC and discuss ideas. If that's
|
|
764
|
+
not possible, you can create an issue explaining the proposed change
|
|
765
|
+
and a use case. We pay a lot of attention to use cases, because our
|
|
766
|
+
goal is to keep the code base simple. In many cases, the result of
|
|
767
|
+
a conversation will be the creation of another tool, instead of the
|
|
768
|
+
modification of Cuba itself.
|
|
769
|
+
|
|
770
|
+
If you want to test Cuba, you may want to use a gemset to isolate
|
|
771
|
+
the requirements. We recommend the use of tools like [dep][dep] and
|
|
772
|
+
[gs][gs], but you can use similar tools like [gst][gst] or [bs][bs].
|
|
773
|
+
|
|
774
|
+
The required gems for testing and development are listed in the
|
|
775
|
+
`.gems` file. If you are using [dep][dep], you can create a gemset
|
|
776
|
+
and run `dep install`.
|
|
777
|
+
|
|
778
|
+
[dep]: http://cyx.github.io/dep/
|
|
779
|
+
[gs]: http://soveran.github.io/gs/
|
|
780
|
+
[gst]: https://github.com/tonchis/gst
|
|
781
|
+
[bs]: https://github.com/educabilia/bs
|