roda 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +42 -0
  3. data/README.rdoc +73 -144
  4. data/doc/conventions.rdoc +10 -8
  5. data/doc/release_notes/1.3.0.txt +109 -0
  6. data/lib/roda.rb +67 -100
  7. data/lib/roda/plugins/assets.rb +4 -4
  8. data/lib/roda/plugins/chunked.rb +4 -1
  9. data/lib/roda/plugins/class_level_routing.rb +7 -1
  10. data/lib/roda/plugins/cookies.rb +34 -0
  11. data/lib/roda/plugins/default_headers.rb +7 -6
  12. data/lib/roda/plugins/delegate.rb +8 -1
  13. data/lib/roda/plugins/delete_empty_headers.rb +33 -0
  14. data/lib/roda/plugins/delete_nil_headers.rb +34 -0
  15. data/lib/roda/plugins/environments.rb +12 -4
  16. data/lib/roda/plugins/error_email.rb +6 -1
  17. data/lib/roda/plugins/error_handler.rb +7 -4
  18. data/lib/roda/plugins/hash_matcher.rb +32 -0
  19. data/lib/roda/plugins/header_matchers.rb +12 -2
  20. data/lib/roda/plugins/json.rb +9 -6
  21. data/lib/roda/plugins/module_include.rb +92 -0
  22. data/lib/roda/plugins/multi_route.rb +7 -0
  23. data/lib/roda/plugins/multi_run.rb +11 -5
  24. data/lib/roda/plugins/named_templates.rb +7 -1
  25. data/lib/roda/plugins/not_found.rb +6 -0
  26. data/lib/roda/plugins/param_matchers.rb +43 -0
  27. data/lib/roda/plugins/path_matchers.rb +51 -0
  28. data/lib/roda/plugins/render.rb +32 -14
  29. data/lib/roda/plugins/static_path_info.rb +10 -3
  30. data/lib/roda/plugins/symbol_matchers.rb +1 -1
  31. data/lib/roda/version.rb +13 -1
  32. data/spec/freeze_spec.rb +28 -0
  33. data/spec/plugin/class_level_routing_spec.rb +26 -0
  34. data/spec/plugin/content_for_spec.rb +1 -2
  35. data/spec/plugin/cookies_spec.rb +25 -0
  36. data/spec/plugin/default_headers_spec.rb +4 -7
  37. data/spec/plugin/delegate_spec.rb +4 -1
  38. data/spec/plugin/delete_empty_headers_spec.rb +15 -0
  39. data/spec/plugin/error_handler_spec.rb +31 -0
  40. data/spec/plugin/hash_matcher_spec.rb +27 -0
  41. data/spec/plugin/header_matchers_spec.rb +15 -0
  42. data/spec/plugin/json_spec.rb +1 -2
  43. data/spec/plugin/mailer_spec.rb +2 -2
  44. data/spec/plugin/module_include_spec.rb +31 -0
  45. data/spec/plugin/multi_route_spec.rb +14 -0
  46. data/spec/plugin/multi_run_spec.rb +41 -0
  47. data/spec/plugin/named_templates_spec.rb +25 -0
  48. data/spec/plugin/not_found_spec.rb +29 -0
  49. data/spec/plugin/param_matchers_spec.rb +37 -0
  50. data/spec/plugin/path_matchers_spec.rb +42 -0
  51. data/spec/plugin/render_spec.rb +33 -8
  52. data/spec/plugin/static_path_info_spec.rb +6 -0
  53. data/spec/plugin/view_subdirs_spec.rb +1 -2
  54. data/spec/response_spec.rb +12 -0
  55. data/spec/spec_helper.rb +2 -0
  56. data/spec/version_spec.rb +8 -2
  57. metadata +19 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3d246589728bb46404806737bb7e67819956f167
4
- data.tar.gz: e57198e8df759bd70039ff42f94d86583c75c2f8
3
+ metadata.gz: 1ec447de1d1c9201190d3e05cddc9ca3aca957e4
4
+ data.tar.gz: fc6e061b744a3ed47259369907bb82e222d12515
5
5
  SHA512:
