roda 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -0,0 +1,15 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "delete_empty_headers plugin" do
4
+ it "automatically deletes headers that are empty" do
5
+ app(:delete_empty_headers) do |r|
6
+ response['Foo'] = ''
7
+ response['Content-Type'] = ''
8
+ response['Content-Length'] = ''
9
+ response['Bar'] = '1'
10
+ 'a'
11
+ end
12
+
13
+ req[1].should == {'Bar'=>'1'}
14
+ end
15
+ end
@@ -39,6 +39,37 @@ describe "error_handler plugin" do
39
39
  status.should == 501
40
40
  end
41
41
 
42
+ it "calculates correct Content-Length" do
43
+ app(:bare) do
44
+ plugin :error_handler do |e|
45
+ "a"
46
+ end
47
+
48
+ route do |r|
49
+ raise ArgumentError, "bad idea"
50
+ end
51
+ end
52
+
53
+ header('Content-Length').should == "1"
54
+ end
55
+
56
+ it "clears existing headers" do
57
+ app(:bare) do
58
+ plugin :error_handler do |e|
59
+ "a"
60
+ end
61
+
62
+ route do |r|
63
+ response['Content-Type'] = 'text/pdf'
64
+ response['Foo'] = 'bar'
65
+ raise ArgumentError, "bad idea"
66
+ end
67
+ end
68
+
69
+ header('Content-Type').should == 'text/html'
70
+ header('Foo').should == nil
71
+ end
72
+
42
73
  it "can set error via the plugin block" do
43
74
  app(:bare) do
44
75
  plugin :error_handler do |e|
