bradphelan-sinatras-hat 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/LICENSE +22 -0
- data/README.md +235 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/bradphelan-sinatras-hat.gemspec +145 -0
- data/ci.rb +9 -0
- data/example/app-with-auth.rb +14 -0
- data/example/app-with-cache.rb +30 -0
- data/example/app.rb +23 -0
- data/example/lib/comment.rb +13 -0
- data/example/lib/common.rb +19 -0
- data/example/lib/post.rb +16 -0
- data/example/simple-app.rb +4 -0
- data/example/views/comments/index.erb +6 -0
- data/example/views/comments/show.erb +1 -0
- data/example/views/posts/index.erb +8 -0
- data/example/views/posts/new.erb +11 -0
- data/example/views/posts/show.erb +28 -0
- data/features/authenticated.feature +12 -0
- data/features/create.feature +16 -0
- data/features/destroy.feature +18 -0
- data/features/edit.feature +17 -0
- data/features/formats.feature +19 -0
- data/features/headers.feature +28 -0
- data/features/index.feature +23 -0
- data/features/layouts.feature +11 -0
- data/features/nested.feature +20 -0
- data/features/new.feature +20 -0
- data/features/only.feature +13 -0
- data/features/show.feature +31 -0
- data/features/steps/authenticated_steps.rb +10 -0
- data/features/steps/common_steps.rb +77 -0
- data/features/steps/create_steps.rb +21 -0
- data/features/steps/destroy_steps.rb +16 -0
- data/features/steps/edit_steps.rb +7 -0
- data/features/steps/format_steps.rb +11 -0
- data/features/steps/header_steps.rb +7 -0
- data/features/steps/index_steps.rb +26 -0
- data/features/steps/nested_steps.rb +11 -0
- data/features/steps/new_steps.rb +15 -0
- data/features/steps/only_steps.rb +10 -0
- data/features/steps/show_steps.rb +24 -0
- data/features/steps/update_steps.rb +22 -0
- data/features/support/env.rb +17 -0
- data/features/support/views/comments/index.erb +5 -0
- data/features/support/views/layout.erb +9 -0
- data/features/support/views/people/edit.erb +1 -0
- data/features/support/views/people/index.erb +1 -0
- data/features/support/views/people/layout.erb +9 -0
- data/features/support/views/people/new.erb +1 -0
- data/features/support/views/people/show.erb +1 -0
- data/features/update.feature +25 -0
- data/lib/core_ext/array.rb +5 -0
- data/lib/core_ext/hash.rb +23 -0
- data/lib/core_ext/module.rb +14 -0
- data/lib/core_ext/object.rb +45 -0
- data/lib/sinatras-hat.rb +22 -0
- data/lib/sinatras-hat/actions.rb +81 -0
- data/lib/sinatras-hat/authentication.rb +55 -0
- data/lib/sinatras-hat/extendor.rb +24 -0
- data/lib/sinatras-hat/hash_mutator.rb +18 -0
- data/lib/sinatras-hat/logger.rb +36 -0
- data/lib/sinatras-hat/maker.rb +187 -0
- data/lib/sinatras-hat/model.rb +110 -0
- data/lib/sinatras-hat/resource.rb +57 -0
- data/lib/sinatras-hat/responder.rb +106 -0
- data/lib/sinatras-hat/response.rb +60 -0
- data/lib/sinatras-hat/router.rb +46 -0
- data/sinatras-hat.gemspec +34 -0
- data/spec/actions/create_spec.rb +68 -0
- data/spec/actions/destroy_spec.rb +58 -0
- data/spec/actions/edit_spec.rb +52 -0
- data/spec/actions/index_spec.rb +72 -0
- data/spec/actions/new_spec.rb +39 -0
- data/spec/actions/show_spec.rb +85 -0
- data/spec/actions/update_spec.rb +83 -0
- data/spec/extendor_spec.rb +78 -0
- data/spec/fixtures/views/articles/edit.erb +1 -0
- data/spec/fixtures/views/articles/index.erb +1 -0
- data/spec/fixtures/views/articles/new.erb +1 -0
- data/spec/fixtures/views/articles/show.erb +1 -0
- data/spec/hash_mutator_spec.rb +23 -0
- data/spec/maker_spec.rb +411 -0
- data/spec/model_spec.rb +152 -0
- data/spec/resource_spec.rb +74 -0
- data/spec/responder_spec.rb +139 -0
- data/spec/response_spec.rb +120 -0
- data/spec/router_spec.rb +105 -0
- data/spec/spec_helper.rb +80 -0
- metadata +161 -0
data/spec/model_spec.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe Sinatra::Hat::Model do
|
4
|
+
attr_reader :model, :maker, :fake_request
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
build_models!
|
8
|
+
end
|
9
|
+
|
10
|
+
def new_model(maker=new_maker)
|
11
|
+
Sinatra::Hat::Model.new(maker)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "initialization" do
|
15
|
+
it "takes an instance of Maker" do
|
16
|
+
proc {
|
17
|
+
maker = new_maker
|
18
|
+
new_model(maker)
|
19
|
+
}.should_not raise_error
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "find_last_modified" do
|
24
|
+
before(:each) do
|
25
|
+
@last_modified_article = Article.create!
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns the last modified record" do
|
29
|
+
new_model.find_last_modified([@article, @last_modified_article]).should == @last_modified_article
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "all()" do
|
34
|
+
before(:each) do
|
35
|
+
@maker = new_maker
|
36
|
+
@model = new_model(maker)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "takes the params" do
|
40
|
+
proc {
|
41
|
+
model.all({ })
|
42
|
+
}.should_not raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
it "calls the finder" do
|
46
|
+
mock.proxy(maker.options[:finder]).call(Article, { })
|
47
|
+
model.all({ }).should == Article.all
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "foreign_key" do
|
52
|
+
before(:each) do
|
53
|
+
@maker = new_maker
|
54
|
+
@model = new_model(maker)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns the singular name with _id" do
|
58
|
+
model.foreign_key.should == "#{model.singular}_id".to_sym
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "find_owner" do
|
63
|
+
before(:each) do
|
64
|
+
@maker = new_maker
|
65
|
+
@model = new_model(maker)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "uses the foreign key to search the params" do
|
69
|
+
model.find_owner(model.foreign_key => @article.to_param).should == @article
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "find()" do
|
74
|
+
attr_reader :article
|
75
|
+
|
76
|
+
before(:each) do
|
77
|
+
@maker = new_maker
|
78
|
+
@model = new_model(maker)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "takes the params" do
|
82
|
+
proc {
|
83
|
+
model.find(:id => @article.to_param)
|
84
|
+
}.should_not raise_error
|
85
|
+
end
|
86
|
+
|
87
|
+
it "calls for the :record" do
|
88
|
+
mock.proxy(maker.options[:record]).call(Article, { :id => @article.to_param })
|
89
|
+
model.find(:id => @article.to_param).should == @article
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "plural" do
|
94
|
+
it "returns snakecased, pluralized form of model name" do
|
95
|
+
new_model(new_maker(Article)).plural.should == "articles"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "singular" do
|
100
|
+
it "returns snakecased, singular form of model name" do
|
101
|
+
new_model(new_maker(Article)).singular.should == "article"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "update" do
|
106
|
+
it "finds the record" do
|
107
|
+
model = new_model
|
108
|
+
mock.proxy(model).find(anything) { @article }
|
109
|
+
mock.proxy(@article).attributes = { "name" => "Hooray!" }
|
110
|
+
model.update("id" => @article.to_param, "article[name]" => "Hooray!")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "new" do
|
115
|
+
context "when there is no parent" do
|
116
|
+
it "instantiates a new model object" do
|
117
|
+
mock.proxy(Article).new(anything)
|
118
|
+
new_model.new
|
119
|
+
end
|
120
|
+
|
121
|
+
it "railsifies params passed through" do
|
122
|
+
mock.proxy(Article).new("name" => "The article")
|
123
|
+
new_model.new("article[name]" => "The article")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "when there is a parent" do
|
128
|
+
attr_reader :article
|
129
|
+
|
130
|
+
before(:each) do
|
131
|
+
@maker = new_maker(Article)
|
132
|
+
@maker.setup(mock_app)
|
133
|
+
child_maker = maker.mount(Comment)
|
134
|
+
child_maker.parent = maker
|
135
|
+
@child_model = new_model(child_maker)
|
136
|
+
end
|
137
|
+
|
138
|
+
context "when there is an association proxy" do
|
139
|
+
it "uses the association proxy" do
|
140
|
+
@child_model.new(:article_id => @article.to_param)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context "when there isn't an association proxy" do
|
145
|
+
it "just returns the klass" do
|
146
|
+
mock.proxy(Comment).new(anything)
|
147
|
+
new_model(maker.mount(Comment)).new
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe Sinatra::Hat::Resource do
|
4
|
+
before(:each) do
|
5
|
+
build_models!
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "initialization" do
|
9
|
+
it "takes an instance of Maker" do
|
10
|
+
proc {
|
11
|
+
Sinatra::Hat::Resource.new(new_maker)
|
12
|
+
}.should_not raise_error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "paths" do
|
17
|
+
context "when maker has no parent" do
|
18
|
+
before(:each) do
|
19
|
+
maker = new_maker(Article)
|
20
|
+
@resource = Sinatra::Hat::Resource.new(maker)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns normal resource path" do
|
24
|
+
@resource.path('/:id').should == "/articles/:id"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "can return :root path" do
|
28
|
+
@resource.path('/:id').should == "/articles/:id"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "can return path for model object" do
|
32
|
+
@resource.path('/:id', @article).should == "/articles/#{@article.to_param}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when maker has a parent" do
|
37
|
+
before(:each) do
|
38
|
+
@child = new_maker(Comment, :parent => new_maker(Article))
|
39
|
+
@resource = Sinatra::Hat::Resource.new(@child)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns nested resource path" do
|
43
|
+
@resource.path('/:id').should == "/articles/:article_id/comments/:id"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "doesn't mutate parents" do # regression test
|
47
|
+
@resource.path('/:id').should == "/articles/:article_id/comments/:id"
|
48
|
+
@resource.path('/:id').should == "/articles/:article_id/comments/:id"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "can return path for model object" do
|
52
|
+
@resource.path('/:id', @comment).should == "/articles/#{@article.to_param}/comments/#{@comment.to_param}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when maker has multiple parents" do
|
57
|
+
before(:each) do
|
58
|
+
build_model(:replies) { integer :comment_id; belongs_to :comment }
|
59
|
+
@reply = Reply.create! :comment => @comment
|
60
|
+
@child = new_maker(Comment, :parent => new_maker(Article))
|
61
|
+
@grand_child = new_maker(Reply, :parent => @child)
|
62
|
+
@resource = Sinatra::Hat::Resource.new(@grand_child)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "returns nested resource path" do
|
66
|
+
@resource.path('/:id').should == "/articles/:article_id/comments/:comment_id/replies/:id"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "can return path for model object" do
|
70
|
+
@resource.path('/:id', @reply).should == "/articles/#{@article.to_param}/comments/#{@comment.to_param}/replies/#{@reply.to_param}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe Sinatra::Hat::Responder do
|
4
|
+
attr_reader :maker, :responder
|
5
|
+
|
6
|
+
def new_responder(maker=@maker)
|
7
|
+
Sinatra::Hat::Responder.new(maker)
|
8
|
+
end
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
build_models!
|
12
|
+
@maker = new_maker
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "initialization" do
|
16
|
+
it "takes an instance of Maker" do
|
17
|
+
proc {
|
18
|
+
Sinatra::Hat::Responder.new(@maker)
|
19
|
+
}.should_not raise_error
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "success" do
|
24
|
+
context "when there's a default format" do
|
25
|
+
before(:each) do
|
26
|
+
@maker.format :json
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'serializes the response with the default format' do
|
30
|
+
request = fake_request
|
31
|
+
responder = new_responder
|
32
|
+
responder.success(:show, request, :article).should == :article.to_json
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when there's a format" do
|
37
|
+
it "serializes the response" do
|
38
|
+
request = fake_request(:format => "yaml")
|
39
|
+
mock.proxy(responder = new_responder).serialize(:article, "yaml")
|
40
|
+
responder.success(:show, request, :article)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when there's no format" do
|
45
|
+
it "calls that action's :success proc" do
|
46
|
+
request = fake_request
|
47
|
+
mock.proxy(Sinatra::Hat::Response).new(maker, request) do |response|
|
48
|
+
mock.proxy(response).render(anything)
|
49
|
+
end
|
50
|
+
new_responder.success(:show, request, :article)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "assigning instance variables" do
|
55
|
+
context "when the result is a collection" do
|
56
|
+
it "assigns the plural instance variable in the request" do
|
57
|
+
request = fake_request
|
58
|
+
new_responder.success(:index, request, [:articles])
|
59
|
+
request.instance_eval { @articles }.should == [:articles]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when the result is not a collection" do
|
64
|
+
it "assigns the singular instance variable in the request" do
|
65
|
+
request = fake_request
|
66
|
+
new_responder.success(:show, request, :article)
|
67
|
+
request.instance_eval { @article }.should == :article
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "assigning parent instance variables" do
|
72
|
+
before(:each) do
|
73
|
+
@parent_maker = new_maker(Article)
|
74
|
+
@child_maker = new_maker(Comment)
|
75
|
+
@responder = @child_maker.responder
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "failure" do
|
82
|
+
# context "when there's a format" do
|
83
|
+
# it "serializes the response" do
|
84
|
+
# request = fake_request(:format => "yaml")
|
85
|
+
# mock.proxy(responder = new_responder).serialize("yaml", :article)
|
86
|
+
# responder.success(:show, request, :article)
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
|
90
|
+
context "when there's no format" do
|
91
|
+
it "calls that action's :failure proc" do
|
92
|
+
request = fake_request
|
93
|
+
mock.proxy(Sinatra::Hat::Response).new(maker, request) do |response|
|
94
|
+
mock(response).redirect(anything)
|
95
|
+
end
|
96
|
+
new_responder.failure(:show, request, :article)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "serialize()" do
|
102
|
+
before(:each) do
|
103
|
+
@responder = new_responder
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when there is no formatter" do
|
107
|
+
context "when the data responds to to_*" do
|
108
|
+
it "serializes data and gets mime type" do
|
109
|
+
response, mime = responder.serialize([:article], "yaml")
|
110
|
+
response.should == [:article].to_yaml
|
111
|
+
mime.should == 'text/yaml'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "when the data doesn't respond to to_*" do
|
116
|
+
it "returns nil" do
|
117
|
+
responder.serialize([:article], "say_what").should be_nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "when there is a formatter" do
|
123
|
+
before(:each) do
|
124
|
+
maker.formats[:yaml] = proc { |data| [data, :formatted].inspect }
|
125
|
+
end
|
126
|
+
|
127
|
+
it "returns serialized data and the mime type" do
|
128
|
+
response = responder.serialize(:article, "yaml")
|
129
|
+
response.should == [[:article, :formatted].inspect, 'text/yaml']
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "custom responses using #on" do
|
135
|
+
before(:each) do
|
136
|
+
@responder = new_responder
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
class Article; end
|
4
|
+
|
5
|
+
describe Sinatra::Hat::Response do
|
6
|
+
attr_reader :maker, :response, :request
|
7
|
+
|
8
|
+
def new_response(maker=@maker)
|
9
|
+
Sinatra::Hat::Response.new(maker, request)
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
@maker = new_maker
|
14
|
+
@request = fake_request
|
15
|
+
stub(request.options).views { fixture('views') }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "render()" do
|
19
|
+
describe "rendering templates" do
|
20
|
+
it "renders the index template" do
|
21
|
+
mock.proxy(request).erb :"articles/index"
|
22
|
+
new_response.render(:index)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "renders the show template" do
|
26
|
+
mock.proxy(request).erb :"articles/show"
|
27
|
+
new_response.render(:show)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when there are options passed" do
|
32
|
+
it "sends the options to the request" do
|
33
|
+
t = Time.now
|
34
|
+
mock(request).last_modified t
|
35
|
+
new_response.render(:show, :last_modified => t)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when there is no views dir" do
|
40
|
+
before(:each) do
|
41
|
+
stub(request.options).views { nil }
|
42
|
+
end
|
43
|
+
|
44
|
+
it "raises Sinatra::NoTemplateError" do
|
45
|
+
proc {
|
46
|
+
new_response.render(:show)
|
47
|
+
}.should raise_error(Sinatra::NoTemplateError)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when the view does not exist" do
|
52
|
+
it "raises Sinatra::NoTemplateError" do
|
53
|
+
proc {
|
54
|
+
new_response.render(:nonsense)
|
55
|
+
}.should raise_error(Sinatra::NoTemplateError)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "redirect()" do
|
61
|
+
context "when passed a path" do
|
62
|
+
it "redirects to the given path" do
|
63
|
+
mock(request).redirect("/articles")
|
64
|
+
new_response.redirect("/articles")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when passed a record" do
|
69
|
+
before(:each) do
|
70
|
+
stub(@article = Article.new).id { 2 }
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when passed a record for the current maker" do
|
74
|
+
it "redirects to the resource path for that data" do
|
75
|
+
mock(request).redirect("/articles/2")
|
76
|
+
new_response.redirect(@article)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when passed a record for the current maker" do
|
81
|
+
before(:each) do
|
82
|
+
@article.save!
|
83
|
+
@comment = @article.comments.create!
|
84
|
+
@child_maker = new_maker(Comment, :parent => @maker)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "redirects to the resource path for that data" do
|
88
|
+
mock(request).redirect("/articles/#{@article.to_param}")
|
89
|
+
new_response(@child_maker).redirect(@article)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "when passed a symbol" do
|
95
|
+
before(:each) do
|
96
|
+
stub(@article = Article.new).id { 2 }
|
97
|
+
end
|
98
|
+
|
99
|
+
it "can redirect to the :index path" do
|
100
|
+
mock(request).redirect("/articles")
|
101
|
+
new_response.redirect(:index)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "can redirect to the :show path" do
|
105
|
+
mock(request).redirect("/articles/2")
|
106
|
+
new_response.redirect(:show, @article)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "can redirect to the :new path" do
|
110
|
+
mock(request).redirect("/articles/new")
|
111
|
+
new_response.redirect(:new)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "can redirect to the :edit path" do
|
115
|
+
mock(request).redirect("/articles/2/edit")
|
116
|
+
new_response.redirect(:edit, @article)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|