roda 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +709 -0
  5. data/Rakefile +124 -0
  6. data/lib/roda.rb +608 -0
  7. data/lib/roda/plugins/all_verbs.rb +48 -0
  8. data/lib/roda/plugins/default_headers.rb +50 -0
  9. data/lib/roda/plugins/error_handler.rb +69 -0
  10. data/lib/roda/plugins/flash.rb +62 -0
  11. data/lib/roda/plugins/h.rb +24 -0
  12. data/lib/roda/plugins/halt.rb +79 -0
  13. data/lib/roda/plugins/header_matchers.rb +57 -0
  14. data/lib/roda/plugins/hooks.rb +106 -0
  15. data/lib/roda/plugins/indifferent_params.rb +47 -0
  16. data/lib/roda/plugins/middleware.rb +88 -0
  17. data/lib/roda/plugins/multi_route.rb +77 -0
  18. data/lib/roda/plugins/not_found.rb +62 -0
  19. data/lib/roda/plugins/pass.rb +34 -0
  20. data/lib/roda/plugins/render.rb +217 -0
  21. data/lib/roda/plugins/streaming.rb +165 -0
  22. data/spec/composition_spec.rb +19 -0
  23. data/spec/env_spec.rb +11 -0
  24. data/spec/integration_spec.rb +63 -0
  25. data/spec/matchers_spec.rb +658 -0
  26. data/spec/module_spec.rb +29 -0
  27. data/spec/opts_spec.rb +42 -0
  28. data/spec/plugin/all_verbs_spec.rb +29 -0
  29. data/spec/plugin/default_headers_spec.rb +63 -0
  30. data/spec/plugin/error_handler_spec.rb +67 -0
  31. data/spec/plugin/flash_spec.rb +59 -0
  32. data/spec/plugin/h_spec.rb +13 -0
  33. data/spec/plugin/halt_spec.rb +62 -0
  34. data/spec/plugin/header_matchers_spec.rb +61 -0
  35. data/spec/plugin/hooks_spec.rb +97 -0
  36. data/spec/plugin/indifferent_params_spec.rb +13 -0
  37. data/spec/plugin/middleware_spec.rb +52 -0
  38. data/spec/plugin/multi_route_spec.rb +98 -0
  39. data/spec/plugin/not_found_spec.rb +99 -0
  40. data/spec/plugin/pass_spec.rb +23 -0
  41. data/spec/plugin/render_spec.rb +148 -0
  42. data/spec/plugin/streaming_spec.rb +52 -0
  43. data/spec/plugin_spec.rb +61 -0
  44. data/spec/redirect_spec.rb +24 -0
  45. data/spec/request_spec.rb +55 -0
  46. data/spec/response_spec.rb +131 -0
  47. data/spec/session_spec.rb +35 -0
  48. data/spec/spec_helper.rb +89 -0
  49. data/spec/version_spec.rb +8 -0
  50. metadata +148 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ab3cd69eb4146c87b08dc9d58ac4c6cb40e98e71