@@ -0,0 +1,27 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "hash_matcher plugin" do
4
+ it "should enable the handling of arbitrary hash keys" do
5
+ app(:bare) do
6
+ plugin :hash_matcher
7
+ hash_matcher(:foos){|v| consume(self.class.cached_matcher(:"foos-#{v}"){/((?:foo){#{v}})/})}
8
+ route do |r|
9
+ r.is :foos=>1 do |f|
10
+ "1#{f}"
11
+ end
12
+ r.is :foos=>2 do |f|
13
+ "2#{f}"
14
+ end
15
+ r.is :foos=>3 do |f|
16
+ "3#{f}"
17
+ end
18
+ end
19
+ end
20
+
21
+ body("/foo").should == '1foo'
22
+ body("/foofoo").should == '2foofoo'
23
+ body("/foofoofoo").should == '3foofoofoo'
24
+ status("/foofoofoofoo").should == 404
25
+ status.should == 404
26
+ end
27
+ end
@@ -24,6 +24,21 @@ describe "header matcher" do
24
24
  body("HTTP_ACCEPT" => "application/xml").should == "bar"
25
25
  status.should == 404
26
26
  end
27
+
28
+ it "should yield the header value if :match_header_yield option is present" do
29
+ app(:bare) do
30
+ plugin :header_matchers
31
+ opts[:match_header_yield] = true
32
+ route do |r|
33
+ r.on :header=>"http-accept" do |v|
34
+ "bar-#{v}"
35
+ end
36
+ end
37
+ end
38
+
39
+ body("HTTP_ACCEPT" => "application/xml").should == "bar-application/xml"
40
+ status.should == 404
41
+ end
27
42
  end
28
43
 
29
44
  describe "host matcher" do
@@ -9,8 +9,7 @@ describe "json plugin" do
9
9
  end
10
10
 
11
11
  app(:bare) do
12
- plugin :json
13
- json_result_classes << c
12
+ plugin :json, :classes=>[Array, Hash, c]
14
13
 
15
14
  route do |r|
16
15
  r.is 'array' do
@@ -91,7 +91,7 @@ describe "mailer plugin" do
91
91
 
92
92
  it "supports regular web requests in same application" do
93
93
  app(:mailer) do |r|
94
- r.get "foo", :param=>'bar' do |bar|
94
+ r.get "foo/:bar" do |bar|
95
95
  "foo#{bar}"
96
96
  end
97
97
  r.mail "bar" do
@@ -100,7 +100,7 @@ describe "mailer plugin" do
100
100
  end
101
101
  end
102
102
 
103
- body("/foo", 'QUERY_STRING'=>'bar=baz', 'rack.input'=>StringIO.new).should == 'foobaz'
103
+ body("/foo/baz", 'rack.input'=>StringIO.new).should == 'foobaz'
104
104
  app.mail('/bar').body.should == 'b'
105
105
  end
106
106
 
@@ -0,0 +1,31 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "module_include plugin" do
4
+ it "should include given module in request or response class" do
5
+ app(:bare) do
6
+ plugin :module_include
7
+ request_module(Module.new{def h; halt response.finish end})
8
+ response_module(Module.new{def finish; [1, {}, []] end})
9
+
10
+ route do |r|
11
+ r.h
12
+ end
13
+ end
14
+
15
+ req.should == [1, {}, []]
16
+ end
17
+
18
+ it "should accept blocks and turn them into modules" do
19
+ app(:bare) do
20
+ plugin :module_include
21
+ request_module{def h; halt response.finish end}
22
+ response_module{def finish; [1, {}, []] end}
23
+
24
+ route do |r|
25
+ r.h
26
+ end
27
+ end
28
+
29
+ req.should == [1, {}, []]
30
+ end
31
+ end
@@ -76,6 +76,20 @@ describe "multi_route plugin" do
76
76
  status('/c', 'REQUEST_METHOD'=>'POST').should == 404
77
77
  end
78
78
 
79
+ it "works when freezing the app" do
80
+ app.freeze
81
+ body.should == 'get'
82
+ body('REQUEST_METHOD'=>'POST').should == 'post'
83
+ body('/a').should == 'geta'
84
+ body('/a', 'REQUEST_METHOD'=>'POST').should == 'posta'
85
+ body('/b').should == 'getb'
86
+ body('/b', 'REQUEST_METHOD'=>'POST').should == 'postb'
87
+ status('/c').should == 404
88
+ status('/c', 'REQUEST_METHOD'=>'POST').should == 404
89
+
90
+ proc{app.route("foo"){}}.should raise_error
91
+ end
92
+
79
93
  it "uses multi_route to dispatch to any named route" do
80
94
  status('/foo').should == 404
81
95
  body('/foo/get/').should == 'get'
@@ -28,4 +28,45 @@ describe "multi_run plugin" do
28
28
  body("/b/a").should == 'b2'
29
29
  body.should == 'c'
30
30
  end
31
+
32
+ it "works when freezing the app" do
33
+ app(:multi_run) do |r|
34
+ r.multi_run
35
+ "c"
36
+ end
37
+
38
+ app.run "a", Class.new(Roda).class_eval{route{"a1"}; app}
39
+ app.run "b", Class.new(Roda).class_eval{route{"b1"}; app}
40
+ app.run "b/a", Class.new(Roda).class_eval{route{"b2"}; app}
41
+ app.freeze
42
+
43
+ body("/a").should == 'a1'
44
+ body("/b").should == 'b1'
45
+ body("/b/a").should == 'b2'
46
+ body.should == 'c'
47
+
48
+ proc{app.run "a", Class.new(Roda).class_eval{route{"a1"}; app}}.should raise_error
49
+ end
50
+
51
+ it "works when subclassing" do
52
+ app(:multi_run) do |r|
53
+ r.multi_run
54
+ "c"
55
+ end
56
+
57
+ app.run "a", Class.new(Roda).class_eval{route{"a1"}; app}
58
+ body("/a").should == 'a1'
59
+
60
+ a = app
61
+ @app = Class.new(a)
62
+
63
+ a.run "b", Class.new(Roda).class_eval{route{"b2"}; app}
64
+ app.run "b", Class.new(Roda).class_eval{route{"b1"}; app}
65
+
66
+ body("/a").should == 'a1'
67
+ body("/b").should == 'b1'
68
+
69
+ @app = a
70
+ body("/b").should == 'b2'
71
+ end
31
72
  end
@@ -25,6 +25,31 @@ describe "named_templates plugin" do
25
25
  body.should == 'bar13-foo12-baz'
26
26
  end
27
27
 
28
+ it "works when freezing the app" do
29
+ app(:bare) do
30
+ plugin :named_templates
31
+
32
+ template :foo do
33
+ @b = 2
34
+ "foo<%= @a %><%= @b %>"
35
+ end
36
+ template :layout, :engine=>:str do
37
+ @c = 3
38
+ 'bar#{@a}#{@c}-#{yield}-baz'
39
+ end
40
+
41
+ route do |r|
42
+ @a = 1
43
+ view(:foo)
44
+ end
45
+ end
46
+
47
+ app.freeze
48
+ body.should == 'bar13-foo12-baz'
49
+
50
+ proc{app.template(:b){"a"}}.should raise_error
51
+ end
52
+
28
53
  it "works with the view_subdirs plugin" do
29
54
  app(:bare) do
30
55
  plugin :render
@@ -38,6 +38,35 @@ describe "not_found plugin" do
38
38
  status.should == 403
39
39
  end
40
40
 
41
+ it "calculates correct Content-Length" do
42
+ app(:bare) do
43
+ plugin :not_found do
44
+ "a"
45
+ end
46
+
47
+ route{}
48
+ end
49
+
50
+ header('Content-Length').should == "1"
51
+ end
52
+
53
+ it "clears existing headers" do
54
+ app(:bare) do
55
+ plugin :not_found do ||
56
+ "a"
57
+ end
58
+
59
+ route do |r|
60
+ response['Content-Type'] = 'text/pdf'
61
+ response['Foo'] = 'bar'
62
+ nil
63
+ end
64
+ end
65
+
66
+ header('Content-Type').should == 'text/html'
67
+ header('Foo').should == nil
68
+ end
69
+
41
70
  it "does not modify behavior if not_found is not called" do
42
71
  app(:not_found) do |r|
43
72
  r.on "a" do
@@ -0,0 +1,37 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "param_matchers plugin" do
4
+ it "param! matcher should yield a param only if given and not empty" do
5
+ app(:param_matchers) do |r|
6
+ r.get "signup", :param! => "email" do |email|
7
+ email
8
+ end
9
+
10
+ r.on do
11
+ "No email"
12
+ end
13
+ end
14
+
15
+ io = StringIO.new
16
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "email=john@doe.com").should == 'john@doe.com'
17
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "").should == 'No email'
18
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "email=").should == 'No email'
19
+ end
20
+
21
+ it "param matcheshould yield a param only if given" do
22
+ app(:param_matchers) do |r|
23
+ r.get "signup", :param=>"email" do |email|
24
+ email
25
+ end
26
+
27
+ r.on do
28
+ "No email"
29
+ end
30
+ end
31
+
32
+ io = StringIO.new
33
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "email=john@doe.com").should == 'john@doe.com'
34
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "").should == 'No email'
35
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "email=").should == ''
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "path_matchers plugin" do
4
+ it ":extension matcher should match given file extension" do
5
+ app(:path_matchers) do |r|
6
+ r.on "css" do
7
+ r.on :extension=>"css" do |file|
8
+ file
9
+ end
10
+ end
11
+ end
12
+
13
+ body("/css/reset.css").should == 'reset'
14
+ status("/css/reset.bar").should == 404
15
+ end
16
+
17
+ it ":suffix matcher should match given suffix" do
18
+ app(:path_matchers) do |r|
19
+ r.on "css" do
20
+ r.on :suffix=>".css" do |file|
21
+ file
22
+ end
23
+ end
24
+ end
25
+
26
+ body("/css/reset.css").should == 'reset'
27
+ status("/css/reset.bar").should == 404
28
+ end
29
+
30
+ it ":prefix matcher should match given prefix" do
31
+ app(:path_matchers) do |r|
32
+ r.on "css" do
33
+ r.on :prefix=>"reset" do |file|
34
+ file
35
+ end
36
+ end
37
+ end
38
+
39
+ body("/css/reset.css").should == '.css'
40
+ status("/css/foo.bar").should == 404
41
+ end
42
+ end
@@ -9,8 +9,7 @@ else
9
9
  describe "render plugin" do
