roda 1.0.0 → 1.1.0

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 +4 -4
  2. data/CHANGELOG +34 -0
  3. data/README.rdoc +18 -13
  4. data/Rakefile +8 -0
  5. data/doc/conventions.rdoc +163 -0
  6. data/doc/release_notes/1.1.0.txt +226 -0
  7. data/lib/roda.rb +51 -22
  8. data/lib/roda/plugins/assets.rb +613 -0
  9. data/lib/roda/plugins/caching.rb +215 -0
  10. data/lib/roda/plugins/chunked.rb +278 -0
  11. data/lib/roda/plugins/error_email.rb +112 -0
  12. data/lib/roda/plugins/flash.rb +3 -3
  13. data/lib/roda/plugins/hooks.rb +1 -1
  14. data/lib/roda/plugins/indifferent_params.rb +3 -3
  15. data/lib/roda/plugins/middleware.rb +3 -8
  16. data/lib/roda/plugins/multi_route.rb +110 -18
  17. data/lib/roda/plugins/not_allowed.rb +3 -3
  18. data/lib/roda/plugins/path.rb +38 -0
  19. data/lib/roda/plugins/render.rb +18 -16
  20. data/lib/roda/plugins/render_each.rb +0 -2
  21. data/lib/roda/plugins/streaming.rb +1 -2
  22. data/lib/roda/plugins/view_subdirs.rb +7 -1
  23. data/lib/roda/version.rb +1 -1
  24. data/spec/assets/css/app.scss +1 -0
  25. data/spec/assets/css/no_access.css +1 -0
  26. data/spec/assets/css/raw.css +1 -0
  27. data/spec/assets/js/head/app.js +1 -0
  28. data/spec/integration_spec.rb +95 -3
  29. data/spec/matchers_spec.rb +2 -2
  30. data/spec/plugin/assets_spec.rb +413 -0
  31. data/spec/plugin/caching_spec.rb +335 -0
  32. data/spec/plugin/chunked_spec.rb +182 -0
  33. data/spec/plugin/default_headers_spec.rb +6 -5
  34. data/spec/plugin/error_email_spec.rb +76 -0
  35. data/spec/plugin/multi_route_spec.rb +120 -0
  36. data/spec/plugin/not_allowed_spec.rb +14 -3
  37. data/spec/plugin/path_spec.rb +29 -0
  38. data/spec/plugin/render_each_spec.rb +6 -1
  39. data/spec/plugin/symbol_matchers_spec.rb +7 -2
  40. data/spec/request_spec.rb +10 -0
  41. data/spec/response_spec.rb +47 -0
  42. data/spec/views/about.erb +1 -0
  43. data/spec/views/about.str +1 -0
  44. data/spec/views/content-yield.erb +1 -0
  45. data/spec/views/home.erb +2 -0
  46. data/spec/views/home.str +2 -0
  47. data/spec/views/layout-alternative.erb +2 -0
  48. data/spec/views/layout-yield.erb +3 -0
  49. data/spec/views/layout.erb +2 -0
  50. data/spec/views/layout.str +2 -0
  51. metadata +57 -2
@@ -7,6 +7,7 @@ describe "default_headers plugin" do
7
7
  app(:bare) do
8
8
  plugin :default_headers, h
9
9
  route do |r|
10
+ r.halt response.finish_with_body([])
10
11
  end
11
12
  end
12
13
 
@@ -22,6 +23,7 @@ describe "default_headers plugin" do
22
23
  plugin :default_headers
23
24
 
24
25
  route do |r|
26
+ r.halt response.finish_with_body([])
25
27
  end
26
28
  end
27
29
 
@@ -29,18 +31,17 @@ describe "default_headers plugin" do
29
31
  end
30
32
 
31
33
  it "should allow modifying the default headers at a later point" do
32
- h = {'Content-Type'=>'text/json', 'Foo'=>'bar'}
33
-
34
34
  app(:bare) do
35
35
  plugin :default_headers
