roda-cj 0.9.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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +13 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +715 -0
  5. data/Rakefile +124 -0
  6. data/lib/roda/plugins/all_verbs.rb +48 -0
  7. data/lib/roda/plugins/default_headers.rb +50 -0
  8. data/lib/roda/plugins/error_handler.rb +69 -0
  9. data/lib/roda/plugins/flash.rb +108 -0
  10. data/lib/roda/plugins/h.rb +24 -0
  11. data/lib/roda/plugins/halt.rb +79 -0
  12. data/lib/roda/plugins/header_matchers.rb +57 -0
  13. data/lib/roda/plugins/hooks.rb +106 -0
  14. data/lib/roda/plugins/indifferent_params.rb +47 -0
  15. data/lib/roda/plugins/middleware.rb +88 -0
  16. data/lib/roda/plugins/multi_route.rb +77 -0
  17. data/lib/roda/plugins/not_found.rb +62 -0
  18. data/lib/roda/plugins/pass.rb +34 -0
  19. data/lib/roda/plugins/render.rb +217 -0
  20. data/lib/roda/plugins/streaming.rb +165 -0
  21. data/lib/roda/version.rb +3 -0
  22. data/lib/roda.rb +610 -0
  23. data/spec/composition_spec.rb +19 -0
  24. data/spec/env_spec.rb +11 -0
  25. data/spec/integration_spec.rb +63 -0
  26. data/spec/matchers_spec.rb +683 -0
  27. data/spec/module_spec.rb +29 -0
  28. data/spec/opts_spec.rb +42 -0
  29. data/spec/plugin/all_verbs_spec.rb +29 -0
  30. data/spec/plugin/default_headers_spec.rb +63 -0
  31. data/spec/plugin/error_handler_spec.rb +67 -0
  32. data/spec/plugin/flash_spec.rb +123 -0
  33. data/spec/plugin/h_spec.rb +13 -0
  34. data/spec/plugin/halt_spec.rb +62 -0
  35. data/spec/plugin/header_matchers_spec.rb +61 -0
  36. data/spec/plugin/hooks_spec.rb +97 -0
  37. data/spec/plugin/indifferent_params_spec.rb +13 -0
  38. data/spec/plugin/middleware_spec.rb +52 -0
  39. data/spec/plugin/multi_route_spec.rb +98 -0
  40. data/spec/plugin/not_found_spec.rb +99 -0
  41. data/spec/plugin/pass_spec.rb +23 -0
  42. data/spec/plugin/render_spec.rb +148 -0
  43. data/spec/plugin/streaming_spec.rb +52 -0
  44. data/spec/plugin_spec.rb +61 -0
  45. data/spec/redirect_spec.rb +24 -0
  46. data/spec/request_spec.rb +55 -0
  47. data/spec/response_spec.rb +131 -0
  48. data/spec/session_spec.rb +35 -0
  49. data/spec/spec_helper.rb +89 -0
  50. data/spec/version_spec.rb +8 -0
  51. metadata +136 -0