10
10
  before do
11
11
  app(:bare) do
12
- plugin :render
13
- render_opts[:views] = "./spec/views"
12
+ plugin :render, :views=>"./spec/views"
14
13
 
15
14
  route do |r|
16
15
  r.on "home" do
@@ -45,14 +44,14 @@ describe "render plugin" do
45
44
  end
46
45
 
47
46
  it "with str as engine" do
48
- app.render_opts[:engine] = "str"
47
+ app.plugin :render, :engine => "str"
49
48
  body("/about").strip.should == "<h1>About Roda</h1>"
50
49
  body("/home").strip.should == "<title>Roda: Home</title>\n<h1>Home</h1>\n<p>Hello Agent Smith</p>"
51
50
  body("/inline").strip.should == "Hello <%= name %>"
52
51
  end
53
52
 
54
53
  it "with str as ext" do
55
- app.render_opts[:ext] = "str"
54
+ app.plugin :render, :ext => "str"
56
55
  body("/about").strip.should == "<h1>About Roda</h1>"
57
56
  body("/home").strip.should == "<title>Roda: Home</title>\n<h1>Home</h1>\n<p>Hello Agent Smith</p>"
58
57
  body("/inline").strip.should == "Hello Agent Smith"
@@ -125,9 +124,11 @@ describe "render plugin" do
125
124
  end
