roda-cj 0.9.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,5 +1,21 @@
1
1
  = HEAD
2
2
 
3
+ * Support adding middleware after the route block has been added (jeremyevans)
4
+
5
+ * Allow Roda subclasses to use route block from superclass (jeremyevans)
6
+
7
+ * Have r.multi_route ignore non-String named routes (jeremyevans)
8
+
9
+ * Pick up newly added named routes while running in the multi_route plugin, useful for development (jeremyevans)
10
+
11
+ * Add path plugin, for named path support (jeremyevans) (#4)
12
+
13
+ * Add error_email plugin, for easily emailing an error notification for an exception (jeremyevans)
14
+
15
+ = 1.0.0 (2014-08-19)
16
+
17
+ * Don't have :extension hash matcher force a terminal match (jeremyevans)
18
+
3
19
  * Add :content option to view method in render plugin to use given content instead of rendering a template (jeremyevans)
4
20
 
5
21
  * Add :escape option to render plugin for using erb templates where <%= %> escapes and <%== %> does not (jeremyevans)
@@ -35,25 +35,30 @@ Here's a simple application, showing how the routing tree works:
35
35
  use Rack::Session::Cookie, :secret => ENV['SECRET']
36
36
 
37
37
  route do |r|
38
- # matches any GET request
39
- r.get do
38
+ # GET / request
39
+ r.root do
40
+ r.redirect "/hello"
41
+ end
40
42
 
41
- # matches GET /
42
- r.root do
43
- r.redirect "/hello"
44
- end
43
+ # /hello branch
44
+ r.on "hello" do
45
45
 
46
- # matches GET /hello or GET /hello/.*
47
- r.on "hello" do
46
+ # GET /hello/world request
47
+ r.get "world" do
48
+ "Hello world!"
49
+ end
48
50
 
49
- # matches GET /hello/world
50
- r.is "world" do
51
- "Hello world!"
51
+ # /hello request
52
+ r.is do
53
+ # GET /hello request
54
+ r.get do
55
+ "Hello!"
52
56
  end
53
57
 
54
- # matches GET /hello
55
- r.is do
56
- "Hello!"
58
+ # POST /hello request
59
+ r.post do
60
+ puts "Someone said hello!"
61
+ r.redirect
57
62
  end
58
63
  end
59
64
  end
@@ -62,8 +67,6 @@ Here's a simple application, showing how the routing tree works:
62
67
 
63
68
  run App.app
64
69
 
65
- You can now run +rackup+ and enjoy what you have just created.
66
-
67
70
  Here's a breakdown of what is going on in the above block:
68
71
 
69
72
  After requiring the library and subclassing Roda, the +use+ method
@@ -76,34 +79,120 @@ with some additional methods for matching routes. By
76
79
  convention, this argument should be named +r+.
77
80
 
78
81
  The primary way routes are matched in Roda is by calling
79
- +r.on+, or a method like +r.get+ or +r.is+ which calls +r.on+.
80
- +r.on+ takes each of the arguments given and tries to match them to
81
- the current request. If it is able to successfully match
82
- all of the arguments, it yields to the +r.on+ block, otherwise
83
- it returns immediately.
84
-
85
- +r.get+ is a shortcut that matches any GET request, and
86
- +r.is+ is a shortcut that ensures the the exact route is
87
- matched and there are no further entries in the path.
88
-
89
- If +r.on+ matches and control is yielded to the block, whenever
90
- the block returns, the response will be returned. If the block
91
- returns a string and the response body hasn't already been
92
- written to, the block return value will interpreted as the body
93
- for the response. If none of the +r.on+ blocks match and the
94
- route block returns a string, it will be interpreted as the body
95
- for the response.
82
+ +r.on+, +r.is+, +r.root+, +r.get+, or +r.post+. These methods are
83
+ calling the routing methods, and each of them takes a block. The
84
+ block is referred to as a match block.
85
+
86
+ Each routing method takes each of the arguments (called matchers)
87
+ given and tries to match them to the current request. If it is
88
+ able to match all of the arguments, it yields to the match block,
89
+ otherwise the block is skipped and execution continues.
90
+
91
+ +r.on+ matches if all of the arguments match.
92
+ +r.is+ matches if all of the arguments match, and there are no
93
+ further entries in the path after matching.
94
+ +r.get+ when called without arguments matches any +GET+ request.
95
+ +r.get+ when called with any arguments matches only if the
96
+ current request is a +GET+ request and there are no further entries
97
+ in the path after matching.
98
+ +r.root+ only matches a +GET+ request where the current path is +/+.
99
+
100
+ If a routing method matches and control is yielded to the match
101
+ block, whenever the match block returns, Roda will return the
102
+ rack response array of status, headers, and body, to the caller.
103
+
104
+ If the match block returns a string and the response body hasn't
105
+ already been written to, the block return value will interpreted
106
+ as the body for the response. If none of the routing methods match
107
+ and the route block returns a string, it will be interpreted as the
108
+ body for the response.
96
109
 
97
110
  +r.redirect+ immediately returns the response, allowing for
98
- code such as <tt>r.redirect(path) if some_condition</tt>.
111
+ code such as <tt>r.redirect(path) if some_condition</tt>. If
112
+ called without arguments, it redirects to the current path if
113
+ the current request method is not +GET+.
99
114
 
100
115
  The +.app+ at the end is an optimization, which you can leave
101
116
  off, but which saves a few methods call for every response.
102
117
 
118
+ == The Routing Tree
119
+
120
+ Roda is called a routing tree web framework because the way most
121
+ sites are structured, routing takes the form of a tree based on the
122
+ URL structure of the site. In general, +r.on+ is used to split the
123
+ tree into different branches, and +r.is+ is finalizes the routing,
124
+ where the request is actually handled.
125
+
126
+ So a simple routing tree may look something like this:
127
+
128
+ r.on "a" do # /a branch
129
+ r.on "b" do # /a/b branch
130
+ r.is "c" do # /a/b/c request
131
+ r.get do end # GET /a/b/c request
132
+ r.post do end # POST /a/b/c request
133
+ end
134
+ r.get "d" do end # GET /a/b/d request
135
+ r.post "e" do end # POST /a/b/e request
136
+ end
137
+ end
138
+
139
+ It's also possible to handle the same requests, but structure the
140
+ routing tree by first branching on the request method:
141
+
142
+ r.get do # GET
143
+ r.on "a" do # GET /a branch
144
+ r.on "b" do # GET /a/b branch
145
+ r.is "c" do end # GET /a/b/c request
146
+ r.is "d" do end # GET /a/b/d request
147
+ end
148
+ end
149
+ end
150
+
151
+ r.post do # POST
152
+ r.on "a" do # POST /a branch
153
+ r.on "b" do # POST /a/b branch
154
+ r.is "c" do end # POST /a/b/c request
155
+ r.is "e" do end # POST /a/b/e request
156
+ end
157
+ end
158
+ end
159
+
160
+ This allows you to easily separate your +GET+ request handling from
161
+ your +POST+ request handling. If you only have a small number of
162
+ +POST+ request URLs and a large number of +GET+ request URLs, this
163
+ may make things easier.
164
+
165
+ However, in general routing first by the path and last by the
166
+ request method is likely to lead to simpler and DRYer code. This
167
+ is because at any point during the routing, you can act on the
168
+ request. For example, if all requests in the +/a+ branch need
169
+ need access permission +A+ and all requests in the +/a/b+ branch
170
+ need access permission +B+, you can easily handle this in the
171
+ routing tree:
172
+
173
+ r.on "a" do # /a branch
174
+ check_perm(:A)
175
+ r.on "b" do # /a/b branch
176
+ check_perm(:B)
177
+ r.is "c" do # /a/b/c request
178
+ r.get do end # GET /a/b/c request
179
+ r.post do end # POST /a/b/c request
180
+ end
181
+ r.get "d" do end # GET /a/b/d request
182
+ r.post "e" do end # POST /a/b/e request
183
+ end
184
+ end
185
+
186
+ Being able to operate on the request at any point during the
187
+ the routing is one of the major advantages of Roda compared
188
+ to other web frameworks that do not use a routing tree.
189
+
103
190
  == Matchers
104
191
 
105
- Here's an example showcasing how different matchers work. Matchers
106
- are arguments passed to +r.on+.
192
+ Other than +r.root+, the routing methods all take arguments called
193
+ matchers. If all of the matchers match, the routing method yields to
194
+ the match block. Here's an example showcasing how different
195
+ matchers work:
107
196
 
108
197
  class App < Roda
109
198
  route do |r|
@@ -136,7 +225,6 @@ are arguments passed to +r.on+.
136
225
 
137
226
  # /username/foobar/posts
138
227
  r.is "posts" do
139
-
140
228
  # You can access user here, because the blocks are closures.
141
229
  "Total Posts: #{user.posts.size}" #=> "Total Posts: 6"
142
230
  end
@@ -158,7 +246,7 @@ are arguments passed to +r.on+.
158
246
  r.is "login" do
159
247
 
160
248
  # POST /login, user: foo, pass: baz
161
- r.on {:param=>"user"}, {:param=>"pass"} do |user, pass|
249
+ r.on({:param=>"user"}, {:param=>"pass"}) do |user, pass|
162
250
  "#{user}:#{pass}" #=> "foo:baz"
163
251
  end
164
252
 
@@ -180,75 +268,75 @@ The +/+ here is considered the empty segment.
180
268
  If it does not contain a colon or slash, it matches single segment
181
269
  with the text of the string, preceeded by a slash.
182
270
 
183
- "" matches "/"
184
- "foo" matches "/foo"
185
- "foo" does not match "/food"
271
+ "" # matches "/"
272
+ "foo" # matches "/foo"
273
+ "foo" # does not match "/food"
186
274
 
187
275
  If it contains any slashes, it matches one additional segment for
188
276
  each slash:
189
277
 
190
- "foo/bar" matches "/foo/bar"
191
- "foo/bar" does not match "/foo/bard"
278
+ "foo/bar" # matches "/foo/bar"
279
+ "foo/bar" # does not match "/foo/bard"
192
280
 
193
281
  If it contains a colon followed by any <tt>\\w</tt> characters, the colon and
194
282
  remaing <tt>\\w</tt> characters matches any nonempty segment that contains at
195
283
  least one character:
196
284
 
197
- "foo/:id" matches "/foo/bar", "/foo/baz", etc.
198
- "foo/:id" does not match "/fo/bar"
285
+ "foo/:id" # matches "/foo/bar", "/foo/baz", etc.
286
+ "foo/:id" # does not match "/fo/bar"
199
287
 
200
288
  You can use multiple colons in a string:
201
289
 
202
- ":x/:y" matches "/foo/bar", "/bar/foo" etc.
203
- ":x/:y" does not match "/foo", "/bar/"
290
+ ":x/:y" # matches "/foo/bar", "/bar/foo" etc.
291
+ ":x/:y" # does not match "/foo", "/bar/"
204
292
 
205
293
  You can prefix colons:
206
294
 
207
- "foo:x/bar:y" matches "/food/bard", "/fool/bart", etc.
208
- "foo:x/bar:y" does not match "/foo/bart", "/fool/bar", etc.
295
+ "foo:x/bar:y" # matches "/food/bard", "/fool/bart", etc.
296
+ "foo:x/bar:y" # does not match "/foo/bart", "/fool/bar", etc.
209
297
 
210
298
  If any colons are used, the block will yield one argument for
211
299
  each segment matched containing the matched text. So:
212
300
 
213
- "foo:x/:y" matching "/fool/bar" yields "l", "bar"
301
+ "foo:x/:y" # matching "/fool/bar" yields "l", "bar"
214
302
 
215
303
  Colons that are not followed by a <tt>\\w</tt> character are matched literally:
216
304
 
217
- ":/a" matches "/:/a"
305
+ ":/a" # matches "/:/a"
218
306
 
219
307
  Note that strings are regexp escaped before being used in a regular
220
308
  expression, so:
221
309
 
222
- "\\d+(/\\w+)?" matches "\d+(/\w+)?"
223
- "\\d+/\\w+" does not match "123/abc"
310
+ "\\d+(/\\w+)?" # matches "/\d+(/\w+)?"
311
+ "\\d+(/\\w+)?" # does not match "/123/abc"
224
312
 
225
313
  === Regexp
226
314
 
227
315
  Regexps match one or more segments by looking for the pattern preceeded by a
228
316
  slash:
229
317
 
230
- /foo\w+/ matches "/foobar"
231
- /foo\w+/ does not match "/foo/bar"
318
+ /foo\w+/ # matches "/foobar"
319
+ /foo\w+/ # does not match "/foo/bar"
232
320
 
233
321
  If any patterns are captured by the regexp, they are yielded:
234
322
 
235
- /foo\w+/ matches "/foobar", yields nothing
236
- /foo(\w+)/ matches "/foobar", yields "bar"
323
+ /foo\w+/ # matches "/foobar", yields nothing
324
+ /foo(\w+)/ # matches "/foobar", yields "bar"
237
325
 
238
326
  === Symbol
239
327
 
240
328
  Symbols match any nonempty segment, yielding the segment except for the
241
329
  preceeding slash:
242
330
 
243
- :id matches "/foo" yields "foo"
244
- :id does not match "/"
331
+ :id # matches "/foo" yields "foo"
332
+ :id # does not match "/"
245
333
 
246
334
  === Proc
247
335
 
248
336
  Procs match unless they return false or nil:
249
337
 
250
- proc{true} matches anything
251
- proc{false} does not match anything
338
+ proc{true} # matches anything
339
+ proc{false} # does not match anything
252
340
 
253
341
  Procs don't capture anything by default, but they can if you add
254
342
  the captured text to +r.captures+.
@@ -263,8 +351,8 @@ condition). Evaluation stops at the first matcher that matches.
263
351
  Additionally, if the matched object is a String, the string is yielded.