36
36
  default_headers['Content-Type'] = 'text/json'
37
- default_headers['Foo'] = 'bar'
37
+ default_headers['Foo'] = 'baz'
38
38
 
39
39
  route do |r|
40
+ r.halt response.finish_with_body([])
40
41
  end
41
42
  end
42
43
 
43
- req[1].should == h
44
+ req[1].should == {'Content-Type'=>'text/json', 'Foo'=>'baz'}
44
45
  end
45
46
 
46
47
  it "should work correctly in subclasses" do
@@ -52,11 +53,11 @@ describe "default_headers plugin" do
52
53
  default_headers['Foo'] = 'bar'
53
54
 
54
55
  route do |r|
56
+ r.halt response.finish_with_body([])
55
57
  end
56
58
  end
57
59
 
58
60
  @app = Class.new(@app)
59
- @app.route{}
60
61
 
61
62
  req[1].should == h
62
63
  end
@@ -0,0 +1,76 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "error_email plugin" do
4
+ def app(opts={})
5
+ @emails = emails = [] unless defined?(@emails)
6
+ @app ||= super(:bare) do
7
+ plugin :error_email, {:to=>'t', :from=>'f', :emailer=>lambda{|h| emails << h}}.merge(opts)
8
+
9
+ route do |r|
10
+ raise ArgumentError rescue error_email($!)
11
+ 'e'
12
+ end
13
+ end
14
+ end
15
+
16
+ def email
17
+ @emails.last
18
+ end
19
+
20
+ it "adds error_email method for emailing exceptions" do
21
+ app
22
+ body('rack.input'=>StringIO.new, 'QUERY_STRING'=>'b=c', 'rack.session'=>{'d'=>'e'}).should == 'e'
23
+ email[:to].should == 't'
24
+ email[:from].should == 'f'
25
+ email[:host].should == 'localhost'
26
+ email[:message].should =~ /^Subject: ArgumentError/
27
+ email[:message].should =~ /^Backtrace:$.+^ENV:$.+^"rack\.input" => .+^Params:$\s+^"b" => "c"$\s+^Session:$\s+^"d" => "e"$/m
28
+ end
29
+
30
+ it "uses :host option" do
31
+ app(:host=>'foo.bar.com')
32
+ body('rack.input'=>StringIO.new).should == 'e'
33
+ email[:host].should == 'foo.bar.com'
34
+ end
35
+
36
+ it "handles error messages with new lines" do
37
+ app.route do |r|
38
+ raise "foo\nbar\nbaz" rescue error_email($!)
39
+ 'e'
40
+ end
41
+ body('rack.input'=>StringIO.new).should == 'e'
42
+ email[:message].should =~ %r{From: f\r\nSubject: RuntimeError: foo\r\n bar\r\n baz\r\nTo: t\r\n\r\n}
43
+ end
44
+
45
+ it "adds :prefix option to subject line" do
46
+ app(:prefix=>'TEST ')
47
+ body('rack.input'=>StringIO.new).should == 'e'
48
+ email[:message].should =~ /^Subject: TEST ArgumentError/
49
+ end
50
+
51
+ it "uses :headers option for additional headers" do
52
+ app(:headers=>{'Foo'=>'Bar', 'Baz'=>'Quux'})
53
+ body('rack.input'=>StringIO.new).should == 'e'
54
+ email[:message].should =~ /^Foo: Bar/
55
+ email[:message].should =~ /^Baz: Quux/
56
+ end
57
+
58
+ it "requires the :to and :from options" do
59
+ proc{app :from=>nil}.should raise_error(Roda::RodaError)
60
+ proc{app :to=>nil}.should raise_error(Roda::RodaError)
61
+ end
62
+
63
+ it "works correctly in subclasses" do
64
+ @app = Class.new(app)
65
+ @app.route do |r|
66
+ raise ArgumentError rescue error_email($!)
67
+ 'e'
68
+ end
69
+ body('rack.input'=>StringIO.new).should == 'e'
70
+ email[:to].should == 't'
71
+ email[:from].should == 'f'
72
+ email[:host].should == 'localhost'
73
+ email[:message].should =~ /^Subject: ArgumentError/
74
+ email[:message].should =~ /Backtrace.*ENV/m
75
+ end
76
+ end
@@ -13,6 +13,8 @@ describe "multi_route plugin" do
13
13
  r.is "a" do
