roda 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +70 -0
  3. data/README.rdoc +261 -302
  4. data/Rakefile +1 -1
  5. data/doc/release_notes/1.2.0.txt +406 -0
  6. data/lib/roda.rb +206 -124
  7. data/lib/roda/plugins/all_verbs.rb +11 -10
  8. data/lib/roda/plugins/assets.rb +5 -5
  9. data/lib/roda/plugins/backtracking_array.rb +12 -5
  10. data/lib/roda/plugins/caching.rb +10 -8
  11. data/lib/roda/plugins/class_level_routing.rb +94 -0
  12. data/lib/roda/plugins/content_for.rb +6 -0
  13. data/lib/roda/plugins/default_headers.rb +4 -11
  14. data/lib/roda/plugins/delay_build.rb +42 -0
  15. data/lib/roda/plugins/delegate.rb +64 -0
  16. data/lib/roda/plugins/drop_body.rb +33 -0
  17. data/lib/roda/plugins/empty_root.rb +48 -0
  18. data/lib/roda/plugins/environments.rb +68 -0
  19. data/lib/roda/plugins/error_email.rb +1 -2
  20. data/lib/roda/plugins/error_handler.rb +1 -1
  21. data/lib/roda/plugins/halt.rb +7 -5
  22. data/lib/roda/plugins/head.rb +4 -2
  23. data/lib/roda/plugins/header_matchers.rb +17 -9
  24. data/lib/roda/plugins/hooks.rb +16 -32
  25. data/lib/roda/plugins/json.rb +4 -10
  26. data/lib/roda/plugins/mailer.rb +233 -0
  27. data/lib/roda/plugins/match_affix.rb +48 -0
  28. data/lib/roda/plugins/multi_route.rb +9 -11
  29. data/lib/roda/plugins/multi_run.rb +81 -0
  30. data/lib/roda/plugins/named_templates.rb +93 -0
  31. data/lib/roda/plugins/not_allowed.rb +43 -48
  32. data/lib/roda/plugins/path.rb +63 -2
  33. data/lib/roda/plugins/render.rb +79 -48
  34. data/lib/roda/plugins/render_each.rb +6 -0
  35. data/lib/roda/plugins/sinatra_helpers.rb +523 -0
  36. data/lib/roda/plugins/slash_path_empty.rb +25 -0
  37. data/lib/roda/plugins/static_path_info.rb +64 -0
  38. data/lib/roda/plugins/streaming.rb +1 -1
  39. data/lib/roda/plugins/view_subdirs.rb +12 -8
  40. data/lib/roda/version.rb +1 -1
  41. data/spec/integration_spec.rb +33 -0
  42. data/spec/plugin/backtracking_array_spec.rb +24 -18
  43. data/spec/plugin/class_level_routing_spec.rb +138 -0
  44. data/spec/plugin/delay_build_spec.rb +23 -0
  45. data/spec/plugin/delegate_spec.rb +20 -0
  46. data/spec/plugin/drop_body_spec.rb +20 -0
  47. data/spec/plugin/empty_root_spec.rb +14 -0
  48. data/spec/plugin/environments_spec.rb +31 -0
  49. data/spec/plugin/h_spec.rb +1 -3
  50. data/spec/plugin/header_matchers_spec.rb +14 -0
  51. data/spec/plugin/hooks_spec.rb +3 -5
  52. data/spec/plugin/mailer_spec.rb +191 -0
  53. data/spec/plugin/match_affix_spec.rb +22 -0
  54. data/spec/plugin/multi_run_spec.rb +31 -0
  55. data/spec/plugin/named_templates_spec.rb +65 -0
  56. data/spec/plugin/path_spec.rb +66 -2
  57. data/spec/plugin/render_spec.rb +46 -1
  58. data/spec/plugin/sinatra_helpers_spec.rb +534 -0
  59. data/spec/plugin/slash_path_empty_spec.rb +22 -0
  60. data/spec/plugin/static_path_info_spec.rb +50 -0
  61. data/spec/request_spec.rb +23 -0
  62. data/spec/response_spec.rb +12 -1
  63. metadata +48 -6