264
352
  This makes it easy to handle multiple strings without a Regexp:
265
353
 
266
- %w'page1 page2' matches "/page1", "/page2"
267
- [] does not match anything
354
+ ['page1', 'page2'] # matches "/page1", "/page2"
355
+ [] # does not match anything
268
356
 
269
357
  === Hash
270
358
 
@@ -276,12 +364,12 @@ block will be called with the value of the hash.
276
364
 
277
365
  class App < Roda
278
366
  hash_matcher(:foo) do |v|
279
- ...
367
+ # ...
280
368
  end
281
369
 
282
370
  route do |r|
283
371
  r.on :foo=>'bar' do
284
- ...
372
+ # ...
285
373
  end
286
374
  end
287
375
  end
@@ -291,13 +379,13 @@ block will be called with the value of the hash.
291
379
  The :all matcher matches if all of the entries in the given array matches. So
292
380
 
293
381
  r.on :all=>[:a, :b] do
294
- ...
382
+ # ...
295
383
  end
296
384
 
297
385
  is the same as:
298
386
 
299
387
  r.on :a, :b do
300
- ...
388
+ # ...
301
389
  end
302
390
 
303
391
  The reason it also exists as a separate hash matcher is so you can use it inside
@@ -312,34 +400,32 @@ Would match +/foo+ and +/foos/10+, but not +/foos+.
312
400
 