126
125
 
127
126
  it "template renders with :template opts" do
128
- app(:render) do
129
- render_opts[:views] = "./spec/views"
130
- render(:template=>"about", :locals=>{:title => "About Roda"})
127
+ app(:bare) do
128
+ plugin :render, :views => "./spec/views"
129
+ route do
130
+ render(:template=>"about", :locals=>{:title => "About Roda"})
131
+ end
131
132
  end
132
133
  body.strip.should == "<h1>About Roda</h1>"
133
134
  end
@@ -164,6 +165,30 @@ describe "render plugin" do
164
165
  body('/b').should == "i-b"
165
166
  end
166
167
 
168
+ it "template cache respects :template_opts" do
169
+ c = Class.new do
170
+ def initialize(path, _, opts)
171
+ @path = path
172
+ @opts = opts
173
+ end
174
+ def render(*)
175
+ "#{@path}-#{@opts[:foo]}"
176
+ end
177
+ end
178
+
179
+ app(:render) do |r|
180
+ r.is "a" do
181
+ render(:inline=>"i", :template_class=>c, :template_opts=>{:foo=>'a'})
182
+ end
183
+ r.is "b" do
184
+ render(:inline=>"i", :template_class=>c, :template_opts=>{:foo=>'b'})
185
+ end
186
+ end
187
+
188
+ body('/a').should == "i-a"
189
+ body('/b').should == "i-b"
190
+ end
191
+
167
192
  it "render_opts inheritance" do
168
193
  c = Class.new(Roda)
169
194
  c.plugin :render
@@ -171,7 +196,7 @@ describe "render plugin" do
171
196
 
172
197
  c.render_opts.should_not equal(sc.render_opts)
173
198
  c.render_opts[:layout_opts].should_not equal(sc.render_opts[:layout_opts])
174
- c.render_opts[:opts].should_not equal(sc.render_opts[:opts])
199
+ c.render_opts[:template_opts].should_not equal(sc.render_opts[:template_opts])
175
200
  c.render_opts[:cache].should_not equal(sc.render_opts[:cache])
176
201
  end
177
202