data/README.rdoc ADDED
@@ -0,0 +1,715 @@
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
+ == Inspiration
18
+
19
+ Roda was inspired by {Sinatra}[http://www.sinatrarb.com] and {Cuba}[http://cuba.is],
20
+ two other Ruby web frameworks. It started out as a fork of Cuba, from which it borrows
21
+ the idea of using a routing tree (which Cuba in turn took from
22
+ {Rum}[https://github.com/chneukirchen/rum]). From Sinatra it takes the ideas that
23
+ route blocks should return the request bodies and that routes should be canonical.
24
+ It pilfers the idea for an extensible plugin system from the Ruby database library
25
+ {Sequel}[http://sequel.jeremyevans.net].
26
+
27
+ == Usage
28
+
29
+ Here's a simple application, showing how the routing tree works:
30
+
31
+ # cat config.ru
32
+ require "roda"
33
+
34
+ class App < Roda
35
+ use Rack::Session::Cookie, :secret => ENV['SECRET']
36
+
37
+ route do |r|
38
+ # matches any GET request
39
+ r.get do
40
+
41
+ # matches GET /
42
+ r.root do
43
+ r.redirect "/hello"
44
+ end
45
+
46
+ # matches GET /hello or GET /hello/.*
47
+ r.on "hello" do
48
+
49
+ # matches GET /hello/world
50
+ r.is "world" do
51
+ "Hello world!"
52
+ end
53
+
54
+ # matches GET /hello
55
+ r.is do
56
+ "Hello!"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ run App.app
64
+
65
+ You can now run +rackup+ and enjoy what you have just created.
66
+
67
+ Here's a breakdown of what is going on in the above block:
68
+
69
+ After requiring the library and subclassing Roda, the +use+ method
70
+ is called, which loads a rack middleware into the current
71
+ application.
72
+
73
+ The +route+ block is called whenever a new request comes in,
74
+ and it is yielded an instance of a subclass of <tt>Rack::Request</tt>
75
+ with some additional methods for matching routes. By
76
+ convention, this argument should be named +r+.
77
+
78
+ 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.
96
+
97
+ +r.redirect+ immediately returns the response, allowing for
98
+ code such as <tt>r.redirect(path) if some_condition</tt>.
99
+
100
+ The +.app+ at the end is an optimization, which you can leave
101
+ off, but which saves a few methods call for every response.
102
+
103
+ == Matchers
104
+
105
+ Here's an example showcasing how different matchers work. Matchers
106
+ are arguments passed to +r.on+.
107
+
108
+ class App < Roda
109
+ route do |r|
110
+ # only GET requests
111
+ r.get do
112
+
113
+ # /
114
+ r.root do
115
+ "Home"
116
+ end
117
+
118
+ # /about
119
+ r.is "about" do
120
+ "About"
121
+ end
122
+
123
+ # /styles/basic.css
124
+ r.is "styles", :extension => "css" do |file|
125
+ "Filename: #{file}" #=> "Filename: basic"
126
+ end
127
+
128
+ # /post/2011/02/16/hello
129
+ r.is "post/:y/:m/:d/:slug" do |y, m, d, slug|
130
+ "#{y}-#{m}-#{d} #{slug}" #=> "2011-02-16 hello"
131
+ end
132
+
133
+ # /username/foobar
134
+ r.on "username/:username" do |username|
135
+ user = User.find_by_username(username) # username == "foobar"
136
+
137
+ # /username/foobar/posts
138
+ r.is "posts" do
139
+
140
+ # You can access user here, because the blocks are closures.
141
+ "Total Posts: #{user.posts.size}" #=> "Total Posts: 6"
142
+ end
143
+
144
+ # /username/foobar/following
145
+ r.is "following" do
146
+ user.following.size.to_s #=> "1301"
147
+ end
148
+ end
149
+
150
+ # /search?q=barbaz
151
+ r.is "search", :param=>"q" do |query|
152
+ "Searched for #{query}" #=> "Searched for barbaz"
153
+ end
154
+ end
155
+
156
+ # only POST requests
157
+ r.post do
158
+ r.is "login" do
159
+
160
+ # POST /login, user: foo, pass: baz
161
+ r.on {:param=>"user"}, {:param=>"pass"} do |user, pass|
162
+ "#{user}:#{pass}" #=> "foo:baz"
163
+ end
164
+
165
+ # If the params user and pass are not provided, this
166
+ # will get executed.
167
+ "You need to provide user and pass!"
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ Here's a description of the matchers. Note that segment as used
174
+ here means one part of the path preceeded by a +/+. So a path such
175
+ as +/foo/bar//baz+ has 4 segments, +/foo+, +/bar+, +/+ and +/baz+.
176
+ The +/+ here is considered the empty segment.
177
+
178
+ === String
179
+
180
+ If it does not contain a colon or slash, it matches single segment
181
+ with the text of the string, preceeded by a slash.
182
+
183
+ "" matches "/"
184
+ "foo" matches "/foo"
185
+ "foo" does not match "/food"
186
+
187
+ If it contains any slashes, it matches one additional segment for
188
+ each slash:
189
+
190
+ "foo/bar" matches "/foo/bar"
191
+ "foo/bar" does not match "/foo/bard"
192
+
193
+ If it contains a colon followed by any <tt>\\w</tt> characters, the colon and
194
+ remaing <tt>\\w</tt> characters matches any nonempty segment that contains at
195
+ least one character:
196
+
197
+ "foo/:id" matches "/foo/bar", "/foo/baz", etc.
198
+ "foo/:id" does not match "/fo/bar"
199
+
200
+ You can use multiple colons in a string:
201
+
202
+ ":x/:y" matches "/foo/bar", "/bar/foo" etc.
203
+ ":x/:y" does not match "/foo", "/bar/"
204
+
205
+ You can prefix colons:
206
+
207
+ "foo:x/bar:y" matches "/food/bard", "/fool/bart", etc.
208
+ "foo:x/bar:y" does not match "/foo/bart", "/fool/bar", etc.
209
+
210
+ If any colons are used, the block will yield one argument for
211
+ each segment matched containing the matched text. So:
212
+
213
+ "foo:x/:y" matching "/fool/bar" yields "l", "bar"
214
+
215
+ Colons that are not followed by a <tt>\\w</tt> character are matched literally:
216
+
217
+ ":/a" matches "/:/a"
218
+
219
+ Note that strings are regexp escaped before being used in a regular
220
+ expression, so:
221
+
222
+ "\\d+(/\\w+)?" matches "\d+(/\w+)?"
223
+ "\\d+/\\w+" does not match "123/abc"
224
+
225
+ === Regexp
226
+
227
+ Regexps match one or more segments by looking for the pattern preceeded by a
228
+ slash:
229
+
230
+ /foo\w+/ matches "/foobar"
231
+ /foo\w+/ does not match "/foo/bar"
232
+
233
+ If any patterns are captured by the regexp, they are yielded:
234
+
235
+ /foo\w+/ matches "/foobar", yields nothing
236
+ /foo(\w+)/ matches "/foobar", yields "bar"
237
+
238
+ === Symbol
239
+
240
+ Symbols match any nonempty segment, yielding the segment except for the
241
+ preceeding slash:
242
+
243
+ :id matches "/foo" yields "foo"
244
+ :id does not match "/"
245
+
246
+ === Proc
247
+
248
+ Procs match unless they return false or nil:
249
+
250
+ proc{true} matches anything
251
+ proc{false} does not match anything
252
+
253
+ Procs don't capture anything by default, but they can if you add
254
+ the captured text to +r.captures+.
255
+
256
+ === Arrays
257
+
258
+ Arrays match when any of their elements matches. If multiple matchers
259
+ are given to +r.on+, they all must match (an AND condition), while
260
+ if an array of matchers is given, only one needs to match (an OR
261
+ condition). Evaluation stops at the first matcher that matches.
262
+
263
+ Additionally, if the matched object is a String, the string is yielded.
264
+ This makes it easy to handle multiple strings without a Regexp:
265
+
266
+ %w'page1 page2' matches "/page1", "/page2"
267
+ [] does not match anything
268
+
269
+ === Hash
270
+
271
+ Hashes call a <tt>match_*</tt> method with the given key using the hash value,
272
+ and match if that matcher returns true.
273
+
274
+ The default registered matchers included with Roda are documented below.
275
+ You can add your own hash matchers by adding the appropriate <tt>match_*</tt>
276
+ method to the request class using the +request_module+ method:
277
+
278
+ class App < Roda
279
+ request_module do
280
+ def match_foo(v)
281
+ ...
282
+ end
283
+ end
284
+
285
+ route do |r|
286
+ r.on :foo=>'bar' do
287
+ ...
288
+ end
289
+ end
290
+ end
291
+
292
+
293
+ ==== :extension
294
+
295
+ The :extension matcher matches any nonempty path ending with the given extension:
296
+
297
+ :extension => "css" matches "/foo.css", "/bar.css"
298
+ :extension => "css" does not match "/foo.css/x", "/foo.bar", "/.css"
299
+
300
+ This matcher yields the part before the extension. Note that unlike other
301
+ matchers, this matcher assumes terminal behavior, it doesn't match if there
302
+ are additional segments.
303
+
304
+ ==== :method
305
+
306
+ This matches the method of the request. You can provide an array to specify multiple
307
+ request methods and match on any of them:
308
+
309
+ :method => :post matches POST
310
+ :method => %w'post patch' matches POST and PATCH
311
+
312
+ ==== :param
313
+
314
+ The :param matcher matches if the given parameter is present, even if empty.
315
+
316
+ :param => "user" matches "/foo?user=bar", "/foo?user="
317
+ :param => "user" does not matches "/foo"
318
+
319
+ ==== :param!
320
+
321
+ The :param! matcher matches if the given parameter is present and not empty.
322
+
323
+ :param! => "user" matches "/foo?user=bar"
324
+ :param! => "user" does not matches "/foo", "/foo?user="
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
+ == Root Method
417
+
418
+ As displayed above, you can also use +r.root+ as a match method. This
419
+ method matches only if the path at that point is exactly +/+. +r.root+
420
+ is similar to <tt>r.is ""</tt>, except that it does not
421
+ consume the +/+ from the path.
422
+
423
+ Unlike the other matching methods, +r.root+ does not take multiple
424
+ arguments and pass them to +r.on+. It only accepts an optional request
425
+ method symbol, so <tt>r.root :get</tt> is similar to <tt>r.get ""</tt>,
426
+ except that it does not consume the +/+ from the path.
427
+
428
+ Note that +r.root+ does not match if the path is empty, you should use
429
+ +r.is+ with no arguments for that. If you want to match either the
430
+ the empty path or +/+, you can use <tt>r.is ["", true]</tt>.
431
+
432
+ == Request and Response
433
+
434
+ While the request object is yielded to the route block, it is also
435
+ available via the +request+ method. Likewise, the response object
436
+ is available via the +response+ method.
437
+
438
+ The request object is an instance of a subclass of <tt>Rack::Request</tt>
439
+ with some additional methods, and the response object is an
440
+ instance of a subclass of <tt>Rack::Response</tt> with some additional
441
+ methods.
442
+
443
+ If you want to extend the request and response objects with additional
444
+ modules, you can do so via the +request_module+ or +response_module+
445
+ methods, or via plugins.
446
+
447
+ == Pollution
448
+
449
+ Roda tries very hard to avoid polluting the scope in which the +route+
450
+ block operates. The only instance variables defined by default in the scope of
451
+ the +route+ block are <tt>@_request</tt> and <tt>@_response</tt>. The only methods defined
452
+ (beyond the default methods for +Object+) are: +env+, +opts+, +request+,
453
+ +response+, +call+, +session+, and +_route+ (private). Constants inside the
454
+ Roda namespace are all prefixed with +Roda+ (e.g. <tt>Roda::RodaRequest</tt>). This
455
+ should make it unlikely that Roda will cause a namespace issue with your
456
+ application code.
457
+
458
+ == Captures
459
+
460
+ You may have noticed that some matchers yield a value to the block. The rules
461
+ for determining if a matcher will yield a value are simple:
462
+
463
+ 1. Regexp captures: <tt>/posts\/(\d+)-(.*)/</tt> will yield two values, corresponding to each capture.
464
+ 2. String placeholders: <tt>"users/:id"</tt> will yield the value in the position of +:id+.
465
+ 3. Symbols: +:foobar+ will yield if a segment is available.
466
+ 4. File extensions: <tt>:extension=>"css"</tt> will yield the basename of the matched file.
467
+ 5. Parameters: <tt>:param=>"user"</tt> will yield the value of the parameter user, if present.
468
+
469
+ The first case is important because it shows the underlying effect of regex
470
+ captures.
471
+
472
+ In the second case, the substring +:id+ gets replaced by <tt>([^\\/]+)</tt> and the
473
+ regexp becomes <tt>/users\/([^\/]+)/</tt> before performing the match, thus it reverts
474
+ to the first form we saw.
475
+
476
+ In the third case, the symbol, no matter what it says, gets replaced
477
+ by <tt>/([^\\/]+)/</tt>, and again we are in presence of case 1.
478
+
479
+ The fourth case, again, reverts to the basic matcher: it generates the string
480
+ <tt>/([^\/]+?)\.#{ext}\z/</tt> before performing the match.
481
+
482
+ The fifth case is different: it checks if the the parameter supplied is present
483
+ in the request (via POST or QUERY_STRING) and it pushes the value as a capture.
484
+
485
+ == Composition
486
+
487
+ You can mount a Roda app, along with middlewares, inside another Roda app,
488
+ via +r.run+:
489
+
490
+ class API < Roda
491
+ use SomeMiddleware
492
+
493
+ route do |r|
494
+ r.is do
495
+ # ...
496
+ end
497
+ end
498
+ end
499
+
500
+ class App < Roda
501
+ route do |r|
502
+ r.on "api" do
503
+ r.run API
504
+ end
505
+ end
506
+ end
507
+
508
+ run App.app
509
+
510
+ You can also use the +multi_route+ plugin, which keeps the current scope of
511
+ the route block:
512
+
513
+ class App < Roda
514
+ plugin :multi_route
515
+
516
+ route :api do |r|
517
+ r.is do
518
+ # ...
519
+ end
520
+ end
521
+
522
+ route do |r|
523
+ r.on "api" do
524
+ route :api
525
+ end
526
+ end
527
+ end
528
+
529
+ run App.app
530
+
531
+ == Testing
532
+
533
+ It is very easy to test Roda with {Rack::Test}[https://github.com/brynary/rack-test]
534
+ or {Capybara}[https://github.com/jnicklas/capybara]. Roda's own tests use
535
+ {RSpec}[http://rspec.info]. The default rake task will run the specs for Roda, if
536
+ RSpec is installed.
537
+
538
+ == Settings
539
+
540
+ Each Roda app can store settings in the +opts+ hash. The settings are
541
+ inherited if you happen to subclass +Roda+.
542
+
543
+ Roda.opts[:layout] = "guest"
544
+
545
+ class Users < Roda; end
546
+ class Admin < Roda; end
547
+
548
+ Admin.opts[:layout] = "admin"
549
+
550
+ Users.opts[:layout] # => 'guest'
551
+ Admin.opts[:layout] # => 'admin'
552
+
553
+ Feel free to store whatever you find convenient. Note that when subclassing,
554
+ Roda only does a shallow clone of the settings. If you store nested structures
555
+ and plan to mutate them in subclasses, it is your responsibility to dup the nested
556
+ structures inside +Roda.inherited+ (making sure to call +super+). The
557
+ plugins that ship with Roda all handle this. Also, note that this means that
558
+ future modifications to the parent class after subclassing do not affect the
559
+ subclass.
560
+
561
+ == Rendering
562
+
563
+ Roda ships with a +render+ plugin that provides helpers for rendering templates. It uses
564
+ {Tilt}[https://github.com/rtomayko/tilt], a gem that interfaces with many template
565
+ engines. The +erb+ engine is used by default.
566
+
567
+ Note that in order to use this plugin you need to have Tilt installed, along
568
+ with the templating engines you want to use.
569
+
570
+ This plugin adds the +render+ and +view+ methods, for rendering templates.
571
+ The difference between +render+ and +view+ is that +view+ will by default
572
+ attempt to render the template inside the default layout template, where
573
+ +render+ will just render the template.
574
+
575
+ class App < Roda
576
+ plugin :render
577
+
578
+ route do |r|
579
+ @var = '1'
580
+
581
+ r.is "render" do
582
+ # Renders the views/home.erb template, which will have access to the
583
+ # instance variable @var, as well as local variable content
584
+ render("home", :locals=>{:content => "hello, world"})
585
+ end
586
+
587
+ r.is "view" do
588
+ @var2 = '1'
589
+
590
+ # Renders the views/home.erb template, which will have access to the
591
+ # instance variables @var and @var2, and takes the output of that and
592
+ # renders it inside views/layout.erb (which should yield where the
593
+ # content should be inserted).
594
+ view("home")
595
+ end
596
+ end
597
+ end
598
+
599
+ You can override the default rendering options by passing a hash to the plugin,
600
+ or modifying the +render_opts+ hash after loading the plugin:
601
+
602
+ class App < Roda
603
+ plugin :render, :engine=>'slim' # Tilt engine/template file extension to use
604
+ render_opts[:views] = 'admin_views' # Default views directory
605
+ render_opts[:layout] = "admin_layout" # Default layout template
606
+ render_opts[:layout_opts] = {:engine=>'haml'} # Default layout template options
607
+ render_opts[:opts] = {:default_encoding=>'UTF-8'} # Default template options
608
+ end
609
+
610
+ == Plugins
611
+
612
+ Roda provides a way to extend its functionality with plugins. Plugins can
613
+ override any Roda method and call +super+ to get the default behavior.
614
+
615
+ === Included Plugins
616
+
617
+ These plugins ship with roda:
618
+
619
+ all_verbs :: Adds routing methods to the request for all http verbs.
620
+ default_headers :: Override the default response headers used.
621
+ error_handler :: Adds a +error+ block that is called for all responses that
622
+ raise exceptions.
623
+ flash :: Adds a flash handler.
624
+ h :: Adds h method for html escaping.
625
+ halt :: Augments request#halt method to take status and/or body or status,
626
+ headers, and body.
627
+ header_matchers :: Adds host, header, and accept hash matchers.
628
+ hooks :: Adds before and after methods to run code before and after requests.
629
+ indifferent_params :: Adds params method with indifferent access to params,
630
+ allowing use of symbol keys for accessing params.
631
+ middleware :: Allows the Roda app to be used as a rack middleware, calling the
632
+ next middleware if no route matches.
633
+ multi_route :: Adds the ability for multiple named route blocks, with the
634
+ ability to dispatch to them add any point in the main route block.
635
+ not_found :: Adds a +not_found+ block that is called for all 404 responses
636
+ without bodies.
637
+ pass :: Adds a pass method allowing you to skip the current +r.on+ block as if
638
+ it did not match.
639
+ render :: Adds support for rendering templates via tilt, as described above.
640
+ streaming :: Adds support for streaming responses.
641
+
642
+ === External Plugins
643
+
644
+ The following libraries include Roda plugins:
645
+
646
+ forme :: Adds support for easy HTML form creation in erb templates.
647
+ autoforme :: Adds support for easily creating a simple administrative front
648
+ end for Sequel models.
649
+
650
+ === How to create plugins
651
+
652
+ Authoring your own plugins is pretty straightforward. Plugins are just modules
653
+ that contain one of the following modules:
654
+
655
+ InstanceMethods :: module included in the Roda class
656
+ ClassMethods :: module that extends the Roda class
657
+ RequestMethods :: module included in the class of the request
658
+ ResponseMethods :: module included in the class of the response
659
+
660
+ If the plugin responds to +load_dependencies+, it will be called first, and should
661
+ be used if the plugin depends on another plugin.
662
+
663
+ If the plugin responds to +configure+, it will be called last, and should be
664
+ used to configure the plugin.
665
+
666
+ Both +load_dependencies+ and +configure+ are called with the additional arguments
667
+ and block given to the plugin call.
668
+
669
+ So a simple plugin to add an instance method would be:
670
+
671
+ module MarkdownHelper
672
+ module InstanceMethods
673
+ def markdown(str)
674
+ BlueCloth.new(str).to_html
675
+ end
676
+ end
677
+ end
678
+
679
+ Roda.plugin MarkdownHelper
680
+
681
+ === Registering plugins
682
+
683
+ If you want to ship a Roda plugin in a gem, but still have
684
+ Roda load it automatically via <tt>Roda.plugin :plugin_name</tt>, you should
685
+ place it where it can be required via +roda/plugins/plugin_name+, and
686
+ then have the file register it as a plugin via
687
+ <tt>Roda::RodaPlugins.register_plugin</tt>. It's recommended but not required
688
+ that you store your plugin module in the <tt>Roda::RodaPlugins</tt> namespace:
689
+
690
+ module Roda
691
+ module RodaPlugins
692
+ module Markdown
693
+ module InstanceMethods
694
+ def markdown(str)
695
+ BlueCloth.new(str).to_html
696
+ end
697
+ end
698
+ end
699
+
700
+ register_plugin :markdown, Markdown
701
+ end
702
+ end
703
+
704
+ You should avoid creating your module directly in the +Roda+ namespace
705
+ to avoid polluting the namespace. Additionally, any instance variables
706
+ created inside InstanceMethods should be prefixed with an underscore
707
+ (e.g. <tt>@_variable</tt>) to avoid polluting the scope.
708
+
709
+ == License
710
+
711
+ MIT
712
+
713
+ == Maintainer
714
+
715
+ Jeremy Evans <code@jeremyevans.net>