14
14
  "geta"
15
15
  end
16
+
17
+ "getd"
16
18
  end
17
19
 
18
20
  route("post") do |r|
@@ -23,6 +25,14 @@ describe "multi_route plugin" do
23
25
  r.is "a" do
24
26
  "posta"
25
27
  end
28
+
29
+ "postd"
30
+ end
31
+
32
+ route(:p) do |r|
33
+ r.is do
34
+ 'p'
35
+ end
26
36
  end
27
37
 
28
38
  route do |r|
@@ -30,6 +40,10 @@ describe "multi_route plugin" do
30
40
  r.multi_route do
31
41
  "foo"
32
42
  end
43
+
44
+ r.on "p" do
45
+ r.route(:p)
46
+ end
33
47
  end
34
48
 
35
49
  r.get do
@@ -71,6 +85,29 @@ describe "multi_route plugin" do
71
85
  body('/foo/post/b').should == 'foo'
72
86
  end
73
87
 
88
+ it "does not have multi_route match non-String named routes" do
89
+ body('/foo/p').should == 'p'
90
+ status('/foo/p/2').should == 404
91
+ end
92
+
93
+ it "has multi_route pick up routes newly added" do
94
+ body('/foo/get/').should == 'get'
95
+ status('/foo/delete').should == 404
96
+ app.route('delete'){|r| r.on{'delete'}}
97
+ body('/foo/delete').should == 'delete'
98
+ end
99
+
100
+ it "makes multi_route match longest route if multiple routes have the same prefix" do
101
+ app.route("post/a"){|r| r.on{"pa2"}}
102
+ app.route("get/a"){|r| r.on{"ga2"}}
103
+ status('/foo').should == 404
104
+ body('/foo/get/').should == 'get'
105
+ body('/foo/get/a').should == 'ga2'
106
+ body('/foo/post/').should == 'post'
107
+ body('/foo/post/a').should == 'pa2'
108
+ body('/foo/post/b').should == 'foo'
109
+ end
110
+
74
111
  it "handles loading the plugin multiple times correctly" do
75
112
  app.plugin :multi_route
76
113
  body.should == 'get'
@@ -111,4 +148,87 @@ describe "multi_route plugin" do
111
148
  status('/c').should == 404
112
149
  status('/c', 'REQUEST_METHOD'=>'POST').should == 404
113
150
  end