@@ -0,0 +1,14 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "empty_root plugin" do
4
+ it "makes root match on emtpy path" do
5
+ app(:empty_root) do |r|
6
+ r.root{"root"}
7
+ "notroot"
8
+ end
9
+
10
+ body.should == 'root'
11
+ body("").should == 'root'
12
+ body("a").should == 'notroot'
13
+ end
14
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "environments plugin" do
4
+ before do
5
+ app
6
+ app.plugin :environments, :development
7
+ end
8
+
9
+ it "adds environment accessor for getting/setting the environment" do
10
+ app.environment.should == :development
11
+ app.environment = :test
12
+ app.environment.should == :test
13
+
14
+ app.plugin :environments, :production
15
+ app.environment.should == :production
16
+ end
17
+
18
+ it "adds predicates for testing the environment" do
19
+ app.development?.should == true
20
+ app.test?.should == false
21
+ app.production?.should == false
22
+ end
23
+
24
+ it "adds configure method which yields if no arguments are given or an environment matches" do
25
+ a = []
26
+ app.configure{a << 1}
27
+ app.configure(:development){|ap| a << ap}
28
+ app.configure(:test, :production){a << 2}
29
+ a.should == [1, app]
30
+ end
31
+ end
@@ -3,9 +3,7 @@ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
3
3
  describe "h plugin" do
4
4
  it "adds h method for html escaping" do
5
5
  app(:h) do |r|
6
- r.on do
7
- h("<form>") + h(:form)
8
- end
6
+ h("<form>") + h(:form)
9
7
  end
10
8
 
11
9
  body.should == '&lt;form&gt;form'
@@ -59,3 +59,17 @@ describe "host matcher" do
59
59
  body("HTTP_HOST" => "example.com").should == '0'
60
60
  end
61
61
  end
62
+
63
+ describe "user_agent matcher" do
64
+ it "should accept pattern and match against user-agent" do
65
+ app(:header_matchers) do |r|
66
+ r.on :user_agent=>/(chrome)(\d+)/ do |agent, num|
67
+ "a-#{agent}-#{num}"
68
+ end
69
+ end
70
+
71
+ body("HTTP_USER_AGENT" => "chrome31").should == "a-chrome-31"
72
+ status.should == 404
73
+ end
74
+ end
75
+
@@ -20,11 +20,9 @@ describe "hooks plugin" do
20
20
  end
21
21
 
22
22
  route do |r|
23
- r.is "" do
24
- f = response['foo']
25
- response['foo'] = 'baz'
26
- f
27
- end
23
+ f = response['foo']
24
+ response['foo'] = 'baz'
25
+ f
28
26
  end
29
27
  end
30
28
  end
