roda 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +42 -0
- data/README.rdoc +73 -144
- data/doc/conventions.rdoc +10 -8
- data/doc/release_notes/1.3.0.txt +109 -0
- data/lib/roda.rb +67 -100
- data/lib/roda/plugins/assets.rb +4 -4
- data/lib/roda/plugins/chunked.rb +4 -1
- data/lib/roda/plugins/class_level_routing.rb +7 -1
- data/lib/roda/plugins/cookies.rb +34 -0
- data/lib/roda/plugins/default_headers.rb +7 -6
- data/lib/roda/plugins/delegate.rb +8 -1
- data/lib/roda/plugins/delete_empty_headers.rb +33 -0
- data/lib/roda/plugins/delete_nil_headers.rb +34 -0
- data/lib/roda/plugins/environments.rb +12 -4
- data/lib/roda/plugins/error_email.rb +6 -1
- data/lib/roda/plugins/error_handler.rb +7 -4
- data/lib/roda/plugins/hash_matcher.rb +32 -0
- data/lib/roda/plugins/header_matchers.rb +12 -2
- data/lib/roda/plugins/json.rb +9 -6
- data/lib/roda/plugins/module_include.rb +92 -0
- data/lib/roda/plugins/multi_route.rb +7 -0
- data/lib/roda/plugins/multi_run.rb +11 -5
- data/lib/roda/plugins/named_templates.rb +7 -1
- data/lib/roda/plugins/not_found.rb +6 -0
- data/lib/roda/plugins/param_matchers.rb +43 -0
- data/lib/roda/plugins/path_matchers.rb +51 -0
- data/lib/roda/plugins/render.rb +32 -14
- data/lib/roda/plugins/static_path_info.rb +10 -3
- data/lib/roda/plugins/symbol_matchers.rb +1 -1
- data/lib/roda/version.rb +13 -1
- data/spec/freeze_spec.rb +28 -0
- data/spec/plugin/class_level_routing_spec.rb +26 -0
- data/spec/plugin/content_for_spec.rb +1 -2
- data/spec/plugin/cookies_spec.rb +25 -0
- data/spec/plugin/default_headers_spec.rb +4 -7
- data/spec/plugin/delegate_spec.rb +4 -1
- data/spec/plugin/delete_empty_headers_spec.rb +15 -0
- data/spec/plugin/error_handler_spec.rb +31 -0
- data/spec/plugin/hash_matcher_spec.rb +27 -0
- data/spec/plugin/header_matchers_spec.rb +15 -0
- data/spec/plugin/json_spec.rb +1 -2
- data/spec/plugin/mailer_spec.rb +2 -2
- data/spec/plugin/module_include_spec.rb +31 -0
- data/spec/plugin/multi_route_spec.rb +14 -0
- data/spec/plugin/multi_run_spec.rb +41 -0
- data/spec/plugin/named_templates_spec.rb +25 -0
- data/spec/plugin/not_found_spec.rb +29 -0
- data/spec/plugin/param_matchers_spec.rb +37 -0
- data/spec/plugin/path_matchers_spec.rb +42 -0
- data/spec/plugin/render_spec.rb +33 -8
- data/spec/plugin/static_path_info_spec.rb +6 -0
- data/spec/plugin/view_subdirs_spec.rb +1 -2
- data/spec/response_spec.rb +12 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/version_spec.rb +8 -2
- 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
|
data/spec/plugin/json_spec.rb
CHANGED
data/spec/plugin/mailer_spec.rb
CHANGED
@@ -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"
|
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", '
|
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
|
data/spec/plugin/render_spec.rb
CHANGED
@@ -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.
|
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.
|
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(:
|
129
|
-
|
130
|
-
|
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[:
|
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
|
|