roda-cj 0.9.1

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