roda 1.1.0 → 1.2.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 (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