bradphelan-sinatras-hat 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/.gitignore +4 -0
  2. data/LICENSE +22 -0
  3. data/README.md +235 -0
  4. data/Rakefile +59 -0
  5. data/VERSION +1 -0
  6. data/bradphelan-sinatras-hat.gemspec +145 -0
  7. data/ci.rb +9 -0
  8. data/example/app-with-auth.rb +14 -0
  9. data/example/app-with-cache.rb +30 -0
  10. data/example/app.rb +23 -0
  11. data/example/lib/comment.rb +13 -0
  12. data/example/lib/common.rb +19 -0
  13. data/example/lib/post.rb +16 -0
  14. data/example/simple-app.rb +4 -0
  15. data/example/views/comments/index.erb +6 -0
  16. data/example/views/comments/show.erb +1 -0
  17. data/example/views/posts/index.erb +8 -0
  18. data/example/views/posts/new.erb +11 -0
  19. data/example/views/posts/show.erb +28 -0
  20. data/features/authenticated.feature +12 -0
  21. data/features/create.feature +16 -0
  22. data/features/destroy.feature +18 -0
  23. data/features/edit.feature +17 -0
  24. data/features/formats.feature +19 -0
  25. data/features/headers.feature +28 -0
  26. data/features/index.feature +23 -0
  27. data/features/layouts.feature +11 -0
  28. data/features/nested.feature +20 -0
  29. data/features/new.feature +20 -0
  30. data/features/only.feature +13 -0
  31. data/features/show.feature +31 -0
  32. data/features/steps/authenticated_steps.rb +10 -0
  33. data/features/steps/common_steps.rb +77 -0
  34. data/features/steps/create_steps.rb +21 -0
  35. data/features/steps/destroy_steps.rb +16 -0
  36. data/features/steps/edit_steps.rb +7 -0
  37. data/features/steps/format_steps.rb +11 -0
  38. data/features/steps/header_steps.rb +7 -0
  39. data/features/steps/index_steps.rb +26 -0
  40. data/features/steps/nested_steps.rb +11 -0
  41. data/features/steps/new_steps.rb +15 -0
  42. data/features/steps/only_steps.rb +10 -0
  43. data/features/steps/show_steps.rb +24 -0
  44. data/features/steps/update_steps.rb +22 -0
  45. data/features/support/env.rb +17 -0
  46. data/features/support/views/comments/index.erb +5 -0
  47. data/features/support/views/layout.erb +9 -0
  48. data/features/support/views/people/edit.erb +1 -0
  49. data/features/support/views/people/index.erb +1 -0
  50. data/features/support/views/people/layout.erb +9 -0
  51. data/features/support/views/people/new.erb +1 -0
  52. data/features/support/views/people/show.erb +1 -0
  53. data/features/update.feature +25 -0
  54. data/lib/core_ext/array.rb +5 -0
  55. data/lib/core_ext/hash.rb +23 -0
  56. data/lib/core_ext/module.rb +14 -0
  57. data/lib/core_ext/object.rb +45 -0
  58. data/lib/sinatras-hat.rb +22 -0
  59. data/lib/sinatras-hat/actions.rb +81 -0
  60. data/lib/sinatras-hat/authentication.rb +55 -0
  61. data/lib/sinatras-hat/extendor.rb +24 -0
  62. data/lib/sinatras-hat/hash_mutator.rb +18 -0
  63. data/lib/sinatras-hat/logger.rb +36 -0
  64. data/lib/sinatras-hat/maker.rb +187 -0
  65. data/lib/sinatras-hat/model.rb +110 -0
  66. data/lib/sinatras-hat/resource.rb +57 -0
  67. data/lib/sinatras-hat/responder.rb +106 -0
  68. data/lib/sinatras-hat/response.rb +60 -0
  69. data/lib/sinatras-hat/router.rb +46 -0
  70. data/sinatras-hat.gemspec +34 -0
  71. data/spec/actions/create_spec.rb +68 -0
  72. data/spec/actions/destroy_spec.rb +58 -0
  73. data/spec/actions/edit_spec.rb +52 -0
  74. data/spec/actions/index_spec.rb +72 -0
  75. data/spec/actions/new_spec.rb +39 -0
  76. data/spec/actions/show_spec.rb +85 -0
  77. data/spec/actions/update_spec.rb +83 -0
  78. data/spec/extendor_spec.rb +78 -0
  79. data/spec/fixtures/views/articles/edit.erb +1 -0
  80. data/spec/fixtures/views/articles/index.erb +1 -0
  81. data/spec/fixtures/views/articles/new.erb +1 -0
  82. data/spec/fixtures/views/articles/show.erb +1 -0
  83. data/spec/hash_mutator_spec.rb +23 -0
  84. data/spec/maker_spec.rb +411 -0
  85. data/spec/model_spec.rb +152 -0
  86. data/spec/resource_spec.rb +74 -0
  87. data/spec/responder_spec.rb +139 -0
  88. data/spec/response_spec.rb +120 -0
  89. data/spec/router_spec.rb +105 -0
  90. data/spec/spec_helper.rb +80 -0
  91. metadata +161 -0
