grape 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- data/.gitignore +13 -1
- data/.rspec +1 -1
- data/.travis.yml +1 -0
- data/Gemfile +10 -0
- data/Guardfile +15 -0
- data/README.markdown +472 -67
- data/grape.gemspec +4 -4
- data/lib/grape.rb +17 -5
- data/lib/grape/api.rb +227 -124
- data/lib/grape/cookies.rb +41 -0
- data/lib/grape/endpoint.rb +256 -27
- data/lib/grape/entity.rb +227 -0
- data/lib/grape/middleware/auth/oauth2.rb +1 -2
- data/lib/grape/middleware/base.rb +59 -6
- data/lib/grape/middleware/error.rb +15 -6
- data/lib/grape/middleware/filter.rb +17 -0
- data/lib/grape/middleware/formatter.rb +25 -31
- data/lib/grape/middleware/versioner.rb +20 -20
- data/lib/grape/middleware/versioner/header.rb +59 -0
- data/lib/grape/middleware/versioner/path.rb +42 -0
- data/lib/grape/route.rb +23 -0
- data/lib/grape/util/hash_stack.rb +100 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +651 -162
- data/spec/grape/endpoint_spec.rb +216 -18
- data/spec/grape/entity_spec.rb +320 -0
- data/spec/grape/middleware/auth/basic_spec.rb +2 -2
- data/spec/grape/middleware/auth/digest_spec.rb +4 -6
- data/spec/grape/middleware/exception_spec.rb +1 -0
- data/spec/grape/middleware/formatter_spec.rb +81 -27
- data/spec/grape/middleware/versioner/header_spec.rb +148 -0
- data/spec/grape/middleware/versioner/path_spec.rb +40 -0
- data/spec/grape/middleware/versioner_spec.rb +6 -34
- data/spec/grape/util/hash_stack_spec.rb +133 -0
- data/spec/shared/versioning_examples.rb +77 -0
- data/spec/spec_helper.rb +11 -3
- data/spec/support/basic_auth_encode_helpers.rb +4 -0
- data/spec/support/rack_patch.rb +25 -0
- data/spec/support/versioned_helpers.rb +34 -0
- metadata +140 -241
- data/.yardoc/checksums +0 -13
- data/.yardoc/objects/Grape.dat +0 -0
- data/.yardoc/objects/Grape/API.dat +0 -0
- data/.yardoc/objects/Grape/API/auth_c.dat +0 -0
- data/.yardoc/objects/Grape/API/build_endpoint_c.dat +0 -0
- data/.yardoc/objects/Grape/API/call_c.dat +0 -0
- data/.yardoc/objects/Grape/API/compile_path_c.dat +0 -0
- data/.yardoc/objects/Grape/API/default_format_c.dat +0 -0
- data/.yardoc/objects/Grape/API/delete_c.dat +0 -0
- data/.yardoc/objects/Grape/API/get_c.dat +0 -0
- data/.yardoc/objects/Grape/API/group_c.dat +0 -0
- data/.yardoc/objects/Grape/API/head_c.dat +0 -0
- data/.yardoc/objects/Grape/API/helpers_c.dat +0 -0
- data/.yardoc/objects/Grape/API/http_basic_c.dat +0 -0
- data/.yardoc/objects/Grape/API/inherited_c.dat +0 -0
- data/.yardoc/objects/Grape/API/logger_c.dat +0 -0
- data/.yardoc/objects/Grape/API/namespace_c.dat +0 -0
- data/.yardoc/objects/Grape/API/nest_c.dat +0 -0
- data/.yardoc/objects/Grape/API/post_c.dat +0 -0
- data/.yardoc/objects/Grape/API/prefix_c.dat +0 -0
- data/.yardoc/objects/Grape/API/put_c.dat +0 -0
- data/.yardoc/objects/Grape/API/reset_21_c.dat +0 -0
- data/.yardoc/objects/Grape/API/resource_c.dat +0 -0
- data/.yardoc/objects/Grape/API/resources_c.dat +0 -0
- data/.yardoc/objects/Grape/API/route_c.dat +0 -0
- data/.yardoc/objects/Grape/API/route_set_c.dat +0 -0
- data/.yardoc/objects/Grape/API/scope_c.dat +0 -0
- data/.yardoc/objects/Grape/API/set_c.dat +0 -0
- data/.yardoc/objects/Grape/API/settings_c.dat +0 -0
- data/.yardoc/objects/Grape/API/settings_stack_c.dat +0 -0
- data/.yardoc/objects/Grape/API/version_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/block_3D_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/block_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/call_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/call_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/env_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/error_21_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/generate_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/header_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/params_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/request_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/status_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/version_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/authenticator_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/basic_request_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/credentials_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/initialize_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/error_out_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/parse_authorization_header_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/token_class_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/verify_token_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/after_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/app_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/call_21_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/call_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/env_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/initialize_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/request_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/response_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Error.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Error/call_21_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Error/error_response_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/CONTENT_TYPES.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/after_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/content_types_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/encode_json_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/encode_txt_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/format_from_extension_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/format_from_header_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/headers_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/mime_array_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/mime_types_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Prefixer.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Prefixer/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Prefixer/prefix_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Versioner.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Versioner/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Versioner/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/initialize_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/stack_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/to_app_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/use_i.dat +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -2
- data/Gemfile.lock +0 -52
- data/autotest/discover.rb +0 -1
data/spec/grape/endpoint_spec.rb
CHANGED
@@ -2,71 +2,157 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Grape::Endpoint do
|
4
4
|
subject { Class.new(Grape::API) }
|
5
|
-
before { subject.default_format :txt }
|
6
5
|
def app; subject end
|
7
|
-
|
6
|
+
|
7
|
+
describe '#initialize' do
|
8
|
+
it 'should take a settings stack, options, and a block' do
|
9
|
+
expect{ Grape::Endpoint.new(Grape::Util::HashStack.new, {
|
10
|
+
:path => '/',
|
11
|
+
:method => :get
|
12
|
+
}, &Proc.new{}) }.not_to raise_error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should set itself in the env upon call' do
|
17
|
+
subject.get('/'){ "Hello world." }
|
18
|
+
get '/'
|
19
|
+
last_request.env['api.endpoint'].should be_kind_of(Grape::Endpoint)
|
20
|
+
end
|
21
|
+
|
8
22
|
describe '#status' do
|
9
23
|
it 'should be callable from within a block' do
|
10
24
|
subject.get('/home') do
|
11
25
|
status 206
|
12
26
|
"Hello"
|
13
27
|
end
|
14
|
-
|
28
|
+
|
15
29
|
get '/home'
|
16
30
|
last_response.status.should == 206
|
17
31
|
last_response.body.should == "Hello"
|
18
32
|
end
|
19
33
|
end
|
20
|
-
|
34
|
+
|
21
35
|
describe '#header' do
|
22
36
|
it 'should be callable from within a block' do
|
23
37
|
subject.get('/hey') do
|
24
38
|
header 'X-Awesome', 'true'
|
25
39
|
"Awesome"
|
26
40
|
end
|
27
|
-
|
41
|
+
|
28
42
|
get '/hey'
|
29
43
|
last_response.headers['X-Awesome'].should == 'true'
|
30
44
|
end
|
31
45
|
end
|
46
|
+
|
47
|
+
describe '#cookies' do
|
48
|
+
it 'should be callable from within a block' do
|
49
|
+
subject.get('/get/cookies') do
|
50
|
+
cookies['my-awesome-cookie1'] = 'is cool'
|
51
|
+
cookies['my-awesome-cookie2'] = {
|
52
|
+
:value => 'is cool too',
|
53
|
+
:domain => 'my.example.com',
|
54
|
+
:path => '/',
|
55
|
+
:secure => true,
|
56
|
+
}
|
57
|
+
cookies[:cookie3] = 'symbol'
|
58
|
+
cookies['cookie4'] = 'secret code here'
|
59
|
+
end
|
60
|
+
|
61
|
+
get('/get/cookies')
|
62
|
+
|
63
|
+
last_response.headers['Set-Cookie'].split("\n").sort.should eql [
|
64
|
+
"cookie3=symbol",
|
65
|
+
"cookie4=secret+code+here",
|
66
|
+
"my-awesome-cookie1=is+cool",
|
67
|
+
"my-awesome-cookie2=is+cool+too; domain=my.example.com; path=/; secure"
|
68
|
+
]
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should set browser cookies and should not set response cookies" do
|
72
|
+
subject.get('/username') do
|
73
|
+
cookies[:username]
|
74
|
+
end
|
75
|
+
get('/username', {}, 'HTTP_COOKIE' => 'username=mrplum; sandbox=true')
|
76
|
+
|
77
|
+
last_response.body.should == 'mrplum'
|
78
|
+
last_response.headers['Set-Cookie'].should_not =~ /username=mrplum/
|
79
|
+
last_response.headers['Set-Cookie'].should_not =~ /sandbox=true/
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should set and update browser cookies" do
|
83
|
+
subject.get('/username') do
|
84
|
+
cookies[:sandbox] = true if cookies[:sandbox] == 'false'
|
85
|
+
cookies[:username] += "_test"
|
86
|
+
end
|
87
|
+
get('/username', {}, 'HTTP_COOKIE' => 'username=user; sandbox=false')
|
88
|
+
last_response.body.should == 'user_test'
|
89
|
+
last_response.headers['Set-Cookie'].should =~ /username=user_test/
|
90
|
+
last_response.headers['Set-Cookie'].should =~ /sandbox=true/
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should delete cookie" do
|
94
|
+
subject.get('/test') do
|
95
|
+
sum = 0
|
96
|
+
cookies.each do |name, val|
|
97
|
+
sum += val.to_i
|
98
|
+
cookies.delete name
|
99
|
+
end
|
100
|
+
sum
|
101
|
+
end
|
102
|
+
get('/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2')
|
103
|
+
last_response.body.should == '3'
|
104
|
+
last_response.headers['Set-Cookie'].split("\n").sort.should == [
|
105
|
+
"and_this=deleted; expires=Thu, 01-Jan-1970 00:00:00 GMT",
|
106
|
+
"delete_this_cookie=deleted; expires=Thu, 01-Jan-1970 00:00:00 GMT"
|
107
|
+
]
|
108
|
+
end
|
109
|
+
end
|
32
110
|
|
33
111
|
describe '#params' do
|
34
112
|
it 'should be available to the caller' do
|
35
113
|
subject.get('/hey') do
|
36
114
|
params[:howdy]
|
37
115
|
end
|
38
|
-
|
116
|
+
|
39
117
|
get '/hey?howdy=hey'
|
40
118
|
last_response.body.should == 'hey'
|
41
119
|
end
|
42
|
-
|
120
|
+
|
43
121
|
it 'should parse from path segments' do
|
44
122
|
subject.get('/hey/:id') do
|
45
123
|
params[:id]
|
46
124
|
end
|
47
|
-
|
125
|
+
|
48
126
|
get '/hey/12'
|
49
127
|
last_response.body.should == '12'
|
50
128
|
end
|
129
|
+
|
130
|
+
it 'should deeply convert nested params' do
|
131
|
+
subject.get '/location' do
|
132
|
+
params[:location][:city]
|
133
|
+
end
|
134
|
+
get '/location?location[city]=Dallas'
|
135
|
+
last_response.body.should == 'Dallas'
|
136
|
+
end
|
51
137
|
end
|
52
|
-
|
138
|
+
|
53
139
|
describe '#error!' do
|
54
140
|
it 'should accept a message' do
|
55
141
|
subject.get('/hey') do
|
56
142
|
error! "This is not valid."
|
57
143
|
"This is valid."
|
58
144
|
end
|
59
|
-
|
145
|
+
|
60
146
|
get '/hey'
|
61
147
|
last_response.status.should == 403
|
62
148
|
last_response.body.should == "This is not valid."
|
63
149
|
end
|
64
|
-
|
150
|
+
|
65
151
|
it 'should accept a code' do
|
66
152
|
subject.get('/hey') do
|
67
153
|
error! "Unauthorized.", 401
|
68
154
|
end
|
69
|
-
|
155
|
+
|
70
156
|
get '/hey'
|
71
157
|
last_response.status.should == 401
|
72
158
|
last_response.body.should == "Unauthorized."
|
@@ -82,33 +168,145 @@ describe Grape::Endpoint do
|
|
82
168
|
last_response.body.should == '{"dude":"rad"}'
|
83
169
|
end
|
84
170
|
end
|
85
|
-
|
171
|
+
|
86
172
|
it 'should not persist params between calls' do
|
87
173
|
subject.post('/new') do
|
88
174
|
params[:text]
|
89
175
|
end
|
90
|
-
|
176
|
+
|
91
177
|
post '/new', :text => 'abc'
|
92
178
|
last_response.body.should == 'abc'
|
93
|
-
|
179
|
+
|
94
180
|
post '/new', :text => 'def'
|
95
181
|
last_response.body.should == 'def'
|
96
182
|
end
|
97
|
-
|
183
|
+
|
98
184
|
it 'should reset all instance variables (except block) between calls' do
|
99
185
|
subject.helpers do
|
100
186
|
def memoized
|
101
187
|
@memoized ||= params[:howdy]
|
102
188
|
end
|
103
189
|
end
|
104
|
-
|
190
|
+
|
105
191
|
subject.get('/hello') do
|
106
192
|
memoized
|
107
193
|
end
|
108
|
-
|
194
|
+
|
109
195
|
get '/hello?howdy=hey'
|
110
196
|
last_response.body.should == 'hey'
|
111
197
|
get '/hello?howdy=yo'
|
112
198
|
last_response.body.should == 'yo'
|
113
199
|
end
|
200
|
+
|
201
|
+
describe '#present' do
|
202
|
+
it 'should just set the object as the body if no options are provided' do
|
203
|
+
subject.get '/example' do
|
204
|
+
present({:abc => 'def'})
|
205
|
+
body.should == {:abc => 'def'}
|
206
|
+
end
|
207
|
+
get '/example'
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'should call through to the provided entity class if one is given' do
|
211
|
+
subject.get '/example' do
|
212
|
+
entity_mock = Object.new
|
213
|
+
entity_mock.should_receive(:represent)
|
214
|
+
present Object.new, :with => entity_mock
|
215
|
+
end
|
216
|
+
get '/example'
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'should pull a representation from the class options if it exists' do
|
220
|
+
entity = Class.new(Grape::Entity)
|
221
|
+
entity.stub!(:represent).and_return("Hiya")
|
222
|
+
|
223
|
+
subject.represent Object, :with => entity
|
224
|
+
subject.get '/example' do
|
225
|
+
present Object.new
|
226
|
+
end
|
227
|
+
get '/example'
|
228
|
+
last_response.body.should == 'Hiya'
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'should pull a representation from the class ancestor if it exists' do
|
232
|
+
entity = Class.new(Grape::Entity)
|
233
|
+
entity.stub!(:represent).and_return("Hiya")
|
234
|
+
|
235
|
+
subclass = Class.new(Object)
|
236
|
+
|
237
|
+
subject.represent Object, :with => entity
|
238
|
+
subject.get '/example' do
|
239
|
+
present subclass.new
|
240
|
+
end
|
241
|
+
get '/example'
|
242
|
+
last_response.body.should == 'Hiya'
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'should add a root key to the output if one is given' do
|
246
|
+
subject.get '/example' do
|
247
|
+
present({:abc => 'def'}, :root => :root)
|
248
|
+
body.should == {:root => {:abc => 'def'}}
|
249
|
+
end
|
250
|
+
get '/example'
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'filters' do
|
255
|
+
describe 'before filters' do
|
256
|
+
it 'should run the before filter if set' do
|
257
|
+
subject.before{ env['before_test'] = "OK" }
|
258
|
+
subject.get('/before_test'){ env['before_test'] }
|
259
|
+
|
260
|
+
get '/before_test'
|
261
|
+
last_response.body.should == "OK"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe 'after filters' do
|
266
|
+
it 'should override the response body if it sets it' do
|
267
|
+
subject.after{ body "after" }
|
268
|
+
subject.get('/after_test'){ "during" }
|
269
|
+
get '/after_test'
|
270
|
+
last_response.body.should == 'after'
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'should not override the response body with its return' do
|
274
|
+
subject.after{ "after" }
|
275
|
+
subject.get('/after_test'){ "body" }
|
276
|
+
get '/after_test'
|
277
|
+
last_response.body.should == "body"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
context 'anchoring' do
|
283
|
+
verbs = %w(post get head delete put options patch)
|
284
|
+
|
285
|
+
verbs.each do |verb|
|
286
|
+
it "should allow for the anchoring option with a #{verb.upcase} method" do
|
287
|
+
subject.send(verb, '/example', :anchor => true) do
|
288
|
+
verb
|
289
|
+
end
|
290
|
+
send(verb, '/example/and/some/more')
|
291
|
+
last_response.status.should eql 404
|
292
|
+
end
|
293
|
+
|
294
|
+
it "should anchor paths by default for the #{verb.upcase} method" do
|
295
|
+
subject.send(verb, '/example') do
|
296
|
+
verb
|
297
|
+
end
|
298
|
+
send(verb, '/example/and/some/more')
|
299
|
+
last_response.status.should eql 404
|
300
|
+
end
|
301
|
+
|
302
|
+
it "should respond to /example/and/some/more for the non-anchored #{verb.upcase} method" do
|
303
|
+
subject.send(verb, '/example', :anchor => false) do
|
304
|
+
verb
|
305
|
+
end
|
306
|
+
send(verb, '/example/and/some/more')
|
307
|
+
last_response.status.should eql (verb == "post" ? 201 : 200)
|
308
|
+
last_response.body.should eql verb
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
114
312
|
end
|
@@ -0,0 +1,320 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Entity do
|
4
|
+
let(:fresh_class){ Class.new(Grape::Entity) }
|
5
|
+
|
6
|
+
context 'class methods' do
|
7
|
+
subject{ fresh_class }
|
8
|
+
|
9
|
+
describe '.expose' do
|
10
|
+
context 'multiple attributes' do
|
11
|
+
it 'should be able to add multiple exposed attributes with a single call' do
|
12
|
+
subject.expose :name, :email, :location
|
13
|
+
subject.exposures.size.should == 3
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should set the same options for all exposures passed' do
|
17
|
+
subject.expose :name, :email, :location, :foo => :bar
|
18
|
+
subject.exposures.values.each{|v| v.should == {:foo => :bar}}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'option validation' do
|
23
|
+
it 'should make sure that :as only works on single attribute calls' do
|
24
|
+
expect{ subject.expose :name, :email, :as => :foo }.to raise_error(ArgumentError)
|
25
|
+
expect{ subject.expose :name, :as => :foo }.not_to raise_error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with a block' do
|
30
|
+
it 'should error out if called with multiple attributes' do
|
31
|
+
expect{ subject.expose(:name, :email) do
|
32
|
+
true
|
33
|
+
end }.to raise_error(ArgumentError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should set the :proc option in the exposure options' do
|
37
|
+
block = lambda{|obj,opts| true }
|
38
|
+
subject.expose :name, &block
|
39
|
+
subject.exposures[:name][:proc].should == block
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'inherited exposures' do
|
44
|
+
it 'should return exposures from an ancestor' do
|
45
|
+
subject.expose :name, :email
|
46
|
+
child_class = Class.new(subject)
|
47
|
+
|
48
|
+
child_class.exposures.should eq(subject.exposures)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should return exposures from multiple ancestor' do
|
52
|
+
subject.expose :name, :email
|
53
|
+
parent_class = Class.new(subject)
|
54
|
+
child_class = Class.new(parent_class)
|
55
|
+
|
56
|
+
child_class.exposures.should eq(subject.exposures)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should return descendant exposures as a priotity' do
|
60
|
+
subject.expose :name, :email
|
61
|
+
child_class = Class.new(subject)
|
62
|
+
child_class.expose :name do |n|
|
63
|
+
'foo'
|
64
|
+
end
|
65
|
+
|
66
|
+
subject.exposures[:name].should_not have_key :proc
|
67
|
+
child_class.exposures[:name].should have_key :proc
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '.represent' do
|
73
|
+
it 'should return a single entity if called with one object' do
|
74
|
+
subject.represent(Object.new).should be_kind_of(subject)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should return a single entity if called with a hash' do
|
78
|
+
subject.represent(Hash.new).should be_kind_of(subject)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should return multiple entities if called with a collection' do
|
82
|
+
representation = subject.represent(4.times.map{Object.new})
|
83
|
+
representation.should be_kind_of(Array)
|
84
|
+
representation.size.should == 4
|
85
|
+
representation.reject{|r| r.kind_of?(subject)}.should be_empty
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should add the :collection => true option if called with a collection' do
|
89
|
+
representation = subject.represent(4.times.map{Object.new})
|
90
|
+
representation.each{|r| r.options[:collection].should be_true}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '.root' do
|
95
|
+
context 'with singular and plural root keys' do
|
96
|
+
before(:each) do
|
97
|
+
subject.root 'things', 'thing'
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'with a single object' do
|
101
|
+
it 'should allow a root element name to be specified' do
|
102
|
+
representation = subject.represent(Object.new)
|
103
|
+
representation.should be_kind_of(Hash)
|
104
|
+
representation.should have_key('thing')
|
105
|
+
representation['thing'].should be_kind_of(subject)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'with an array of objects' do
|
110
|
+
it 'should allow a root element name to be specified' do
|
111
|
+
representation = subject.represent(4.times.map{Object.new})
|
112
|
+
representation.should be_kind_of(Hash)
|
113
|
+
representation.should have_key('things')
|
114
|
+
representation['things'].should be_kind_of(Array)
|
115
|
+
representation['things'].size.should == 4
|
116
|
+
representation['things'].reject{|r| r.kind_of?(subject)}.should be_empty
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'it can be overridden' do
|
121
|
+
it 'can be disabled' do
|
122
|
+
representation = subject.represent(4.times.map{Object.new}, :root=>false)
|
123
|
+
representation.should be_kind_of(Array)
|
124
|
+
representation.size.should == 4
|
125
|
+
representation.reject{|r| r.kind_of?(subject)}.should be_empty
|
126
|
+
end
|
127
|
+
it 'can use a different name' do
|
128
|
+
representation = subject.represent(4.times.map{Object.new}, :root=>'others')
|
129
|
+
representation.should be_kind_of(Hash)
|
130
|
+
representation.should have_key('others')
|
131
|
+
representation['others'].should be_kind_of(Array)
|
132
|
+
representation['others'].size.should == 4
|
133
|
+
representation['others'].reject{|r| r.kind_of?(subject)}.should be_empty
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'with singular root key' do
|
139
|
+
before(:each) do
|
140
|
+
subject.root nil, 'thing'
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'with a single object' do
|
144
|
+
it 'should allow a root element name to be specified' do
|
145
|
+
representation = subject.represent(Object.new)
|
146
|
+
representation.should be_kind_of(Hash)
|
147
|
+
representation.should have_key('thing')
|
148
|
+
representation['thing'].should be_kind_of(subject)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'with an array of objects' do
|
153
|
+
it 'should allow a root element name to be specified' do
|
154
|
+
representation = subject.represent(4.times.map{Object.new})
|
155
|
+
representation.should be_kind_of(Array)
|
156
|
+
representation.size.should == 4
|
157
|
+
representation.reject{|r| r.kind_of?(subject)}.should be_empty
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'with plural root key' do
|
163
|
+
before(:each) do
|
164
|
+
subject.root 'things'
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'with a single object' do
|
168
|
+
it 'should allow a root element name to be specified' do
|
169
|
+
subject.represent(Object.new).should be_kind_of(subject)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'with an array of objects' do
|
174
|
+
it 'should allow a root element name to be specified' do
|
175
|
+
representation = subject.represent(4.times.map{Object.new})
|
176
|
+
representation.should be_kind_of(Hash)
|
177
|
+
representation.should have_key('things')
|
178
|
+
representation['things'].should be_kind_of(Array)
|
179
|
+
representation['things'].size.should == 4
|
180
|
+
representation['things'].reject{|r| r.kind_of?(subject)}.should be_empty
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe '#initialize' do
|
187
|
+
it 'should take an object and an optional options hash' do
|
188
|
+
expect{ subject.new(Object.new) }.not_to raise_error
|
189
|
+
expect{ subject.new }.to raise_error(ArgumentError)
|
190
|
+
expect{ subject.new(Object.new, {}) }.not_to raise_error
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'should have attribute readers for the object and options' do
|
194
|
+
entity = subject.new('abc', {})
|
195
|
+
entity.object.should == 'abc'
|
196
|
+
entity.options.should == {}
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'instance methods' do
|
202
|
+
let(:model){ mock(attributes) }
|
203
|
+
let(:attributes){ {
|
204
|
+
:name => 'Bob Bobson',
|
205
|
+
:email => 'bob@example.com',
|
206
|
+
:friends => [
|
207
|
+
mock(:name => "Friend 1", :email => 'friend1@example.com', :friends => []),
|
208
|
+
mock(:name => "Friend 2", :email => 'friend2@example.com', :friends => [])
|
209
|
+
]
|
210
|
+
} }
|
211
|
+
subject{ fresh_class.new(model) }
|
212
|
+
|
213
|
+
describe '#serializable_hash' do
|
214
|
+
it 'should not throw an exception if a nil options object is passed' do
|
215
|
+
expect{ fresh_class.new(model).serializable_hash(nil) }.not_to raise_error
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'should not blow up when the model is nil' do
|
219
|
+
fresh_class.expose :name
|
220
|
+
expect{ fresh_class.new(nil).serializable_hash }.not_to raise_error
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
describe '#value_for' do
|
225
|
+
before do
|
226
|
+
fresh_class.class_eval do
|
227
|
+
expose :name, :email
|
228
|
+
expose :friends, :using => self
|
229
|
+
expose :computed do |object, options|
|
230
|
+
options[:awesome]
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'should pass through bare expose attributes' do
|
236
|
+
subject.send(:value_for, :name).should == attributes[:name]
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'should instantiate a representation if that is called for' do
|
240
|
+
rep = subject.send(:value_for, :friends)
|
241
|
+
rep.reject{|r| r.is_a?(fresh_class)}.should be_empty
|
242
|
+
rep.first.serializable_hash[:name].should == 'Friend 1'
|
243
|
+
rep.last.serializable_hash[:name].should == 'Friend 2'
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'should disable root key name for child representations' do
|
247
|
+
class FriendEntity < Grape::Entity
|
248
|
+
root 'friends', 'friend'
|
249
|
+
expose :name, :email
|
250
|
+
end
|
251
|
+
fresh_class.class_eval do
|
252
|
+
expose :friends, :using => FriendEntity
|
253
|
+
end
|
254
|
+
rep = subject.send(:value_for, :friends)
|
255
|
+
rep.should be_kind_of(Array)
|
256
|
+
rep.reject{|r| r.is_a?(FriendEntity)}.should be_empty
|
257
|
+
rep.first.serializable_hash[:name].should == 'Friend 1'
|
258
|
+
rep.last.serializable_hash[:name].should == 'Friend 2'
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'should call through to the proc if there is one' do
|
262
|
+
subject.send(:value_for, :computed, :awesome => 123).should == 123
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe '#key_for' do
|
267
|
+
it 'should return the attribute if no :as is set' do
|
268
|
+
fresh_class.expose :name
|
269
|
+
subject.send(:key_for, :name).should == :name
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should return a symbolized version of the attribute' do
|
273
|
+
fresh_class.expose :name
|
274
|
+
subject.send(:key_for, 'name').should == :name
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'should return the :as alias if one exists' do
|
278
|
+
fresh_class.expose :name, :as => :nombre
|
279
|
+
subject.send(:key_for, 'name').should == :nombre
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe '#conditions_met?' do
|
284
|
+
it 'should only pass through hash :if exposure if all attributes match' do
|
285
|
+
exposure_options = {:if => {:condition1 => true, :condition2 => true}}
|
286
|
+
|
287
|
+
subject.send(:conditions_met?, exposure_options, {}).should be_false
|
288
|
+
subject.send(:conditions_met?, exposure_options, :condition1 => true).should be_false
|
289
|
+
subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true).should be_true
|
290
|
+
subject.send(:conditions_met?, exposure_options, :condition1 => false, :condition2 => true).should be_false
|
291
|
+
subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true, :other => true).should be_true
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'should only pass through proc :if exposure if it returns truthy value' do
|
295
|
+
exposure_options = {:if => lambda{|obj,opts| opts[:true]}}
|
296
|
+
|
297
|
+
subject.send(:conditions_met?, exposure_options, :true => false).should be_false
|
298
|
+
subject.send(:conditions_met?, exposure_options, :true => true).should be_true
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'should only pass through hash :unless exposure if any attributes do not match' do
|
302
|
+
exposure_options = {:unless => {:condition1 => true, :condition2 => true}}
|
303
|
+
|
304
|
+
subject.send(:conditions_met?, exposure_options, {}).should be_true
|
305
|
+
subject.send(:conditions_met?, exposure_options, :condition1 => true).should be_false
|
306
|
+
subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true).should be_false
|
307
|
+
subject.send(:conditions_met?, exposure_options, :condition1 => false, :condition2 => true).should be_false
|
308
|
+
subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true, :other => true).should be_false
|
309
|
+
subject.send(:conditions_met?, exposure_options, :condition1 => false, :condition2 => false).should be_true
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'should only pass through proc :unless exposure if it returns falsy value' do
|
313
|
+
exposure_options = {:unless => lambda{|object,options| options[:true] == true}}
|
314
|
+
|
315
|
+
subject.send(:conditions_met?, exposure_options, :true => false).should be_true
|
316
|
+
subject.send(:conditions_met?, exposure_options, :true => true).should be_false
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|