4
+ data.tar.gz: f276b865b831438274335aa559ef1cbfe743f045
5
+ SHA512:
6
+ metadata.gz: 04b6479269bea8bffa930f735a11d22436d791eb04531ac65248ab88b9eb7096146929c9538ab4589f0160c41839b1c488bfcc71adf0cd6c3fa3376277973b9f
7
+ data.tar.gz: 603135eaa279af4980f7e799decc28ee288bfbdcb86ebe1ccd66c84ee48c531edbcb032d9f7e1f68d80b08a8a02d1f98cc96e4534f6e7e86b9630428a1f8fc2e
@@ -0,0 +1,3 @@
1
+ = 0.9.0 (2014-07-30)
2
+
3
+ * Initial public release
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014 Jeremy Evans
2
+ Copyright (c) 2010, 2011 Michel Martens, Damian Janowski and Cyril David
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
@@ -0,0 +1,709 @@
1
+ = Roda
2
+
3
+ Roda is a routing tree web framework.
4
+
5
+ = Installation
6
+
7
+ $ gem install roda
8
+
9
+ == Resources
10
+
11
+ Website :: http://roda.jeremyevans.net
12
+ Source :: http://github.com/jeremyevans/roda
13
+ Bugs :: http://github.com/jeremyevans/roda/issues
14
+ Google Group :: http://groups.google.com/group/ruby-roda
15
+ IRC :: irc://chat.freenode.net/#roda
16
+
17
+ == Usage
18
+
19
+ Here's a simple application, showing how the routing tree works:
20
+
21
+ # cat config.ru
22
+ require "roda"
23
+
24
+ class App < Roda
25
+ use Rack::Session::Cookie, :secret => ENV['SECRET']
26
+
27
+ route do |r|
28
+ # matches any GET request
29
+ r.get do
30
+
31
+ # matches GET /
32
+ r.is "" do
33
+ r.redirect "/hello"
34
+ end
35
+
36
+ # matches GET /hello or GET /hello/.*
37
+ r.on "hello" do
38
+
39
+ # matches GET /hello/world
40
+ r.is "world" do
41
+ "Hello world!"
42
+ end
43
+
44
+ # matches GET /hello
45
+ r.is do
46
+ "Hello!"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ run App.app
54
+
55
+ You can now run +rackup+ and enjoy what you have just created.
56
+
57
+ Here's a breakdown of what is going on in the above block:
58
+
59
+ After requiring the library and subclassing Roda, the +use+ method
60
+ is called, which loads a rack middleware into the current
61
+ application.
62
+
63
+ The +route+ block is called whenever a new request comes in,
64
+ and it is yielded an instance of a subclass of <tt>Rack::Request</tt>
65
+ with some additional methods for matching routes. By
66
+ convention, this argument should be named +r+.
67
+
68
+ The primary way routes are matched in Roda is by calling
69
+ +r.on+, or a method like +r.get+ or +r.is+ which calls +r.on+.
70
+ +r.on+ takes each of the arguments given and tries to match them to
71
+ the current request. If it is able to successfully match
72
+ all of the arguments, it yields to the +r.on+ block, otherwise
73
+ it returns immediately.
74
+
75
+ +r.get+ is a shortcut that matches any GET request, and
76
+ +r.is+ is a shortcut that ensures the the exact route is
77
+ matched and there are no further entries in the path.
78
+
79
+ If +r.on+ matches and control is yielded to the block, whenever
80
+ the block returns, the response will be returned. If the block
81
+ returns a string and the response body hasn't already been
82
+ written to, the block return value will interpreted as the body
83
+ for the response. If none of the +r.on+ blocks match and the
84
+ route block returns a string, it will be interpreted as the body
85
+ for the response.
86
+
87
+ +r.redirect+ immediately returns the response, allowing for
88
+ code such as <tt>r.redirect(path) if some_condition</tt>.
89
+
90
+ The +.app+ at the end is an optimization, which you can leave
91
+ off, but which saves a few methods call for every response.
92
+
93
+ == Matchers
94
+
95
+ Here's an example showcasing how different matchers work. Matchers
96
+ are arguments passed to +r.on+.
97
+
98
+ class App < Roda
99
+ route do |r|
100
+ # only GET requests
101
+ r.get do
102
+
103
+ # /
104
+ r.is "" do
105
+ "Home"
106
+ end
107
+
108
+ # /about
109
+ r.is "about" do
110
+ "About"
111
+ end
112
+
113
+ # /styles/basic.css
114
+ r.is "styles", :extension => "css" do |file|
115
+ "Filename: #{file}" #=> "Filename: basic"
116
+ end
117
+
118
+ # /post/2011/02/16/hello
119
+ r.is "post/:y/:m/:d/:slug" do |y, m, d, slug|
120
+ "#{y}-#{m}-#{d} #{slug}" #=> "2011-02-16 hello"
121
+ end
122
+
123
+ # /username/foobar
124
+ r.on "username/:username" do |username|
125
+ user = User.find_by_username(username) # username == "foobar"
126
+
127
+ # /username/foobar/posts
128
+ r.is "posts" do
129
+
130
+ # You can access user here, because the blocks are closures.
131
+ "Total Posts: #{user.posts.size}" #=> "Total Posts: 6"
132
+ end
133
+
134
+ # /username/foobar/following
135
+ r.is "following" do
136
+ user.following.size.to_s #=> "1301"
137
+ end
138
+ end
139
+
140
+ # /search?q=barbaz
141
+ r.is "search", :param=>"q" do |query|
142
+ "Searched for #{query}" #=> "Searched for barbaz"
143
+ end
144
+ end
145
+
146
+ # only POST requests
147
+ r.post do
148
+ r.is "login" do
149
+
150
+ # POST /login, user: foo, pass: baz
151
+ r.on {:param=>"user"}, {:param=>"pass"} do |user, pass|
152
+ "#{user}:#{pass}" #=> "foo:baz"
153
+ end
154
+
155
+ # If the params user and pass are not provided, this
156
+ # will get executed.
157
+ "You need to provide user and pass!"
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ Here's a description of the matchers. Note that segment as used
164
+ here means one part of the path preceeded by a +/+. So a path such
165
+ as +/foo/bar//baz+ has 4 segments, +/foo+, +/bar+, +/+ and +/baz+.
166
+ The +/+ here is considered the empty segment.
167
+
168
+ === String
169
+
170
+ If it does not contain a colon or slash, it matches single segment
171
+ with the text of the string, preceeded by a slash.
172
+
173
+ "" matches "/"
174
+ "foo" matches "/foo"
175
+ "foo" does not match "/food"
176
+
177
+ If it contains any slashes, it matches one additional segment for
178
+ each slash:
179
+
180
+ "foo/bar" matches "/foo/bar"
181
+ "foo/bar" does not match "/foo/bard"
182
+
183
+ If it contains a colon followed by any <tt>\\w</tt> characters, the colon and
184
+ remaing <tt>\\w</tt> characters matches any nonempty segment that contains at
185
+ least one character:
186
+
187
+ "foo/:id" matches "/foo/bar", "/foo/baz", etc.
188
+ "foo/:id" does not match "/fo/bar"
189
+
190
+ You can use multiple colons in a string:
191
+
192
+ ":x/:y" matches "/foo/bar", "/bar/foo" etc.
193
+ ":x/:y" does not match "/foo", "/bar/"
194
+
195
+ You can prefix colons:
196
+
197
+ "foo:x/bar:y" matches "/food/bard", "/fool/bart", etc.
198
+ "foo:x/bar:y" does not match "/foo/bart", "/fool/bar", etc.
199
+
200
+ If any colons are used, the block will yield one argument for
201
+ each segment matched containing the matched text. So:
202
+
203
+ "foo:x/:y" matching "/fool/bar" yields "l", "bar"
204
+
205
+ Colons that are not followed by a <tt>\\w</tt> character are matched literally:
206
+
207
+ ":/a" matches "/:/a"
208
+
209
+ Note that strings are regexp escaped before being used in a regular
210
+ expression, so:
211
+
212
+ "\\d+(/\\w+)?" matches "\d+(/\w+)?"
213
+ "\\d+/\\w+" does not match "123/abc"
214
+
215
+ === Regexp
216
+
217
+ Regexps match one or more segments by looking for the pattern preceeded by a
218
+ slash:
219
+
220
+ /foo\w+/ matches "/foobar"
221
+ /foo\w+/ does not match "/foo/bar"
222
+
223
+ If any patterns are captured by the regexp, they are yielded:
224
+
225
+ /foo\w+/ matches "/foobar", yields nothing
226
+ /foo(\w+)/ matches "/foobar", yields "bar"
227
+
228
+ === Symbol
229
+
230
+ Symbols match any nonempty segment, yielding the segment except for the
231
+ preceeding slash:
232
+
233
+ :id matches "/foo" yields "foo"
234
+ :id does not match "/"
235
+
236
+ === Proc
237
+
238
+ Procs match unless they return false or nil:
239
+
240
+ proc{true} matches anything
241
+ proc{false} does not match anything
242
+
243
+ Procs don't capture anything by default, but they can if you add
244
+ the captured text to +r.captures+.
245
+
246
+ === Arrays
247
+
248
+ Arrays match when any of their elements matches. If multiple matchers
249
+ are given to +r.on+, they all must match (an AND condition), while
250
+ if an array of matchers is given, only one needs to match (an OR
251
+ condition). Evaluation stops at the first matcher that matches.
252
+
253
+ Additionally, if the matched object is a String, the string is yielded.
254
+ This makes it easy to handle multiple strings without a Regexp:
255
+
256
+ %w'page1 page2' matches "/page1", "/page2"
257
+ [] does not match anything
258
+
259
+ === Hash
260
+
261
+ Hashes call a <tt>match_*</tt> method with the given key using the hash value,
262
+ and match if that matcher returns true.
263
+
264
+ The default registered matchers included with Roda are documented below.
265
+ You can add your own hash matchers by adding the appropriate <tt>match_*</tt>
266
+ method to the request class using the +request_module+ method:
267
+
268
+ class App < Roda
269
+ request_module do
270
+ def match_foo(v)
271
+ ...
272
+ end
273
+ end
274
+
275
+ route do |r|
276
+ r.on :foo=>'bar' do
277
+ ...
278
+ end
279
+ end
280
+ end
281
+
282
+
283
+ ==== :extension
284
+
285
+ The :extension matcher matches any nonempty path ending with the given extension:
286
+
287
+ :extension => "css" matches "/foo.css", "/bar.css"
288
+ :extension => "css" does not match "/foo.css/x", "/foo.bar", "/.css"
289
+
290
+ This matcher yields the part before the extension. Note that unlike other
291
+ matchers, this matcher assumes terminal behavior, it doesn't match if there
292
+ are additional segments.
293
+
294
+ ==== :method
295
+
296
+ This matches the method of the request. You can provide an array to specify multiple
297
+ request methods and match on any of them:
298
+
299
+ :method => :post matches POST
300
+ :method => %w'post patch' matches POST and PATCH
301
+
302
+ ==== :param
303
+
304
+ The :param matcher matches if the given parameter is present, even if empty.
305
+
306
+ :param => "user" matches "/foo?user=bar", "/foo?user="
307
+ :param => "user" does not matches "/foo"
308
+
309
+ ==== :param!
310
+
311
+ The :param! matcher matches if the given parameter is present and not empty.
312
+
313
+ :param! => "user" matches "/foo?user=bar"
314
+ :param! => "user" does not matches "/foo", "/foo?user="
315
+
316
+ ==== :term
317
+
318
+ The :term matcher matches if true and there are no segments left. This matcher is
319
+ added by +r.is+ to ensure an exact path match.
320
+
321
+ :term => true matches ""
322
+ :term => true does not match "/"
323
+ :term => false matches "/"
324
+ :term => false does not match ""
325
+
326
+ === false, nil
327
+
328
+ If false or nil is given directly as a matcher, it doesn't match anything.
329
+
330
+ === Everything else
331
+
332
+ Everything else matches anything.
333
+
334
+ == Status codes
335
+
336
+ When it comes time to finalize a response, if a status code has not
337
+ been set manually, it will use a 200 status code if anything has been
338
+ written to the response, otherwise it will use a 404 status code.
339
+ This enables the principle of least surprise to work, where if you
340
+ don't handle an action, a 404 response is assumed.
341
+
342
+ You can always set the status code manually via the status attribute
343
+ for the response.
344
+
345
+ route do |r|
346
+ r.get do
347
+ r.is "hello" do
348
+ response.status = 200
349
+ end
350
+ end
351
+ end
352
+
353
+ == Security
354
+
355
+ If you want to protect against some common web application
356
+ vulnerabilities, you can use
357
+ {Rack::Protection}[https://github.com/rkh/rack-protection].
358
+ It is not included by default because there are legitimate
359
+ uses for plain Roda (for instance, when designing an API).
360
+
361
+ If you are using sessions, you should also always set a session
362
+ secret to some undisclosed value. Keep in mind that the content
363
+ in the session cookie is not encrypted, just signed to prevent
364
+ tampering.
365
+
366
+ require "roda"
367
+ require "rack/protection"
368
+
369
+ class App < Roda
370
+ use Rack::Session::Cookie, :secret => ENV['SECRET']
371
+ use Rack::Protection
372
+
373
+ route do |r|
374
+ # ...
375
+ end
376
+ end
377
+
378
+ == Verb Methods
379
+
380
+ The main match method is +r.on+, but as displayed above, you can also
381
+ use +r.get+ or +r.post+. When called without any arguments, these
382
+ call +r.on+ as long as the request has the appropriate method, so:
383
+
384
+ r.get{}
385
+
386
+ is syntax sugar for:
387
+
388
+ r.on{} if r.get?
389
+
390
+ If any arguments are given to the method, these call +r.is+ as long as
391
+ the request has the appropriate method, so:
392
+
393
+ r.post(""){}
394
+
395
+ is syntax sugar for:
396
+
397
+ r.is(""){} if r.post?
398
+
399
+ The reason for this difference in behavior is that if you are not
400
+ providing any arguments, you probably don't want to to also test
401
+ for an exact match with the current path. If that is something
402
+ you do want, you can provide true as an argument:
403
+
404
+ r.on "foo" do
405
+ r.get true do # Matches GET /foo, not GET /foo/.*
406
+ end
407
+ end
408
+
409
+ If you want to match the request method and do a partial match
410
+ on the request path, you need to use +r.on+ with the <tt>:method</tt>
411
+ hash matcher:
412
+
413
+ r.on "foo", :method=>:get do # Matches GET /foo(/.*)?
414
+ edn
415
+
416
+ == Request and Response
417
+
418
+ While the request object is yielded to the route block, it is also
419
+ available via the +request+ method. Likewise, the response object
420
+ is available via the +response+ method.
421
+
422
+ The request object is an instance of a subclass of <tt>Rack::Request</tt>
423
+ with some additional methods, and the response object is an
424
+ instance of a subclass of <tt>Rack::Response</tt> with some additional
425
+ methods.
426
+
427
+ If you want to extend the request and response objects with additional
428
+ modules, you can do so via the +request_module+ or +response_module+
429
+ methods, or via plugins.
430
+
431
+ == Pollution
432
+
433
+ Roda tries very hard to avoid polluting the scope in which the +route+
434
+ block operates. The only instance variables defined by default in the scope of
435
+ the +route+ block are <tt>@_request</tt> and <tt>@_response</tt>. The only methods defined
436
+ (beyond the default methods for +Object+) are: +env+, +opts+, +request+,
437
+ +response+, +call+, +session+, and +_route+ (private). Constants inside the
438
+ Roda namespace are all prefixed with +Roda+ (e.g. <tt>Roda::RodaRequest</tt>). This
439
+ should make it unlikely that Roda will cause a namespace issue with your
440
+ application code.
441
+
442
+ == Captures
443
+
444
+ You may have noticed that some matchers yield a value to the block. The rules
445
+ for determining if a matcher will yield a value are simple:
446
+
447
+ 1. Regexp captures: <tt>/posts\/(\d+)-(.*)/</tt> will yield two values, corresponding to each capture.
448
+ 2. String placeholders: <tt>"users/:id"</tt> will yield the value in the position of +:id+.
449
+ 3. Symbols: +:foobar+ will yield if a segment is available.
450
+ 4. File extensions: <tt>:extension=>"css"</tt> will yield the basename of the matched file.
451
+ 5. Parameters: <tt>:param=>"user"</tt> will yield the value of the parameter user, if present.
452
+
453
+ The first case is important because it shows the underlying effect of regex
454
+ captures.
455
+
456
+ In the second case, the substring +:id+ gets replaced by <tt>([^\\/]+)</tt> and the
457
+ regexp becomes <tt>/users\/([^\/]+)/</tt> before performing the match, thus it reverts
458
+ to the first form we saw.
459
+
460
+ In the third case, the symbol, no matter what it says, gets replaced
461
+ by <tt>/([^\\/]+)/</tt>, and again we are in presence of case 1.
462
+
463
+ The fourth case, again, reverts to the basic matcher: it generates the string
464
+ <tt>/([^\/]+?)\.#{ext}\z/</tt> before performing the match.
465
+
466
+ The fifth case is different: it checks if the the parameter supplied is present
467
+ in the request (via POST or QUERY_STRING) and it pushes the value as a capture.
468
+
469
+ == Composition
470
+
471
+ You can mount a Roda app, along with middlewares, inside another Roda app,
472
+ via +r.run+:
473
+
474
+ class API < Roda
475
+ use SomeMiddleware
476
+
477
+ route do |r|
478
+ r.is do
479
+ # ...
480
+ end
481
+ end
482
+ end
483
+
484
+ class App < Roda
485
+ route do |r|
486
+ r.on "api" do
487
+ r.run API
488
+ end
489
+ end
490
+ end
491
+
492
+ run App.app
493
+
494
+ You can also use the +multi_route+ plugin, which keeps the current scope of
495
+ the route block:
496
+
497
+ class App < Roda
498
+ plugin :multi_route
499
+
500
+ route :api do |r|
501
+ r.is do
502
+ # ...
503
+ end
504
+ end
505
+
506
+ route do |r|
507
+ r.on "api" do
508
+ route :api
509
+ end
510
+ end
511
+ end
512
+
513
+ run App.app
514
+
515
+ == Testing
516
+
517
+ It is very easy to test Roda with {Rack::Test}[https://github.com/brynary/rack-test]
518
+ or {Capybara}[https://github.com/jnicklas/capybara]. Roda's own tests are written
519
+ with a combination of {RSpec}[http://rspec.info] and Rack::Test
520
+ The default rake task will run the specs for Roda, if RSpec is installed.
521
+
522
+ == Settings
523
+
524
+ Each Roda app can store settings in the +opts+ hash. The settings are
525
+ inherited if you happen to subclass +Roda+.
526
+
527
+ Roda.opts[:layout] = "guest"
528
+
529
+ class Users < Roda; end
530
+ class Admin < Roda; end
531
+
532
+ Admin.opts[:layout] = "admin"
533
+
534
+ Users.opts[:layout] # => 'guest'
535
+ Admin.opts[:layout] # => 'admin'
536
+
537
+ Feel free to store whatever you find convenient. Note that when subclassing,
538
+ Roda only does a shallow clone of the settings. If you store nested structures
539
+ and plan to mutate them in subclasses, it is your responsibility to dup the nested
540
+ structures inside +Roda.inherited+ (making sure to call +super+). The
541
+ plugins that ship with Roda all handle this. Also, note that this means that
542
+ future modifications to the parent class after subclassing do not affect the
543
+ subclass.
544
+
545
+ == Rendering
546
+
547
+ Roda ships with a +render+ plugin that provides helpers for rendering templates. It uses
548
+ {Tilt}[https://github.com/rtomayko/tilt], a gem that interfaces with many template
549
+ engines. The +erb+ engine is used by default.
550
+
551
+ Note that in order to use this plugin you need to have Tilt installed, along
552
+ with the templating engines you want to use.
553
+
554
+ This plugin adds the +render+ and +view+ methods, for rendering templates.
555
+ The difference between +render+ and +view+ is that +view+ will by default
556
+ attempt to render the template inside the default layout template, where
557
+ +render+ will just render the template.
558
+
559
+ class App < Roda
560
+ plugin :render
561
+
562
+ route do |r|
563
+ @var = '1'
564
+
565
+ r.is "render" do
566
+ # Renders the views/home.erb template, which will have access to the
567
+ # instance variable @var, as well as local variable content
568
+ render("home", :locals=>{:content => "hello, world"})
569
+ end
570
+
571
+ r.is "view" do
572
+ @var2 = '1'
573
+
574
+ # Renders the views/home.erb template, which will have access to the
575
+ # instance variables @var and @var2, and takes the output of that and
576
+ # renders it inside views/layout.erb (which should yield where the
577
+ # content should be inserted).
578
+ view("home")
579
+ end
580
+ end
581
+ end
582
+
583
+ You can override the default rendering options by passing a hash to the plugin,
584
+ or modifying the +render_opts+ hash after loading the plugin:
585
+
586
+ class App < Roda
587
+ plugin :render, :engine=>'slim' # Tilt engine/template file extension to use
588
+ render_opts[:views] = 'admin_views' # Default views directory
589
+ render_opts[:layout] = "admin_layout" # Default layout template
590
+ render_opts[:layout_opts] = {:engine=>'haml'} # Default layout template options
591
+ render_opts[:opts] = {:default_encoding=>'UTF-8'} # Default template options
592
+ end
593
+
594
+ == Plugins
595
+
596
+ Roda provides a way to extend its functionality with plugins. Plugins can
597
+ override any Roda method and call +super+ to get the default behavior.
598
+
599
+ === Included Plugins
600
+
601
+ These plugins ship with roda:
602
+
603
+ all_verbs :: Adds routing methods to the request for all http verbs.
604
+ default_headers :: Override the default response headers used.
605
+ error_handler :: Adds a +error+ block that is called for all responses that
606
+ raise exceptions.
607
+ flash :: Adds a flash handler, requires sinatra-flash.
608
+ h :: Adds h method for html escaping.
609
+ halt :: Augments request#halt method to take status and/or body or status,
610
+ headers, and body.
611
+ header_matchers :: Adds host, header, and accept hash matchers.
612
+ hooks :: Adds before and after methods to run code before and after requests.
613
+ indifferent_params :: Adds params method with indifferent access to params,
614
+ allowing use of symbol keys for accessing params.
615
+ middleware :: Allows the Roda app to be used as a rack middleware, calling the
616
+ next middleware if no route matches.
617
+ multi_route :: Adds the ability for multiple named route blocks, with the
618
+ ability to dispatch to them add any point in the main route block.
619
+ not_found :: Adds a +not_found+ block that is called for all 404 responses
620
+ without bodies.
621
+ pass :: Adds a pass method allowing you to skip the current +r.on+ block as if
622
+ it did not match.
623
+ render :: Adds support for rendering templates via tilt, as described above.
624
+ streaming :: Adds support for streaming responses.
625
+
626
+ === External Plugins
627
+
628
+ The following libraries include Roda plugins:
629
+
630
+ forme :: Adds support for easy HTML form creation in erb templates.
631
+ autoforme :: Adds support for easily creating a simple administrative front
632
+ end for Sequel models.
633
+
634
+ === How to create plugins
635
+
636
+ Authoring your own plugins is pretty straightforward. Plugins are just modules
637
+ that contain one of the following modules:
638
+
639
+ InstanceMethods :: module included in the Roda class
640
+ ClassMethods :: module that extends the Roda class
641
+ RequestMethods :: module included in the class of the request
642
+ ResponseMethods :: module included in the class of the response
643
+
644
+ If the plugin responds to +load_dependencies+, it will be called first, and should
645
+ be used if the plugin depends on another plugin.
646
+
647
+ If the plugin responds to +configure+, it will be called last, and should be
648
+ used to configure the plugin.
649
+
650
+ Both +load_dependencies+ and +configure+ are called with the additional arguments
651
+ and block given to the plugin call.
652
+
653
+ So a simple plugin to add an instance method would be:
654
+
655
+ module MarkdownHelper
656
+ module InstanceMethods
657
+ def markdown(str)
658
+ BlueCloth.new(str).to_html
659
+ end
660
+ end
661
+ end
662
+
663
+ Roda.plugin MarkdownHelper
664
+
665
+ === Registering plugins
666
+
667
+ If you want to ship a Roda plugin in a gem, but still have
668
+ Roda load it automatically via <tt>Roda.plugin :plugin_name</tt>, you should
669
+ place it where it can be required via +roda/plugins/plugin_name+, and
670
+ then have the file register it as a plugin via +Roda.register_plugin+.
671
+ It's recommended but not required that you store your plugin module
672
+ in the <tt>Roda::RodaPlugins</tt> namespace:
673
+
674
+ module Roda
675
+ module RodaPlugins
676
+ module Markdown
677
+ module InstanceMethods
678
+ def markdown(str)
679
+ BlueCloth.new(str).to_html
680
+ end
681
+ end
682
+ end
683
+ end
684
+
685
+ register_plugin :markdown, RodaPlugins::Markdown
686
+ end
687
+
688
+ You should avoid creating your module directly in the +Roda+ namespace
689
+ to avoid polluting the namespace. Additionally, any instance variables
690
+ created inside an InstanceMethods should be prefixed with an underscore
691
+ (e.g. <tt>@_variable</tt>) to avoid polluting the scope.
692
+
693
+ == Inspiration
694
+
695
+ Roda was inspired by {Sinatra}[http://www.sinatrarb.com] and {Cuba}[http://cuba.is],
696
+ two other Ruby web frameworks. It started out as a fork of Cuba, from which it borrows
697
+ the idea of using a routing tree (which Cuba in turn took from
698
+ {Rum}[https://github.com/chneukirchen/rum]). From Sinatra it takes the ideas that
699
+ route blocks should return the request bodies and that routes should be canonical.
700
+ It pilfers the idea for an extensible plugin system from the Ruby database library
701
+ {Sequel}[http://sequel.jeremyevans.net].
702
+
703
+ == License
704
+
705
+ MIT
706
+
707
+ == Maintainer
708
+
709
+ Jeremy Evans <code@jeremyevans.net>