313
401
  The :extension matcher matches any nonempty path ending with the given extension:
314
402
 
315
- :extension => "css" matches "/foo.css", "/bar.css"
316
- :extension => "css" does not match "/foo.css/x", "/foo.bar", "/.css"
403
+ {:extension => "css"} # matches "/foo.css", "/bar.css"
404
+ {:extension => "css"} # does not match "/foo.css/x", "/foo.bar", "/.css"
317
405
 
318
- This matcher yields the part before the extension. Note that unlike other
319
- matchers, this matcher assumes terminal behavior, it doesn't match if there
320
- are additional segments.
406
+ This matcher yields the part before the extension.
321
407
 
322
408
  ==== :method
323
409
 
324
410
  This matches the method of the request. You can provide an array to specify multiple
325
411
  request methods and match on any of them:
326
412
 
327
- :method => :post matches POST
328
- :method => %w'post patch' matches POST and PATCH
413
+ {:method => :post} # matches POST
414
+ {:method => ['post', 'patch']} # matches POST and PATCH
329
415
 
330
416
  ==== :param
331
417
 
332
418
  The :param matcher matches if the given parameter is present, even if empty.
333
419
 
334
- :param => "user" matches "/foo?user=bar", "/foo?user="
335
- :param => "user" does not matches "/foo"
420
+ {:param => "user"} # matches "/foo?user=bar", "/foo?user="
421
+ {:param => "user"} # does not matches "/foo"
336
422
 