@@ -0,0 +1,72 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "handle index" do
4
+ attr_reader :maker, :app, :request
5
+
6
+ before(:each) do
7
+ build_models!
8
+ mock_app { }
9
+ @maker = new_maker(Article)
10
+ @request = fake_request
11
+ end
12
+
13
+ def handle(*args)
14
+ maker.handle(:index, *args)
15
+ end
16
+
17
+ it "takes a request" do
18
+ maker.handle(:index, request)
19
+ end
20
+
21
+ it "loads all records" do
22
+ mock.proxy(maker.model).all(anything) { [] }
23
+ handle(request)
24
+ end
25
+
26
+ describe "rendering a response" do
27
+ context "when there's a :format param" do
28
+ before(:each) do
29
+ @newest_article = Article.create!
30
+ @request = fake_request(:format => "yaml")
31
+ stub(maker.model).all(anything).returns([@newest_article, @article])
32
+ end
33
+
34
+ it "sets ETag header" do
35
+ mock(request).etag(anything)
36
+ handle(request)
37
+ end
38
+
39
+ describe "setting the last_modified params" do
40
+ context "when the last record was the most recently updated" do
41
+ it "sets last_modified param to the last updated record's updated_at" do
42
+ mock(request).last_modified(anything)
43
+ handle(request)
44
+ end
45
+ end
46
+
47
+ context "when the last record was not the most recently updated" do
48
+ before(:each) do
49
+ @article.update_attribute :name, "Updated recently"
50
+ end
51
+
52
+ it "sets last_modified param to the last updated record's updated_at" do
53
+ mock(request).last_modified(anything)
54
+ handle(request)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ context "when there's no :format param" do
61
+ before(:each) do
62
+ @request = fake_request
63
+ stub(maker.model).all(anything).returns([:article])
64
+ end
65
+
66
+ it "renders the index template" do
67
+ mock.proxy(maker.responder).success(:index, request, [:article])
68
+ handle(request)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "handle index" do
4
+ attr_reader :maker, :app, :request
5
+
6
+ before(:each) do
7
+ build_models!
8
+ mock_app { }
9
+ @maker = new_maker(Article)
10
+ @request = fake_request
11
+ end
12
+
13
+ def handle(*args)
14
+ maker.handle(:new, *args)
15
+ end
16
+
17
+ it "takes a request" do
18
+ maker.handle(:new, request)
19
+ end
20
+
21
+ it "loads a new record" do
22
+ mock.proxy(maker.model).new(anything) { :article }
23
+ handle(request)
24
+ end
25
+
26
+ describe "rendering a response" do
27
+ context "when there's no :format param" do
28
+ before(:each) do
29
+ @request = fake_request
30
+ stub(maker.model).new(anything).returns(:article)
31
+ end
32
+
33
+ it "renders the index template" do
34
+ mock.proxy(maker.responder).success(:new, request, :article)
35
+ handle(request)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "handle show" do
4
+ attr_reader :maker, :app, :request
5
+
6
+ before(:each) do
7
+ build_models!
8
+ mock_app { }
9
+ @maker = new_maker(Article)
10
+ @request = fake_request(:id => @article.to_param)
11
+ end
12
+
13
+ def handle(*args)
14
+ maker.handle(:show, *args)
15
+ end
16
+
17
+ it "takes a request" do
18
+ handle(request)
19
+ end
20
+
21
+ it "loads correct record" do
22
+ mock.proxy(maker.model).find(:id => @article.to_param) { :article }
23
+ handle(request)
24
+ end
25
+
26
+ describe "rendering a successful response" do
27
+ context "when there's a :format param" do
28
+ before(:each) do
29
+ params = { :format => "yaml", :id => @article.to_param }
30
+ @request = fake_request(params)
31
+ stub(maker.model).find(params).returns(@article)
32
+ end
33
+
34
+ it "sets last_modified header" do
35
+ mock(request).last_modified(@article.updated_at)
36
+ handle(request)
37
+ end
38
+
39
+ it "sets ETag header" do
40
+ mock(request).etag(anything)
41
+ handle(request)
42
+ end
43
+ end
44
+
45
+ context "when there's no :format param" do
46
+ before(:each) do
47
+ params = { :id => @article.to_param }
48
+ @request = fake_request(params)
49
+ stub(maker.model).find(anything).returns(@article)
50
+ end
51
+
52
+ it "uses the success response" do
53
+ mock.proxy(maker.responder).success(:show, request, @article)
54
+ handle(request)
55
+ end
56
+
57
+ it "sets last_modified param" do
58
+ mock(request).last_modified(@article.updated_at)
59
+ handle(request)
60
+ end
61
+
62
+ it "sets ETag header" do
63
+ mock(request).etag(anything)
64
+ handle(request)
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "rendering not_found" do
70
+ before(:each) do
71
+ stub(maker.model).find(request.params).returns(nil)
72
+ stub(request).not_found # because it throws :halt otherwise
73
+ end
74
+
75
+ it "returns not_found" do
76
+ mock(request).not_found
77
+ handle(request)
78
+ end
79
+
80
+ it "does not set last_modified param" do
81
+ mock(request).last_modified(@article.updated_at).never
82
+ handle(request)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "handle create" do
4
+ attr_reader :maker, :app, :request, :article
5
+
6
+ before(:each) do
7
+ build_models!
8
+ mock_app { }
9
+ @maker = new_maker(Article)
10
+ @request = fake_request("article[name]" => "Hooray!")
11
+ stub(request).redirect(anything)
12
+ end
13
+
14
+ def handle(*args)
15
+ maker.handle(:update, *args)
16
+ end
17
+
18
+ describe "when the record doesn't exist" do
19
+ before(:each) do
20
+ stub(maker.model).find(anything) { nil }
21
+ end
22
+
23
+ it "returns not_found" do
24
+ mock.proxy(request).not_found
25
+ catch(:halt) { handle(request) }
26
+ end
27
+ end
28
+
29
+ describe "attempting to update a record" do
30
+ it "finds a record and updates its attributes" do
31
+ mock.proxy(article).attributes = { "name" => "Hooray!" }
32
+ mock.proxy(article).save
33
+ mock.proxy(maker.model).find(anything) { article }
34
+ handle(request)
35
+ end
36
+
37
+ context "when the save is successful" do
38
+ before(:each) do
39
+ stub(maker.model).find(anything).returns(article)
40
+ stub(article).save { true }
41
+ end
42
+
43
+ context "when there's no format" do
44
+ it "redirects to that record's path" do
45
+ mock(request).redirect("/articles/#{article.id}")
46
+ mock.proxy(maker.responder).success(:update, request, article)
47
+ handle(request)
48
+ end
49
+ end
50
+
51
+ # context "when there is a format" do
52
+ # it "serializes the record" do
53
+ # request_with_format = fake_request(:format => "yaml")
54
+ # mock.proxy(maker.responder).serialize("yaml", article)
55
+ # handle(request_with_format)
56
+ # end
57
+ # end
58
+ end
59
+
60
+ context "when the save is not successful" do
61
+ before(:each) do
62
+ stub(maker.model).find(anything).returns(article)
63
+ stub(article).save { false }
64
+ end
65
+
66
+ context "when there's no format" do
67
+ it "renders edit template" do
68
+ mock(request).erb :"articles/edit"
69
+ mock.proxy(maker.responder).failure(:update, request, article)
70
+ handle(request)
71
+ end
72
+ end
73
+
74
+ # context "when there is a format" do
75
+ # it "serializes the record" do
76
+ # request_with_format = fake_request(:format => "yaml")
77
+ # mock.proxy(maker.responder).serialize("yaml", article)
78
+ # handle(request_with_format)
79
+ # end
80
+ # end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe Sinatra::Hat::Extendor do
4
+ describe "mount" do
5
+ context "when used at top level" do
6
+ it "takes a klass" do
7
+ proc {
8
+ mock_app { mount(Article) }
9
+ }.should_not raise_error
10
+ end
11
+
12
+ it "uses Rack::MethodOverride" do
13
+ app = mock_app
14
+ mock.proxy(app).use(Rack::MethodOverride)
15
+ app.class_eval { mount(Article) }
16
+ end
17
+
18
+ it "takes an options hash" do
19
+ proc {
20
+ mock_app { mount(Article, :only => [:index]) }
21
+ }.should_not raise_error
22
+ end
23
+
24
+ it "instantiates a new Sinatra::Hat::Maker" do
25
+ mock.proxy(Sinatra::Hat::Maker).new(Article, { })
26
+ mock_app { mount(Article) }
27
+ end
28
+
29
+ it "instance_eval's the block in the new maker" do
30
+ mock.proxy(maker = new_maker).instance_eval
31
+ mock.proxy(Sinatra::Hat::Maker).new(Article, { }) { maker }
32
+ mock_app { mount(Article, &proc { }) }
33
+ end
34
+
35
+ it "generates routes" do
36
+ mock.proxy.instance_of(Sinatra::Hat::Maker).setup(anything)
37
+ mock_app { mount(Article) }
38
+ end
39
+ end
40
+
41
+ context "when used in nested #mount calls" do
42
+ attr_reader :app, :maker
43
+
44
+ before(:each) do
45
+ @app = mock_app
46
+ @maker = new_maker(Article)
47
+ maker.setup(app)
48
+ end
49
+
50
+ it "takes a klass" do
51
+ proc {
52
+ maker.mount(Comment)
53
+ }.should_not raise_error
54
+ end
55
+
56
+ it "takes an options hash" do
57
+ proc {
58
+ maker.mount(Comment, :only => :index)
59
+ }.should_not raise_error
60
+ end
61
+
62
+ it "instantiates a new Sinatra::Hat::Maker" do
63
+ mock.proxy(Sinatra::Hat::Maker).new(Comment, { })
64
+ maker.mount(Comment)
65
+ end
66
+
67
+ it "sets the :parent option" do
68
+ mock.proxy(Sinatra::Hat::Maker).new(Comment, { })
69
+ maker.mount(Comment).parent.should == maker
70
+ end
71
+
72
+ it "generates routes" do
73
+ mock.proxy.instance_of(Sinatra::Hat::Maker).setup(app)
74
+ maker.mount(Comment)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1 @@
1
+ Edit Article!
@@ -0,0 +1 @@
1
+ HEY: <%= @articles.inspect %>
@@ -0,0 +1 @@
1
+ New Article!
@@ -0,0 +1 @@
1
+ SHOW: <%= @article.inspect %>
@@ -0,0 +1,23 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe Sinatra::Hat::HashMutator do
4
+ attr_reader :mutator, :hash
5
+
6
+ before(:each) do
7
+ @hash = {
8
+ :success => proc { :ftw! },
9
+ :failure => proc { :fail }
10
+ }
11
+ @mutator = Sinatra::Hat::HashMutator.new(hash)
12
+ end
13
+
14
+ it "lets you alter the success key of the passed in hash" do
15
+ mutator.success { :pwnd! }
16
+ hash[:success][].should == :pwnd!
17
+ end
18
+
19
+ it "lets you alter the failure key of the passed in hash" do
20
+ mutator.failure { :pwnd! }
21
+ hash[:failure][].should == :pwnd!
22
+ end
23
+ end
@@ -0,0 +1,411 @@
1
+ require 'spec/spec_helper'
2
+
3
+ class Article; end
4
+ class Comment; end
5
+
6
+ describe Sinatra::Hat::Maker do
7
+ attr_reader :model, :maker, :request
8
+
9
+ describe "initializing" do
10
+ it "takes a klass" do
11
+ proc {
12
+ new_maker(Article)
13
+ }.should_not raise_error
14
+ end
15
+
16
+ it "takes options" do
17
+ proc {
18
+ new_maker(Article, :only => :index)
19
+ }.should_not raise_error
20
+ end
21
+
22
+ it "merges options hash with defaults" do
23
+ maker = new_maker(Article, :only => :index)
24
+ maker.options[:only].should == :index
25
+ end
26
+ end
27
+
28
+ describe "setup" do
29
+ before(:each) do
30
+ stub.instance_of(Sinatra::Hat::Router).generate(:app)
31
+ end
32
+
33
+ it "stores reference to app" do
34
+ maker = new_maker
35
+ maker.setup(:app)
36
+ maker.app.should == :app
37
+ end
38
+ end
39
+
40
+ it "has a klass" do
41
+ new_maker(Article).klass.should == Article
42
+ end
43
+
44
+ describe "protect" do
45
+ before(:each) do
46
+ @maker = new_maker
47
+ end
48
+
49
+ it "sets the protected actions" do
50
+ maker.protect :index, :show
51
+ maker.protect.should == [:index, :show]
52
+ end
53
+
54
+ it "can set the protected actions to :all" do
55
+ maker.protect :all
56
+ maker.protect.should == maker.only
57
+ end
58
+
59
+ it "can set the credentials" do
60
+ maker.protect :username => "admin", :password => "awesome", :realm => "awesome app"
61
+ maker.credentials.should == { :username => "admin", :password => "awesome", :realm => "awesome app" }
62
+ end
63
+ end
64
+
65
+ describe "handling actions" do
66
+ before(:each) do
67
+ @request = fake_request
68
+ @maker = new_maker
69
+ stub(Sinatra::Hat::Maker.actions[:index])[:fn].returns proc { |passed_request| [self, passed_request] }
70
+ end
71
+
72
+ it "takes an action and instance_exec's its event handler" do
73
+ mock(Sinatra::Hat::Maker.actions[:index])[:fn].returns proc { |passed_request| [self, passed_request] }
74
+ maker.handle(:index, request).should == [maker, request]
75
+ end
76
+
77
+ context "when the action is protected" do
78
+ before(:each) do
79
+ maker.protect :index
80
+ end
81
+
82
+ context "when the user is not authenticated" do
83
+ it "protects the action" do
84
+ proc {
85
+ maker.handle(:index, request)
86
+ }.should throw_symbol(:halt)
87
+ end
88
+ end
89
+
90
+ context "when the user is authenticated" do
91
+ before(:each) do
92
+ request.env['REMOTE_USER'] = 'hello'
93
+ end
94
+
95
+ it "allows the request" do
96
+ proc {
97
+ maker.handle(:index, request)
98
+ }.should_not throw_symbol(:halt)
99
+ end
100
+ end
101
+ end
102
+
103
+ context "when the action is not part of the :only list" do
104
+ before(:each) do
105
+ maker.only :show
106
+ end
107
+
108
+ it "returns 404" do
109
+ mock.proxy(request).error(404)
110
+ catch(:halt) { maker.handle(:index, request) }
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ describe "default options" do
117
+ before(:each) do
118
+ @maker = new_maker(Article)
119
+ end
120
+
121
+ it "has a default options hash" do
122
+ maker.options.should_not be_nil
123
+ end
124
+
125
+ describe ":only" do
126
+ it "returns all actions" do
127
+ maker.options[:only].should include(:index, :show, :new, :create, :edit, :update, :destroy)
128
+ end
129
+
130
+ it "is methodized" do
131
+ maker.only.should == maker.options[:only]
132
+ end
133
+
134
+ it "has methodized setter" do
135
+ maker.only :index, :show
136
+ maker.only.should have(2).entries
137
+ maker.only.should include(:index)
138
+ maker.only.should include(:show)
139
+ end
140
+ end
141
+
142
+ describe ":formats" do
143
+ it "is an empty hash" do
144
+ maker.options[:formats].should == { }
145
+ end
146
+
147
+ it "is methodized" do
148
+ maker.formats.should === maker.options[:formats]
149
+ end
150
+ end
151
+
152
+ describe ":to_param" do
153
+ it "is :id by default" do
154
+ maker.options[:to_param].should == :id
155
+ end
156
+
157
+ it "is methodized" do
158
+ maker.to_param.should == :id
159
+ end
160
+
161
+ it "has methodized setter" do
162
+ maker.to_param :permalink
163
+ maker.to_param.should == :permalink
164
+ end
165
+ end
166
+
167
+ describe ":parent" do
168
+ it "is nil" do
169
+ maker.options[:parent].should be_nil
170
+ end
171
+
172
+ it "is methodized" do
173
+ maker.parent.should === maker.options[:parent]
174
+ end
175
+ end
176
+
177
+ describe ':format' do
178
+ it 'is nil by default' do
179
+ maker.options[:format].should be_nil
180
+ maker.options.should have_key(:format)
181
+ end
182
+
183
+ it 'is methodized' do
184
+ maker.options[:format] = :json
185
+ maker.format.should == :json
186
+ end
187
+
188
+ it 'has methodized setter' do
189
+ maker.format :json
190
+ maker.format.should == :json
191
+ end
192
+ end
193
+
194
+ describe ":protect" do
195
+ it "is empty by default" do
196
+ maker.options[:protect].should be_empty
197
+ end
198
+
199
+ it "is methodized" do
200
+ maker.protect.should === maker.options[:protect]
201
+ end
202
+
203
+ it "has methodized setter" do
204
+ maker.protect :index, :show
205
+ maker.protect.should == [:index, :show]
206
+ end
207
+ end
208
+
209
+ describe ":finder" do
210
+ it "finds all for the model" do
211
+ mock(Article).all
212
+ maker.options[:finder][Article, { }]
213
+ end
214
+ end
215
+
216
+ describe ":authenticator" do
217
+ it "finds all for the model" do
218
+ mock(Article).all
219
+ maker.options[:finder][Article, { }]
220
+ end
221
+
222
+ it "has a block setter" do
223
+ fn = proc { |u, p| [u, p] }
224
+ maker.authenticator(&fn)
225
+ maker.authenticator[:user, :pass].should == [:user, :pass]
226
+ end
227
+ end
228
+
229
+ describe ":credentials" do
230
+ it "has username" do
231
+ maker.options[:credentials][:username].should_not be_nil
232
+ end
233
+
234
+ it "has password" do
235
+ maker.options[:credentials][:password].should_not be_nil
236
+ end
237
+
238
+ it "has realm" do
239
+ maker.options[:credentials][:realm].should_not be_nil
240
+ end
241
+ end
242
+
243
+ describe ":record" do
244
+ it "loads a single record" do
245
+ mock(Article).find_by_id(2)
246
+ maker.options[:record][Article, { :id => 2 }]
247
+ end
248
+ end
249
+ end
250
+
251
+ describe "finder" do
252
+ context "when no block is provided" do
253
+ it "returns the finder option" do
254
+ maker = new_maker
255
+ maker.finder.should == maker.options[:finder]
256
+ end
257
+ end
258
+
259
+ context "when a block is provided" do
260
+ it "sets the finder option" do
261
+ maker = new_maker
262
+ block = proc { |model, params| model.find_by_id(params[:id]) }
263
+ maker.finder(&block)
264
+ maker.options[:finder].should == block
265
+ end
266
+ end
267
+ end
268
+
269
+ describe "record" do
270
+ context "when no block is provided" do
271
+ it "returns the record option" do
272
+ maker = new_maker
273
+ maker.record.should == maker.options[:record]
274
+ end
275
+ end
276
+
277
+ context "when a block is provided" do
278
+ it "sets the record option" do
279
+ maker = new_maker
280
+ block = proc { |model, params| model.find_by_id(params[:id]) }
281
+ maker.record(&block)
282
+ maker.options[:record].should == block
283
+ end
284
+ end
285
+ end
286
+
287
+ describe "prefix" do
288
+ context "when used as a method" do
289
+ before(:each) do
290
+ @maker = new_maker(Article)
291
+ end
292
+
293
+ it "is a getter if called without argument" do
294
+ maker.prefix.should == maker.options[:prefix]
295
+ end
296
+
297
+ it "is a setter if called with an argument" do
298
+ maker.prefix 'super/heroes'
299
+ maker.prefix.should == 'super/heroes'
300
+ maker.prefix.should == maker.options[:prefix]
301
+ end
302
+ end
303
+
304
+ context "when specified as an option" do
305
+ it "returns the option value" do
306
+ new_maker(Article, :prefix => "posts").prefix.should == "posts"
307
+ end
308
+ end
309
+
310
+ context "when it's not specified as an option" do
311
+ it "returns the pluralized, downcased klass name" do
312
+ new_maker(Article).prefix.should == "articles"
313
+ end
314
+
315
+ it "snakecases" do
316
+ stub(klass = Class.new).name { "AwesomePerson" }
317
+ new_maker(klass).prefix.should == 'awesome_people'
318
+ end
319
+ end
320
+ end
321
+
322
+ describe "parents" do
323
+ context "when there are none" do
324
+ it "is empty" do
325
+ new_maker.parents.should be_empty
326
+ end
327
+ end
328
+
329
+ context "when there is one" do
330
+ it "includes the parent" do
331
+ parent = new_maker(Article)
332
+ child = new_maker(Comment, :parent => parent)
333
+ child.parents.should == [parent]
334
+ end
335
+ end
336
+
337
+ context "when there are many" do
338
+ it "includes all ancestors" do
339
+ grand_parent = new_maker(Article)
340
+ parent = new_maker(Article, :parent => grand_parent)
341
+ child = new_maker(Comment, :parent => parent)
342
+ child.parents.should == [grand_parent, parent]
343
+ end
344
+ end
345
+ end
346
+
347
+ describe "#model" do
348
+ it "returns an instance of Sinatra::Hat::Model" do
349
+ maker = new_maker
350
+ mock.proxy(Sinatra::Hat::Model.new(maker))
351
+ maker.model
352
+ end
353
+ end
354
+
355
+ describe "#responder" do
356
+ it "returns an instance of Sinatra::Hat::Responder" do
357
+ maker = new_maker
358
+ mock.proxy(Sinatra::Hat::Responder.new(maker))
359
+ maker.responder
360
+ end
361
+ end
362
+
363
+ describe "generating routes" do
364
+ it "generates routes for maker instance" do
365
+ maker = new_maker
366
+ router = Sinatra::Hat::Router.new(maker)
367
+ mock.proxy(Sinatra::Hat::Router).new(maker) { router }
368
+ mock(router).generate(maker.app)
369
+ maker.generate_routes!
370
+ end
371
+ end
372
+
373
+ describe "#after" do
374
+ before(:each) do
375
+ @maker = new_maker
376
+ end
377
+
378
+ it "takes the name of an action" do
379
+ proc {
380
+ maker.after(:create) { }
381
+ }.should_not raise_error
382
+ end
383
+
384
+ it "passes a new hash mutator to the block" do
385
+ maker.after(:create) { |arg| arg }.should be_kind_of(Sinatra::Hat::HashMutator)
386
+ end
387
+
388
+ it "lets you alter the default options" do
389
+ maker.after(:create) do |on|
390
+ on.success { :new_default! }
391
+ end
392
+ maker.responder.defaults[:create][:success][].should == :new_default!
393
+ end
394
+
395
+ describe "when there are nested resources" do
396
+ before(:each) do
397
+ @child_maker = new_maker(Comment)
398
+ @child_maker.parent = @maker
399
+ end
400
+
401
+ it "mutates the child's response" do
402
+ @child_maker.after(:create) do |on|
403
+ on.success { :child_response }
404
+ end
405
+
406
+ @child_maker.responder.defaults[:create][:success][].should == :child_response
407
+ @maker.responder.defaults[:create][:success].should_not === @child_maker.responder.defaults[:create][:success]
408
+ end
409
+ end
410
+ end
411
+ end