@@ -0,0 +1,191 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ begin
4
+ require 'mail'
5
+ rescue LoadError
6
+ warn "mail not installed, skipping mail plugin test"
7
+ else
8
+ Mail.defaults do
9
+ delivery_method :test
10
+ end
11
+
12
+ describe "mailer plugin" do
13
+ def deliveries
14
+ Mail::TestMailer.deliveries
15
+ end
16
+
17
+ after do
18
+ deliveries.clear
19
+ end
20
+
21
+ setup_email = lambda do
22
+ from "f@example.com"
23
+ to "t@example.com"
24
+ subject 's'
25
+ end
26
+
27
+ it "supports sending emails via the routing tree" do
28
+ app(:mailer) do |r|
29
+ r.mail do
30
+ instance_exec(&setup_email)
31
+ cc "c@example.com"
32
+ bcc "b@example.com"
33
+ response['X-Foo'] = 'Bar'
34
+ "b"
35
+ end
36
+ end
37
+
38
+ m = app.mail('/foo')
39
+ deliveries.should == []
40
+ m.from.should == ['f@example.com']
41
+ m.to.should == ['t@example.com']
42
+ m.cc.should == ['c@example.com']
43
+ m.bcc.should == ['b@example.com']
44
+ m.subject.should == 's'
45
+ m.body.should == 'b'
46
+ m.header['X-Foo'].to_s.should == 'Bar'
47
+
48
+ m.deliver!
49
+ deliveries.should == [m]
50
+
51
+ deliveries.clear
52
+ m = app.sendmail('/foo')
53
+ deliveries.should == [m]
54
+ m.from.should == ['f@example.com']
55
+ m.to.should == ['t@example.com']
56
+ m.cc.should == ['c@example.com']
57
+ m.bcc.should == ['b@example.com']
58
+ m.subject.should == 's'
59
+ m.body.should == 'b'
60
+ m.header['X-Foo'].to_s.should == 'Bar'
61
+ end
62
+
63
+ it "supports arguments to mail/sendmail methods, yielding them to the route blocks" do
64
+ app(:mailer) do |r|
65
+ instance_exec(&setup_email)
66
+ r.mail "foo" do |*args|
67
+ "foo#{args.inspect}"
68
+ end
69
+ r.mail :d do |*args|
70
+ args.inspect
71
+ end
72
+ end
73
+
74
+ app.mail('/foo', 1, 2).body.should == 'foo[1, 2]'
75
+ app.sendmail('/bar', 1, 2).body.should == '["bar", 1, 2]'
76
+ end
77
+
78
+ it "supports attachments" do
79
+ app(:mailer) do |r|
80
+ r.mail do
81
+ instance_exec(&setup_email)
82
+ add_file __FILE__
83
+ end
84
+ end
85
+
86
+ m = app.mail('foo')
87
+ m.attachments.length.should == 1
88
+ m.attachments.first.content_type.should =~ /mailer_spec\.rb/
89
+ m.content_type.should =~ /\Amultipart\/mixed/
90
+ end
91
+
92
+ it "supports regular web requests in same application" do
93
+ app(:mailer) do |r|
94
+ r.get "foo", :param=>'bar' do |bar|
95
+ "foo#{bar}"
96
+ end
97
+ r.mail "bar" do
98
+ instance_exec(&setup_email)
99
+ "b"
100
+ end
101
+ end
102
+
103
+ body("/foo", 'QUERY_STRING'=>'bar=baz', 'rack.input'=>StringIO.new).should == 'foobaz'
104
+ app.mail('/bar').body.should == 'b'
105
+ end
106
+
107
+ it "supports multipart email using text_part/html_pat" do
108
+ app(:mailer) do |r|
109
+ r.mail do
110
+ instance_exec(&setup_email)
111
+ text_part "t"
112
+ html_part "h"
113
+ end
114
+ end
115
+
116
+ m = app.mail('/foo')
117
+ m.text_part.body.should == 't'
118
+ m.html_part.body.should == 'h'
119
+ m.content_type.should =~ /\Amultipart\/alternative/
120
+ end
121
+
122
+ it "supports setting arbitrary email headers for multipart emails" do
123
+ app(:mailer) do |r|
124
+ r.mail do
125
+ instance_exec(&setup_email)
126
+ text_part "t", "X-Text"=>'T'
127
+ html_part "h", "X-HTML"=>'H'
128
+ end
129
+ end
130
+
131
+ m = app.mail('/foo')
132
+ m.text_part.body.should == 't'
133
+ m.text_part.header['X-Text'].to_s.should == 'T'
134
+ m.html_part.body.should == 'h'
135
+ m.html_part.header['X-HTML'].to_s.should == 'H'
136
+ m.content_type.should =~ /\Amultipart\/alternative/
137
+ end
138
+
139
+ it "raises error if mail object is not returned" do
140
+ app(:mailer){}
141
+ proc{app.mail('/')}.should raise_error(Roda::RodaPlugins::Mailer::Error)
142
+ end
143
+
144
+ it "does not raise an error when using an explicitly empty body" do
145
+ app(:mailer){""}
146
+ proc{app.mail('/')}.should_not raise_error
147
+ end
148
+
149
+ it "supports setting the default content-type for emails when loading the plugin" do
150
+ app(:bare) do
151
+ plugin :mailer, :content_type=>'text/html'
152
+ route{""}
153
+ end
154
+ app.mail('/').content_type.should =~ /\Atext\/html/
155
+ end
156
+
157
+ it "supports loading the plugin multiple times" do
158
+ app(:bare) do
159
+ plugin :mailer, :content_type=>'text/html'
160
+ plugin :mailer
161
+ route{""}
162
+ end
163
+ app.mail('/').content_type.should =~ /\Atext\/html/
164
+ end
165
+
166
+ it "supports manually overridding the default content-type for emails" do
167
+ app(:bare) do
168
+ plugin :mailer, :content_type=>'text/html'
169
+ route do
170
+ response['Content-Type'] = 'text/foo'
171
+ ""
172
+ end
173
+ end
174
+ app.mail('/').content_type.should =~ /\Atext\/foo/
175
+ end
176
+
177
+ it "supports handle setting the default content type when attachments are used" do
178
+ app(:bare) do
179
+ plugin :mailer, :content_type=>'text/html'
180
+ route do
181
+ add_file 'spec/assets/css/raw.css'
182
+ "a"
183
+ end
184
+ end
185
+ m = app.mail('/')
186
+ m.content_type.should =~ /\Amultipart\/mixed/
187
+ m.parts.first.content_type.should =~ /\Atext\/css/
188
+ m.parts.last.content_type.should =~ /\Atext\/html/
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "match_affix plugin" do
4
+ it "allows changing the match prefix/suffix" do
5
+ app(:bare) do
6
+ plugin :match_affix, "", /(\/|\z)/
7
+
8
+ route do |r|
9
+ r.on "/albums" do |b|
10
+ r.on "b/:id" do |id, s|
11
+ "b-#{b}-#{id}-#{s.inspect}"
12
+ end
13
+
14
+ "albums-#{b}"
15
+ end
16
+ end
17
+ end
18
+
19
+ body("/albums/a/1").should == 'albums-/'
20
+ body("/albums/b/1").should == 'b-/-1-""'
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "multi_run plugin" do
4
+ it "adds Roda.run method for setting up prefix delegations to other rack apps" do
5
+ app(:multi_run) do |r|
6
+ r.multi_run
7
+ "c"
8
+ end
9
+
10
+ app.run "a", Class.new(Roda).class_eval{route{"a1"}; app}
11
+
12
+ body("/a").should == 'a1'
13
+ body("/b").should == 'c'
14
+ body("/b/a").should == 'c'
15
+ body.should == 'c'
16
+
17
+ app.run "b", Class.new(Roda).class_eval{route{"b1"}; app}
18
+
19
+ body("/a").should == 'a1'
20
+ body("/b").should == 'b1'
21
+ body("/b/a").should == 'b1'
22
+ body.should == 'c'
23
+
24
+ app.run "b/a", Class.new(Roda).class_eval{route{"b2"}; app}
25
+
26
+ body("/a").should == 'a1'
27
+ body("/b").should == 'b1'
28
+ body("/b/a").should == 'b2'
29
+ body.should == 'c'
30
+ end
31
+ end
@@ -0,0 +1,65 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "named_templates plugin" do
4
+ it "adds template method method for naming templates, and have render recognize it" do
5
+ app(:bare) do
6
+ plugin :named_templates
7
+
8
+ template :foo do
9
+ @b = 2
10
+ "foo<%= @a %><%= @b %>"
11
+ end
12
+ template :layout, :engine=>:str do
13
+ @c = 3
14
+ 'bar#{@a}#{@c}-#{yield}-baz'
15
+ end
16
+
17
+ route do |r|
18
+ @a = 1
19
+ view(:foo)
20
+ end
21
+ end
22
+
23
+ body.should == 'bar13-foo12-baz'
24
+ @app = Class.new(@app)
25
+ body.should == 'bar13-foo12-baz'
26
+ end
27
+
28
+ it "works with the view_subdirs plugin" do
29
+ app(:bare) do
30
+ plugin :render
31
+ plugin :view_subdirs
32
+ plugin :named_templates
33
+
34
+ template "foo/bar" do
35
+ @b = 2
36
+ "foobar<%= @a %><%= @b %>"
37
+ end
38
+ template "foo/layout", :engine=>:str do
39
+ @c = 3
40
+ 'foo#{@a}#{@c}-#{yield}-baz'
41
+ end
42
+ template "bar/layout", :engine=>:str do
43
+ @c = 3
44
+ 'bar#{@a}#{@c}-#{yield}-baz'
45
+ end
46
+
47
+ route do |r|
48
+ r.is 'foo' do
49
+ set_view_subdir :foo
50
+ @a = 1
51
+ view(:bar)
52
+ end
53
+ r.is 'bar' do
54
+ set_view_subdir :bar
55
+ @a = 4
56
+ @b = 2
57
+ view(:inline=>"barfoo<%= @a %><%= @b %>")
58
+ end
59
+ end
60
+ end
61
+
62
+ body('/foo').should == 'foo13-foobar12-baz'
63
+ body('/bar').should == 'bar43-barfoo42-baz'
64
+ end
65
+ end
@@ -1,6 +1,19 @@
1
1
  require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