6
- metadata.gz: 3127e9f7b7525780556555366e82b0e9f565216e62dacdd5e3e7a69e3440a47771ddad5472b94f7c09d15cf6acb49c7bcdb54fb1d469f68569eb5cf77e589dca
7
- data.tar.gz: f6dd7cda1372931ce0bf92934d584e084f8b1b84d5a181ce1b28bcf951983adce7c477e6b4bdabdad9a8f0038cacafa49ba87948b46a38de0ad23b0b1330d86c
6
+ metadata.gz: a5d0a587873870952a81b8df6a0828c76d68e2de2ff6d388f1ccbf82da5b45815722460ba51a540d1cf74f11fa7911b45d6e7928515901095eb08a8d76754ade
7
+ data.tar.gz: 33f7d3837fdf5b846169597868531493e00cf1089b2d68771e2b9ea1345603adb3462ca8283735ab393721ddb461467a0813cb9b05d13cdda869d8d62aaf143f
data/CHANGELOG CHANGED
@@ -1,3 +1,45 @@
1
+ = 1.3.0 (2015-01-13)
2
+
3
+ * Make static_path_info plugin restore original SCRIPT_NAME/PATH_INFO before returning from r.run (jeremyevans)
4
+
5
+ * Add RodaMajorVersion, RodaMinorVersion, and RodaPatchVersion (jeremyevans)
6
+
7
+ * Add delete_empty_headers plugin for deleting response headers that are empty before return response (jeremyevans)
8
+
9
+ * Make freeze class method freeze internal data structures to avoid thread safety issues (jeremyevans)
10
+
11
+ * Deprecate mutating plugin option hashes for chunked, default_headers, error_email, json, and render plugins (jeremyevans)
12
+
13
+ * Fix subclassing app and using r.multi_run in subclass in multi_run plugin (jeremyevans)
14
+
15
+ * Support :classes option in json plugin to set the classes to use (jeremyevans)
16
+
17
+ * Improve performance in default_headers plugin by not duping the headers (jeremyevans)
18
+
19
+ * Use :template_opts instead of :opts for providing options to the template in the render plugin (jeremyevans)
20
+
21
+ * Support :match_header_yield Roda option in the header_matchers plugin, causing the :header match to yield the value (jeremyevans)
22
+
23
+ * Move :param and :param! hash matchers to the param_matchers plugin (jeremyevans)
24
+
25
+ * Add path_matchers plugin, for :extension, :prefix, and :suffix hash matchers (jeremyevans)
26
+
27
+ * Move Roda.hash_matcher to hash_matcher plugin (jeremyevans)
28
+
29
+ * Move Roda.request_module and .response_module to module_include plugin (jeremyevans)
30
+
31
+ * Move RodaResponse#set_cookie and #delete_cookie to cookies plugin (jeremyevans)
32
+
33
+ * Deprecate RodaRequest#full_path_info, use #path instead (jeremyevans)
34
+
35
+ * Add class_delegate to the delegate plugin (jeremyevans)
36
+
37
+ * Make not_found plugin clear headers for response if it is not found (jeremyevans)
38
+
39
+ * Make error_handler plugin use a new response instead of reusing existing response (jeremyevans)
40
+
41
+ * Make RodaResponse a subclass of Object instead of Rack::Response (jeremyevans)
42
+
1
43
  = 1.2.0 (2014-12-17)
2
44
 
3
45
  * Don't override explicit nil :default_encoding template option in the render plugin (jeremyevans)
data/README.rdoc CHANGED
@@ -66,7 +66,7 @@ Here's a simple application, showing how the routing tree works:
66
66
  end
67
67
  end
68
68
 
69
- run App.app
69
+ run App.freeze.app
70
70
 
71
71
  Here's a breakdown of what is going on in the block above:
72
72
 
@@ -111,8 +111,10 @@ allowing for code such as <tt>r.redirect(path) if some_condition</tt>.
111
111
  If +r.redirect+ is called without arguments
112
112
  and the current request method is not +GET+, it redirects to the current path.
113
113
 
114
- The +.app+ at the end is an (optional) optimization.
115
- It saves a few method calls for every response.
114
+ The +.freeze.app+ at the end is optional. Freezing the app avoids any possible
115
+ thread safety issues inside the application at runtime, which shouldn't be possible
116
+ anyway. This generally should only be done in production mode.
117
+ The +.app+ is an optimization, which saves a few method calls for every request.
116
118
 
117
119
  == The Routing Tree
118
120
 
@@ -195,63 +197,51 @@ Here's an example showcasing how different matchers work:
195
197
 
196
198
  class App < Roda
197
199
  route do |r|