151
+
152
+ it "uses the named route return value in multi_route if no block is given" do
153
+ app.route{|r| r.multi_route}
154
+ body('/get').should == 'getd'
155
+ body('/post').should == 'postd'
156
+ end
157
+ end
158
+
159
+ describe "multi_route plugin" do
160
+ before do
161
+ app(:bare) do
162
+ plugin :multi_route
163
+
164
+ route("foo", "foo") do |r|
165
+ "#{@p}ff"
166
+ end
167
+
168
+ route("bar", "foo") do |r|
169
+ "#{@p}fb"
170
+ end
171
+
172
+ route("foo", "bar") do |r|
173
+ "#{@p}bf"
174
+ end
175
+
176
+ route("bar", "bar") do |r|
177
+ "#{@p}bb"
178
+ end
179
+ end
180
+ end
181
+
182
+ it "handles namespaces in r.route" do
183
+ app.route("foo") do |r|
184
+ @p = 'f'
185
+ r.on("foo"){r.route("foo", "foo")}
186
+ r.on("bar"){r.route("bar", "foo")}
187
+ @p
188
+ end
189
+
190
+ app.route("bar") do |r|
191
+ @p = 'b'
192
+ r.on("foo"){r.route("foo", "bar")}
193
+ r.on("bar"){r.route("bar", "bar")}
194
+ @p
195
+ end
196
+
197
+ app.route do |r|
198
+ r.on("foo"){r.route("foo")}
199
+ r.on("bar"){r.route("bar")}
200
+ end
201
+
202
+ body('/foo').should == 'f'
203
+ body('/foo/foo').should == 'fff'
204
+ body('/foo/bar').should == 'ffb'
205
+ body('/bar').should == 'b'
206
+ body('/bar/foo').should == 'bbf'
207
+ body('/bar/bar').should == 'bbb'
208
+ end
209
+
210
+ it "handles namespaces in r.multi_route" do
211
+ app.route("foo") do |r|
212
+ @p = 'f'
213
+ r.multi_route("foo")
214
+ @p
215
+ end
216
+
217
+ app.route("bar") do |r|
218
+ @p = 'b'
219
+ r.multi_route("bar")
220
+ @p
221
+ end
222
+
223
+ app.route do |r|
224
+ r.multi_route
225
+ end
226
+
227
+ body('/foo').should == 'f'
228
+ body('/foo/foo').should == 'fff'
229
+ body('/foo/bar').should == 'ffb'
230
+ body('/bar').should == 'b'
231
+ body('/bar/foo').should == 'bbf'
232
+ body('/bar/bar').should == 'bbb'
233
+ end
114
234
  end
@@ -19,14 +19,22 @@ describe "not_allowed plugin" do
19
19
  "c"
20
20
  end
21
21
 
22
+ r.on "q" do
23
+ r.is do
24
+ r.get do
25
+ "q"
26
+ end
27
+ end
28
+ end
29
+
22
30
  r.get do
23
31
  r.is 'b' do
24
32
  'b'
25
33
  end
26
- r.is /(d)/ do |s|
34
+ r.is(/(d)/) do |s|
27
35
  s
28
36
  end
29
- r.get /(e)/ do |s|
37
+ r.get(/(e)/) do |s|
30
38
  s
31
39
  end
32
40
  end
@@ -43,7 +51,10 @@ describe "not_allowed plugin" do
43
51
  status('/d', 'REQUEST_METHOD'=>'POST').should == 404
44
52
 
45
53
  body('/e').should == 'e'
46
- status('/d', 'REQUEST_METHOD'=>'POST').should == 404
54
+ status('/e', 'REQUEST_METHOD'=>'POST').should == 404
55
+
56
+ body('/q').should == 'q'
57
+ status('/q', 'REQUEST_METHOD'=>'POST').should == 405
47
58
 
48
59
  body('/c').should == 'cg'
49
60
  body('/c').should == 'cg'
@@ -0,0 +1,29 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "path plugin" do
4
+ it "adds path method for defining named paths" do
5
+ app(:bare) do
6
+ plugin :path
7
+ path :foo, "/foo"
8
+ path :bar do |o|
9
+ "/bar/#{o}"
10
+ end
11
+
12
+ route do |r|
13
+ "#{foo_path}#{bar_path('a')}"
14
+ end
15
+ end
16
+
17
+ body.should == '/foo/bar/a'
18
+ end
19
+
20
+ it "raises if both path and block are given" do
21
+ app.plugin :path
22
+ proc{app.path(:foo, '/foo'){}}.should raise_error(Roda::RodaError)
23
+ end
24
+
25
+ it "raises if neither path nor block are given" do
26
+ app.plugin :path
27
+ proc{app.path(:foo)}.should raise_error(Roda::RodaError)
28
+ end
29
+ end
@@ -5,7 +5,7 @@ describe "render_each plugin" do
5
5
  app(:bare) do
6
6
  plugin :render_each
7
7
  def render(t, opts)
8
- "r#{t}#{opts[:locals][:foo] if opts[:locals]}#{opts[:bar]} "
8
+ "r#{t}#{opts[:locals][:foo] if opts[:locals]}#{opts[:bar]}#{opts[:locals][:bar] if opts[:locals]} "
9
9
  end