2
 
3
3
  describe "path plugin" do
4
+ def path_app(*args, &block)
5
+ app(:bare) do
6
+ plugin :path
7
+ path *args, &block
8
+ route{|r| send(r.path_info)}
9
+ end
10
+ end
11
+
12
+ def path_block_app(b, *args, &block)
13
+ path_app(*args, &block)
14
+ app.route{|r| send(r.path_info, &b)}
15
+ end
16
+
4
17
  it "adds path method for defining named paths" do
5
18
  app(:bare) do
6
19
  plugin :path
@@ -8,13 +21,16 @@ describe "path plugin" do
8
21
  path :bar do |o|
9
22
  "/bar/#{o}"
10
23
  end
24
+ path :baz do |&block|
25
+ "/baz/#{block.call}"
26
+ end
11
27
 
12
28
  route do |r|
13
- "#{foo_path}#{bar_path('a')}"
29
+ "#{foo_path}#{bar_path('a')}#{baz_path{'b'}}"
14
30
  end
15
31
  end
16
32
 
17
- body.should == '/foo/bar/a'
33
+ body.should == '/foo/bar/a/baz/b'
18
34
  end
19
35
 
20
36
  it "raises if both path and block are given" do
@@ -26,4 +42,52 @@ describe "path plugin" do
26
42
  app.plugin :path