198
- # only GET requests
199
- r.get do
200
-
201
- # /
202
- r.root do
203
- "Home"
204
- end
205
-
206
- # /about
207
- r.is "about" do
208
- "About"
209
- end
210
-
211
- # /styles/basic.css
212
- r.is "styles", :extension => "css" do |file|
213
- "Filename: #{file}" #=> "Filename: basic"
214
- end
200
+ # GET /
201
+ r.root do
202
+ "Home"
203
+ end
215
204
 
216
- # /post/2011/02/16/hello
217
- r.is "post/:y/:m/:d/:slug" do |y, m, d, slug|
218
- "#{y}-#{m}-#{d} #{slug}" #=> "2011-02-16 hello"
219
- end
205
+ # GET /about
206
+ r.get "about" do
207
+ "About"
208
+ end
220
209
 
221
- # /username/foobar
222
- r.on "username/:username" do |username|
223
- user = User.find_by_username(username) # username == "foobar"
210
+ # GET /post/2011/02/16/hello
211
+ r.get "post/:y/:m/:d/:slug" do |y, m, d, slug|
212
+ "#{y}-#{m}-#{d} #{slug}" #=> "2011-02-16 hello"
213
+ end
224
214
 
225
- # /username/foobar/posts
226
- r.is "posts" do
227
- # You can access user here, because the blocks are closures.
228
- "Total Posts: #{user.posts.size}" #=> "Total Posts: 6"
229
- end
215
+ # GET /username/foobar branch
216
+ r.on "username/:username", :method=>:get do |username|
217
+ user = User.find_by_username(username)
230
218
 
231
- # /username/foobar/following
232
- r.is "following" do
233
- user.following.size.to_s #=> "1301"
234
- end
219
+ # GET /username/foobar/posts
220
+ r.is "posts" do
221
+ # You can access user here, because the blocks are closures.
222
+ "Total Posts: #{user.posts.size}" #=> "Total Posts: 6"
235
223
  end
236
224
 
237
- # /search?q=barbaz
238
- r.is "search", :param=>"q" do |query|
239
- "Searched for #{query}" #=> "Searched for barbaz"
225
+ # GET /username/foobar/following
226
+ r.is "following" do
227
+ user.following.size.to_s #=> "1301"
240
228
  end
241
229
  end
242
230
 
243
- # only POST requests
244
- r.post do
245
- r.is "login" do
231
+ # /search?q=barbaz
232
+ r.get "search" do
233
+ "Searched for #{r['q']}" #=> "Searched for barbaz"
234
+ end
246
235
 
247
- # POST /login, user: foo, pass: baz
248
- r.on({:param=>"user"}, {:param=>"pass"}) do |user, pass|
249
- "#{user}:#{pass}" #=> "foo:baz"
250
- end
236
+ r.is "login" do
237
+ # GET /login
238
+ r.get do
239
+ "Login"
240
+ end
251
241
 
252
- # If the params user and pass are not provided, this
253
- # will get executed.
254
- "You need to provide user and pass!"
242
+ # POST /login?user=foo&password=baz
243
+ r.post do
244
+ "#{r['user']}:#{r['password']}" #=> "foo:baz"
255
245
  end
256
246
  end
257
247
  end
@@ -355,11 +345,12 @@ This makes it easy to handle multiple strings without a Regexp:
355
345
 
356
346
  Hashes allow easily calling specialized match methods on the request.
357
347
  The default registered matchers included with Roda are documented below.
358
- You can add your own hash matchers using the +hash_matcher+ class method,
359
- which creates an appropriate request match method. The +hash_matcher+
360
- block will be called with the value of the hash.
348
+ Some plugins add additional hash matchers, and the hash_matcher plugin
349
+ allows for easily defining your own:
361
350
 
362
351
  class App < Roda
352
+ plugin :hash_matcher
353
+
363
354
  hash_matcher(:foo) do |v|
364
355
  # ...
365
356
  end
@@ -393,15 +384,6 @@ is so you can use it inside an array matcher, so:
393
384
 
394
385
  would match +/foo+ and +/foos/10+, but not +/foos+.
395
386
 
396
- ==== :extension
397
-
398
- The +:extension+ matcher matches any nonempty path ending with the given extension:
399
-
400
- {:extension => "css"} # matches "/foo.css", "/bar.css"
401
- {:extension => "css"} # does not match "/foo.css/x", "/foo.bar", "/.css"
402
-
403
- This matcher yields the part found before the period and extension (e.g., +foo+).
404
-
405
387
  ==== :method
406
388
 
407
389
  The +:method+ matcher matches the method of the request.
@@ -410,20 +392,6 @@ You can provide an array to specify multiple request methods and match on any of
410
392
  {:method => :post} # matches POST