337
423
  ==== :param!
338
424
 
339
425
  The :param! matcher matches if the given parameter is present and not empty.
340
426
 
341
- :param! => "user" matches "/foo?user=bar"
342
- :param! => "user" does not matches "/foo", "/foo?user="
427
+ {:param! => "user"} # matches "/foo?user=bar"
428
+ {:param! => "user"} # does not matches "/foo", "/foo?user="
343
429
 
344
430
  === false, nil
345
431
 
@@ -361,10 +447,8 @@ You can always set the status code manually via the status attribute
361
447
  for the response.
362
448
 
363
449
  route do |r|
364
- r.get do
365
- r.is "hello" do
366
- response.status = 200
367
- end
450
+ r.get "hello" do
451
+ response.status = 200
368
452
  end
369
453
  end
370
454
 
@@ -372,22 +456,27 @@ for the response.
372
456
 
373
457
  The main match method is +r.on+, but as displayed above, you can also
374
458
  use +r.get+ or +r.post+. When called without any arguments, these
375
- call +r.on+ as long as the request has the appropriate method, so:
459
+ match as long as the request has the appropriate method, so:
460
+
461
+ r.get do end
376
462
 
377
- r.get{}
463
+ matches any +GET+ request, and
378
464
 
379
- is syntax sugar for:
465
+ r.post do end
380
466
 