10
10
 
11
11
  route do |r|
@@ -20,11 +20,16 @@ describe "render_each plugin" do
20
20
  r.is 'b' do
21
21
  render_each([1,2,3], :bar, :local=>nil)
22
22
  end
23
+
24
+ r.is 'c' do
25
+ render_each([1,2,3], :bar, :locals=>{:foo=>4})
26
+ end
23
27
  end
24
28
  end
25
29
 
26
30
  body.should == 'rfoo1 rfoo2 rfoo3 '
27
31
  body("/a").should == 'rbar14 rbar24 rbar34 '
28
32
  body("/b").should == 'rbar rbar rbar '
33
+ body("/c").should == 'rbar41 rbar42 rbar43 '
29
34
  end
30
35
  end
@@ -23,12 +23,16 @@ describe "symbol_matchers plugin" do
23
23
  "format#{f.inspect}"
24
24
  end
25
25
 
26
+ r.is "thing/:thing" do |d|
27
+ "thing#{d}"
28
+ end
29
+
26
30
  r.is :f do |f|
27
31
  "f#{f}"
28
32
  end
29
33
 
30
- r.is 'q:rest' do |r|
31
- "rest#{r}"
34
+ r.is 'q:rest' do |rest|
35
+ "rest#{rest}"
32
36
  end
33
37
 
34
38
  r.is :w do |w|
@@ -64,5 +68,6 @@ describe "symbol_matchers plugin" do
64
68
  status("/1/f/a").should == 404
65
69
  body("/qa/b/c/d//f/g").should == 'resta/b/c/d//f/g'
66
70
  body('/q').should == 'rest'
71
+ body('/thing/q').should == 'thingq'
67
72
  end
68
73
  end
data/spec/request_spec.rb CHANGED
@@ -62,3 +62,13 @@ describe "request.inspect" do
62
62
  body('REQUEST_METHOD'=>'POST').should == "#<Foo::RodaRequest POST />"
63
63
  end
64
64
  end
65
+
66
+ describe "TERM.inspect" do
67
+ it "should return TERM" do
68
+ app do |r|
69
+ r.class::TERM.inspect
70
+ end
71
+
72
+ body.should == "TERM"
73
+ end
74
+ end
@@ -67,6 +67,16 @@ describe "response #finish" do
67
67
  body.should == 'a200text/html1'
68
68
  end
69
69
 
70
+ it "should set Content-Length header" do
71
+ app do |r|
72
+ response.write 'a'
73
+ response['Content-Length'].should == nil
74
+ throw :halt, response.finish
75
+ end
76
+
77
+ header('Content-Length').should == '1'
78
+ end
79
+
70
80
  it "should not overwrite existing status" do
71
81
  app do |r|
72
82
  response.status = 500
@@ -78,6 +88,43 @@ describe "response #finish" do
78
88
  end
79
89
  end
80
90
 
91
+ describe "response #finish_with_body" do
92
+ it "should use given body" do
93
+ app do |r|
94
+ throw :halt, response.finish_with_body(['123'])
95
+ end
96
+
97
+ body.should == '123'
98
+ end
99
+
100
+ it "should set status to 200 if status has not been set" do
101
+ app do |r|
102
+ throw :halt, response.finish_with_body([])
103
+ end
104
+
105
+ status.should == 200
106
+ end
107
+
108
+ it "should not set Content-Length header" do
109
+ app do |r|
110
+ response.write 'a'
111
+ response['Content-Length'].should == nil
112
+ throw :halt, response.finish_with_body(['123'])
113
+ end
114
+
115
+ header('Content-Length').should == nil
116
+ end
117
+
118
+ it "should not overwrite existing status" do
119
+ app do |r|
120
+ response.status = 500
121
+ throw :halt, response.finish_with_body(['123'])
122
+ end
123
+
124
+ status.should == 500
125
+ end
126
+ end
127
+
81
128
  describe "response #redirect" do
82
129
  it "should set location and status" do
83
130
  app do |r|