411
393
  {:method => ['post', 'patch']} # matches POST and PATCH
412
394
 
413
- ==== :param
414
-
415
- The +:param+ matcher matches if the given parameter is present (even if it is empty).
416
-
417
- {:param => "user"} # matches "/foo?user=bar", "/foo?user="
418
- {:param => "user"} # does not matches "/foo"
419
-
420
- ==== :param!
421
-
422
- The +:param!+ matcher matches if the given parameter is present and not empty.
423
-
424
- {:param! => "user"} # matches "/foo?user=bar"
425
- {:param! => "user"} # does not matches "/foo", "/foo?user="
426
-
427
395
  === false, nil
428
396
 
429
397
  If +false+ or +nil+ is given directly as a matcher, it doesn't match anything.
@@ -452,8 +420,11 @@ via the +status+ attribute for the response.
452
420
 
453
421
  == Verb Methods
454
422
 
455
- The main match method is +r.on+, but as displayed above,
456
- you can also use +r.get+ or +r.post+.
423
+ As displayed above, Roda has +r.get+ and +r.post+ methods
424
+ for matching based on the HTTP request method. If you want
425
+ to match on other HTTP request methods, use the all_verbs
426
+ plugin.
427
+
457
428
  When called without any arguments, these match as long
458
429
  as the request has the appropriate method, so:
459
430
 
@@ -467,7 +438,7 @@ matches any +POST+ request
467
438
 
468
439
  If any arguments are given to the method, these match only
469
440
  if the request method matches, all arguments match, and
470
- only the path has been fully matched by the arguments, so:
441
+ the path has been fully matched by the arguments, so:
471
442
 
472
443
  r.post "" do end
473
444
 
@@ -506,7 +477,8 @@ Unlike the other matching methods, +r.root+ takes no arguments.
506
477
  Note that +r.root+ does not match if the path is empty;
507
478
  you should use <tt>r.get true</tt> for that.
508
479
  If you want to match either the the empty path or +/+,
509
- you can use <tt>r.get ["", true]</tt>.
480
+ you can use <tt>r.get ["", true]</tt>, or use the slash_path_empty
481
+ plugin.
510
482
 
511
483
  Note that +r.root+ only matches +GET+ requests.
512
484
  So, to handle <tt>POST /</tt> requests, use <tt>r.post ''</tt>.
@@ -519,17 +491,15 @@ Likewise, the response object is available via the +response+ method.
519
491
 
520
492
  The request object is an instance of a subclass of <tt>Rack::Request</tt>,
521
493
  with some additional methods.
522
- The response object is an instance of a subclass of <tt>Rack::Response</tt>,
523
- with some additional methods.
524
494
 
525
495
  If you want to extend the request and response objects with additional modules,
526
- you can do so via the +request_module+ or +response_module+ methods, or via plugins.
496
+ you can use the module_include plugin.
527
497
 
528
498
  == Pollution
529
499
 
530
500
  Roda tries very hard to avoid polluting the scope of the +route+ block.
531
501
  This should make it unlikely that Roda will cause namespace issues
532
- with your application code:
502
+ with your application code. Some of the things Roda does
533
503
 
534
504
  - The only instance variables defined by default in the scope of the +route+ block
535
505
  are <tt>@_request</tt> and <tt>@_response</tt>.
@@ -538,40 +508,12 @@ with your application code:
538
508
  - Constants inside the Roda namespace are all prefixed with +Roda+
539
509
  (e.g., <tt>Roda::RodaRequest</tt>).
540
510
 
541
- == Captures
542
-
543
- You may have noticed that some matchers yield a value to the block.
544
- The rules for determining if a matcher will yield a value are simple:
545
-
546
- 1. Regexp captures: <tt>/posts\/(\d+)-(.*)/</tt> will yield two values, corresponding to each capture.
547
- 2. String placeholders: <tt>"users/:id"</tt> will yield the value in the position of +:id+.
548
- 3. Symbols: +:foobar+ will yield if a segment is available.
549
- 4. File extensions: <tt>:extension=>"css"</tt> will yield the basename of the matched file.
550
- 5. Parameters: <tt>:param=>"user"</tt> will yield the value of the parameter +user+, if present.
551
-
552
- The first case is important because it shows the underlying effect of Regexp captures.
553
-
554
- In the second case, the substring +:id+ gets replaced by <tt>([^\\/]+)</tt>
555
- and the Regexp becomes <tt>/users\/([^\/]+)/</tt> before performing the match.
556
- Thus, it reverts to the first form we saw.
557
-
558
- In the third case, the symbol, no matter what it says,
559
- gets replaced by <tt>/([^\\/]+)/</tt>, and again we are in presence of case 1.
560
-
561
- The fourth case, again, reverts to the basic matcher: it generates the string
562
- <tt>/([^\/]+?)\.#{ext}\z/</tt> before performing the match.
563
-
564
- The fifth case is different: it checks if the parameter supplied is present
565
- in the request (via POST or QUERY_STRING) and it pushes the value as a capture.
566
-
567
511
  == Composition