381
- r.on{} if r.get?
467
+ matches any +POST+ request
382
468
 
383
- If any arguments are given to the method, these call +r.is+ as long as
384
- the request has the appropriate method, so:
469
+ If any arguments are given to the method, these match only
470
+ if the request method matches, all arguments match, and
471
+ only the path has been fully matched by the arguments. So:
385
472
 
386
- r.post(""){}
473
+ r.post "" do end
387
474
 
388
- is syntax sugar for:
475
+ matches only +POST+ requests where the current path is +/+.
389
476
 
390
- r.is(""){} if r.post?
477
+ r.get "a/b" do end
478
+
479
+ matches only +GET+ requests where the current path is +/a/b+.
391
480
 
392
481
  The reason for this difference in behavior is that if you are not
393
482
  providing any arguments, you probably don't want to to also test
@@ -400,17 +489,17 @@ you do want, you can provide true as an argument:
400
489
  end
401
490
 
402
491
  If you want to match the request method and do a partial match
403
- on the request path, you need to use +r.on+ with the <tt>:method</tt>
404
- hash matcher:
492
+ on the request path instead of a full match, you need to use
493
+ +r.on+ with the <tt>:method</tt> hash matcher:
405
494
 
406
495
  r.on "foo", :method=>:get do # Matches GET /foo(/.*)?
407
- edn
496
+ end
408
497
 
409
498
  == Root Method
410
499
 
411
500
  As displayed above, you can also use +r.root+ as a match method. This
412
- method matches <tt>GET /</tt> requests. +r.root+ is similar to <tt>r.get ""</tt>,
413
- except that it does not consume the +/+ from the path.
501
+ method matches +GET+ requests where the current path +/+. +r.root+ is
502
+ similar to <tt>r.get ""</tt>, except that it does not consume the +/+ from the path.
414
503
 
415
504
  Unlike the other matching methods, +r.root+ takes no arguments.
416
505
 
@@ -438,8 +527,8 @@ methods, or via plugins.
438
527
 
