roda 0.9.0

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