568
512
 
569
513
  You can mount any Rack app (including another Roda app), with its own middlewares,
570
514
  inside a Roda app, using +r.run+:
571
515
 
572
516
  class API < Roda
573
- use SomeMiddleware
574
-
575
517
  route do |r|
576
518
  r.is do
577
519
  # ...
@@ -597,6 +539,10 @@ When you use +r.run+, Roda calls the given Rack app (+API+ in this case);
597
539
  whatever the Rack app returns will be returned
598
540
  as the response for the current application.
599
541
 
542
+ If you have a lot of rack applications that you want to dispatch to, and
543
+ which one to dispatch to is based on the request path prefix, look into the
544
+ +multi_run+ plugin.
545
+
600
546
  === multi_route plugin
601
547
 
602
548
  If you are just looking to split up the main route block up by branches,
@@ -651,10 +597,15 @@ Note that when subclassing, Roda only does a shallow clone of the settings.
651
597
  If you store nested structures and plan to mutate them in subclasses,
652
598
  it is your responsibility to dup the nested structures inside +Roda.inherited+
653
599
  (making sure to call +super+).
600
+
654
601
  The plugins that ship with Roda all handle this.
655
602
  Also, note that this means that modifications to the parent class
656
603
  made after subclassing do _not_ affect the subclass.
657
604
 
605
+ The plugins that ship with Roda freeze their settings and only allow modification
606
+ to their settings by reloading the plugin, and external plugins are encouraged
607
+ to follow this approach.
608
+
658
609
  == Rendering
659
610
 
660
611
  Roda ships with a +render+ plugin that provides helpers for rendering templates.
@@ -675,13 +626,13 @@ By default, +view+ will render the template inside the default layout template;
675
626
  route do |r|
676
627
  @var = '1'
677
628
 
678
- r.is "render" do
629
+ r.get "render" do
679
630
  # Renders the views/home.erb template, which will have access to
680
631
  # the instance variable @var, as well as local variable content.
681
632
  render("home", :locals=>{:content => "hello, world"})
682
633
  end
683
634
 
684
- r.is "view" do
635
+ r.get "view" do
685
636
  @var2 = '1'
686
637
 
687
638
  # Renders the views/home.erb template, which will have access to the
@@ -697,13 +648,11 @@ You can override the default rendering options by passing a hash to the plugin:
697
648
 
698
649
  class App < Roda
699
650
  plugin :render,
700
- :cache => false, # Disable template caching, useful in development
701
- :engine => 'erb', # Tilt engine/template file extension to use
702
- :escape => true, # Automatically escape output in erb templates
703
- :layout => "admin_layout", # Default layout template
704
- :layout_opts => {:ext=>'html.erb'}, # Default layout template options
705
- :opts => {:default_encoding=>'UTF-8'}, # Default template options
706
- :views => 'admin_views' # Default views directory
651
+ :escape => true, # Automatically escape output in erb templates
652
+ :views => 'admin_views' # Default views directory
653
+ :layout_opts => {:template=>'admin_layout',
654
+ :ext=>'html.erb'}, # Default layout template options
655
+ :template_opts => {:default_encoding=>'UTF-8'}, # Default template options
707
656
  end
708
657
 
709
658
  == Sessions
@@ -760,27 +709,13 @@ to escape by default, so that in your templates:
760
709
 