439
528
  == Pollution
440
529
 
441
- Roda tries very hard to avoid polluting the scope in which the +route+
442
- block operates. The only instance variables defined by default in the scope of
530
+ Roda tries very hard to avoid polluting the scope of the +route+
531
+ block. The only instance variables defined by default in the scope of
443
532
  the +route+ block are <tt>@_request</tt> and <tt>@_response</tt>. The only methods defined
444
533
  (beyond the default methods for +Object+) are: +env+, +opts+, +request+,
445
534
  +response+, +call+, +session+, and +_route+ (private). Constants inside the
@@ -476,8 +565,8 @@ in the request (via POST or QUERY_STRING) and it pushes the value as a capture.
476
565
 
477
566
  == Composition
478
567
 
479
- You can mount a Roda app, along with middlewares, inside another Roda app,
480
- via +r.run+:
568
+ You can mount any Rack app (including another Roda app), with its own middlewares,
569
+ inside a Roda app, using +r.run+:
481
570
 
482
571
  class API < Roda
483
572
  use SomeMiddleware
@@ -499,13 +588,24 @@ via +r.run+:
499
588
 
500
589
  run App.app
501
590
 
502
- You can also use the +multi_route+ plugin, which keeps the current scope of
591
+ This will take any path starting with +/api+ and send it to +API+. In this
592
+ example, +API+ is a Roda app, but it could easily be a Sinatra, Rails, or
593
+ other Rack app.
594
+
595
+ When you use +r.run+, Roda calls the given Rack app (+API+ in this
596
+ case), and whatever the Rack app returns will be returned as the response
597
+ for the current application.
598
+
599
+ === multi_route plugin
600
+
601
+ If you are just looking to split up the main route block up by branches, you
602
+ should use the +multi_route+ plugin, which keeps the current scope of
503
603
  the route block:
504
604
 
505
605
  class App < Roda
506
606
  plugin :multi_route
507
607
 
508
- route :api do |r|
608
+ route "api" do |r|
509
609
  r.is do
510
610
  # ...
511
611
  end
@@ -513,13 +613,16 @@ the route block:
513
613
 
514
614
  route do |r|
515
615
  r.on "api" do
516
- route :api
616
+ r.route "api"
517
617
  end
518
618
  end
519
619
  end
520
620
 
521
621
  run App.app
522
622
 
623
+ This allows you to set instance variables in the main route block, and still
624
+ have access to them inside the +api+ route block.
625
+
523
626
  == Testing
524
627
 
525
628
  It is very easy to test Roda with {Rack::Test}[https://github.com/brynary/rack-test]
@@ -656,6 +759,8 @@ Note that unlike most other render options, the :escape option
656
759
  must be passed to the <tt>plugin :render</tt> call, it won't be
657
760
  respected if added later.
658
761
 
762
+ This support requires {Erubis}[http://www.kuwata-lab.com/erubis/].
763
+
659
764
  === Other
660
765
 
661
766
  For prevention of some other vulnerabilities, such as click-jacking,
@@ -687,6 +792,8 @@ content_for :: Allows storage of content in one template and retrieval of
687
792
  csrf :: Adds CSRF protection and helper methods using
688
793
  {rack_csrf}[https://github.com/baldowl/rack_csrf].
689
794
  default_headers :: Override the default response headers used.
795
+ error_email :: Adds an +error_email+ method that can be used to email when
796
+ an exception is raised.
690
797
  error_handler :: Adds a +error+ block that is called for all responses that
691
798
  raise exceptions.
692
799
  flash :: Adds a flash handler.
@@ -710,6 +817,7 @@ not_found :: Adds a +not_found+ block that is called for all 404 responses
710
817
  without bodies.
711
818
  pass :: Adds a pass method allowing you to skip the current +r.on+ block as if
712
819
  it did not match.
820
+ path :: Adds support for named paths.
713
821
  per_thread_caching :: Switches the thread-safe cache from a shared cache to a
714
822
  per-thread cache.
715
823
  render :: Adds support for rendering templates via tilt, as described above.