27
43
  proc{app.path(:foo)}.should raise_error(Roda::RodaError)
28
44
  end
45
+
46
+ it "raises if two options hashes are given" do
47
+ app.plugin :path
48
+ proc{app.path(:foo, {:name=>'a'}, :add_script_name=>true)}.should raise_error(Roda::RodaError)
49
+ end
50
+
51
+ it "supports :name option for naming the method" do
52
+ path_app(:foo, :name=>'foobar_route'){"/bar/foo"}
53
+ body("foobar_route").should == "/bar/foo"
54
+ end
55
+
56
+ it "supports :add_script_name option for automatically adding the script name" do
57
+ path_app(:foo, :add_script_name=>true){"/bar/foo"}
58
+ body("foo_path", 'SCRIPT_NAME'=>'/baz').should == "/baz/bar/foo"
59
+ end
60
+
61
+ it "supports path method accepting a block when using :add_script_name" do
62
+ path_block_app(lambda{"c"}, :foo, :add_script_name=>true){|&block| "/bar/foo/#{block.call}"}
63
+ body("foo_path", 'SCRIPT_NAME'=>'/baz').should == "/baz/bar/foo/c"
64
+ end
65
+
66
+ it "supports :url option for also creating a *_url method" do
67
+ path_app(:foo, :url=>true){"/bar/foo"}
68
+ body("foo_path", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).should == "/bar/foo"
69
+ body("foo_url", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).should == "http://example.org/bar/foo"
70
+ end
71
+
72
+ it "supports url method accepting a block when using :url" do
73
+ path_block_app(lambda{"c"}, :foo, :url=>true){|&block| "/bar/foo/#{block.call}"}
74
+ body("foo_url", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).should == "http://example.org/bar/foo/c"
75
+ end
76
+
77
+ it "supports url method name specified in :url option" do
78
+ path_app(:foo, :url=>:foobar_uri){"/bar/foo"}
79
+ body("foo_path", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).should == "/bar/foo"
80
+ body("foobar_uri", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).should == "http://example.org/bar/foo"
81
+ end
82
+
83
+ it "supports :url_only option for not creating a path method" do
84
+ path_app(:foo, :url_only=>true){"/bar/foo"}
85
+ proc{body("foo_path")}.should raise_error(NoMethodError)
86
+ body("foo_url", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).should == "http://example.org/bar/foo"
87
+ end
88
+
89
+ it "handles non-default ports in url methods" do
90
+ path_app(:foo, :url=>true){"/bar/foo"}
91
+ body("foo_url", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>81).should == "http://example.org:81/bar/foo"
92
+ end
29
93
  end