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
@@ -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