761
710
  This support requires {Erubis}[http://www.kuwata-lab.com/erubis/].
762
711
 
763
- === Other
764
-
765
- For prevention of some other vulnerabilities,
766
- such as click-jacking, directory traversal, session hijacking, and IP spoofing,
767
- consider using {Rack::Protection}[https://github.com/rkh/rack-protection].
768
- This is a Rack middleware that can be added in the usual way:
769
-
770
- require 'roda'
771
- require 'rack/protection'
772
-
773
- class App < Roda
774
- use Rack::Protection
775
- end
776
-
777
712
  == Plugins
778
713
 
779
714
  By design, Roda has a very small core, providing only the essentials.
780
715
  All nonessential features are added via plugins.
781
716
  This is why Roda is referred to as a routing tree web framework toolkit.
782
717
  Using a combination of Roda plugins,
783
- you can build the routing tree web framework that needs your needs.
718
+ you can build the routing tree web framework that suits your needs.
784
719
 
785
720
  Roda's plugins can override any Roda method and call +super+
786
721
  to get the default behavior, which makes Roda very extensible.
@@ -788,14 +723,6 @@ to get the default behavior, which makes Roda very extensible.
788
723
  {Roda ships with a large number of plugins}[http://roda.jeremyevans.net/documentation.html#included-plugins],
789
724
  and {some other libraries ship with support for Roda}[http://roda.jeremyevans.net/documentation.html#external].
790
725
 
791
- === External Plugins
792
-
793
- The following libraries include Roda plugins:
794
-
795
- forme :: Adds support for easy HTML form creation in erb templates.
796
- autoforme :: Adds support for easily creating a simple administrative front
797
- end for Sequel models.
798
-
799
726
  === How to create plugins
800
727
 
801
728
  Authoring your own plugins is pretty straightforward.
@@ -857,7 +784,9 @@ To avoid namespace pollution,
857
784
  you should avoid creating your module directly in the +Roda+ namespace.
858
785
  Additionally, any instance variables created inside +InstanceMethods+
859
786
  should be prefixed with an underscore (e.g., <tt>@_variable</tt>)
860
- to avoid polluting the scope.
787
+ to avoid polluting the scope. Finally, do not add any constants inside
788
+ the InstanceMethods module, add constants to the plugin module itself
789
+ (+Markdown+ in the above example).
861
790
 
862
791
  == License
863
792
 
data/doc/conventions.rdoc CHANGED
@@ -1,8 +1,8 @@
1
1
  = Conventions
2
2
 
3
3
  This guide goes over conventions for directory layout and file layout for Roda applications.
4
- You are free to ignore these conventions, but following them will make your code similar
5
- to other Roda applications.
4
+ You are free to ignore these conventions, they mostly exist to help users who are unsure how
5
+ to structure their Roda applications.
6
6
 
7
7
  == Directory Layout
8
8
 
@@ -78,14 +78,16 @@ Large applications generally need more structure:
78
78
 
79
79
  For larger apps, the +Rakefile+, +assets/+, +migrate+, +models.rb+, +models/+, +public/+, remain the same.
80
80
 
81
- +app_name.rb+ should use the +multi_route+ and +view_subdirs+ plugins. The routes used by the +multi_route+
82
- plugin should be stored in routing files in the +routes/+ directory, with one file per prefix.
81
+ +app_name.rb+ should use the +multi_route+ and +view_subdirs+ plugin, or the +multi_run+ plugin.
82
+ The routes used by the +multi_route+ or +multi_run+ should be stored in routing files in the +routes/+
83
+ directory, with one file per prefix.
83
84
 
84
85
  For specs/tests, you should have +spec/models/+ and +spec/web/+, with one file per model in +spec/models/+
85
86
  and one file per prefix in +spec/web/+.
86
87
 
87
- You should have a separate view subdirectory per prefix, and use +set_view_subdir+ in your routing files
88
- to specify the subdirectory to use, so it doesn't need to be specified on every call to view.
88
+ You should have a separate view subdirectory per prefix. If you are using +multi_route+ and +view_subdirs+,
89
+ use +set_view_subdir+ in your routing files to specify the subdirectory to use, so it doesn't need to be
90
+ specified on every call to view.
89
91
 
90
92
  +helpers/+ should be used to store helper methods for your application, that you call in your routing files
91
93
  and views. In a small application, these methods should just be specified in +app_name.rb+
@@ -93,8 +95,8 @@ and views. In a small application, these methods should just be specified in +a
93
95
  === Really Large Applications
94
96
 
95
97
  For very large applications, it's expected that there will be deviations from these conventions. However,
96
- it is recommended to use namespaces in the +multi_route+ plugin, and have subdirectories in the +routes/+
97
- directory, and nested subdirectories in the +views/+ directory.
98
+ it is recommended to use the +multi_route+ or +multi_run+ plugins to organize your application, and have
99
+ subdirectories in the +routes/+ directory, and nested subdirectories in the +views/+ directory.
98
100
 
99
101
  == Roda Application File Layout
100
102