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.

Files changed (142) hide show
  1. data/.gitignore +13 -1
  2. data/.rspec +1 -1
  3. data/.travis.yml +1 -0
  4. data/Gemfile +10 -0
  5. data/Guardfile +15 -0
  6. data/README.markdown +472 -67
  7. data/grape.gemspec +4 -4
  8. data/lib/grape.rb +17 -5
  9. data/lib/grape/api.rb +227 -124
  10. data/lib/grape/cookies.rb +41 -0
  11. data/lib/grape/endpoint.rb +256 -27
  12. data/lib/grape/entity.rb +227 -0
  13. data/lib/grape/middleware/auth/oauth2.rb +1 -2
  14. data/lib/grape/middleware/base.rb +59 -6
  15. data/lib/grape/middleware/error.rb +15 -6
  16. data/lib/grape/middleware/filter.rb +17 -0
  17. data/lib/grape/middleware/formatter.rb +25 -31
  18. data/lib/grape/middleware/versioner.rb +20 -20
  19. data/lib/grape/middleware/versioner/header.rb +59 -0
  20. data/lib/grape/middleware/versioner/path.rb +42 -0
  21. data/lib/grape/route.rb +23 -0
  22. data/lib/grape/util/hash_stack.rb +100 -0
  23. data/lib/grape/version.rb +1 -1
  24. data/spec/grape/api_spec.rb +651 -162
  25. data/spec/grape/endpoint_spec.rb +216 -18
  26. data/spec/grape/entity_spec.rb +320 -0
  27. data/spec/grape/middleware/auth/basic_spec.rb +2 -2
  28. data/spec/grape/middleware/auth/digest_spec.rb +4 -6
  29. data/spec/grape/middleware/exception_spec.rb +1 -0
  30. data/spec/grape/middleware/formatter_spec.rb +81 -27
  31. data/spec/grape/middleware/versioner/header_spec.rb +148 -0
  32. data/spec/grape/middleware/versioner/path_spec.rb +40 -0
  33. data/spec/grape/middleware/versioner_spec.rb +6 -34
  34. data/spec/grape/util/hash_stack_spec.rb +133 -0
  35. data/spec/shared/versioning_examples.rb +77 -0
  36. data/spec/spec_helper.rb +11 -3
  37. data/spec/support/basic_auth_encode_helpers.rb +4 -0
  38. data/spec/support/rack_patch.rb +25 -0
  39. data/spec/support/versioned_helpers.rb +34 -0
  40. metadata +140 -241
  41. data/.yardoc/checksums +0 -13
  42. data/.yardoc/objects/Grape.dat +0 -0
  43. data/.yardoc/objects/Grape/API.dat +0 -0
  44. data/.yardoc/objects/Grape/API/auth_c.dat +0 -0
  45. data/.yardoc/objects/Grape/API/build_endpoint_c.dat +0 -0
  46. data/.yardoc/objects/Grape/API/call_c.dat +0 -0
  47. data/.yardoc/objects/Grape/API/compile_path_c.dat +0 -0
  48. data/.yardoc/objects/Grape/API/default_format_c.dat +0 -0
  49. data/.yardoc/objects/Grape/API/delete_c.dat +0 -0
  50. data/.yardoc/objects/Grape/API/get_c.dat +0 -0
  51. data/.yardoc/objects/Grape/API/group_c.dat +0 -0
  52. data/.yardoc/objects/Grape/API/head_c.dat +0 -0
  53. data/.yardoc/objects/Grape/API/helpers_c.dat +0 -0
  54. data/.yardoc/objects/Grape/API/http_basic_c.dat +0 -0
  55. data/.yardoc/objects/Grape/API/inherited_c.dat +0 -0
  56. data/.yardoc/objects/Grape/API/logger_c.dat +0 -0
  57. data/.yardoc/objects/Grape/API/namespace_c.dat +0 -0
  58. data/.yardoc/objects/Grape/API/nest_c.dat +0 -0
  59. data/.yardoc/objects/Grape/API/post_c.dat +0 -0
  60. data/.yardoc/objects/Grape/API/prefix_c.dat +0 -0
  61. data/.yardoc/objects/Grape/API/put_c.dat +0 -0
  62. data/.yardoc/objects/Grape/API/reset_21_c.dat +0 -0
  63. data/.yardoc/objects/Grape/API/resource_c.dat +0 -0
  64. data/.yardoc/objects/Grape/API/resources_c.dat +0 -0
  65. data/.yardoc/objects/Grape/API/route_c.dat +0 -0
  66. data/.yardoc/objects/Grape/API/route_set_c.dat +0 -0
  67. data/.yardoc/objects/Grape/API/scope_c.dat +0 -0
  68. data/.yardoc/objects/Grape/API/set_c.dat +0 -0
  69. data/.yardoc/objects/Grape/API/settings_c.dat +0 -0
  70. data/.yardoc/objects/Grape/API/settings_stack_c.dat +0 -0
  71. data/.yardoc/objects/Grape/API/version_c.dat +0 -0
  72. data/.yardoc/objects/Grape/Endpoint.dat +0 -0
  73. data/.yardoc/objects/Grape/Endpoint/block_3D_c.dat +0 -0
  74. data/.yardoc/objects/Grape/Endpoint/block_c.dat +0 -0
  75. data/.yardoc/objects/Grape/Endpoint/call_c.dat +0 -0
  76. data/.yardoc/objects/Grape/Endpoint/call_i.dat +0 -0
  77. data/.yardoc/objects/Grape/Endpoint/env_i.dat +0 -0
  78. data/.yardoc/objects/Grape/Endpoint/error_21_i.dat +0 -0
  79. data/.yardoc/objects/Grape/Endpoint/generate_c.dat +0 -0
  80. data/.yardoc/objects/Grape/Endpoint/header_i.dat +0 -0
  81. data/.yardoc/objects/Grape/Endpoint/params_i.dat +0 -0
  82. data/.yardoc/objects/Grape/Endpoint/request_i.dat +0 -0
  83. data/.yardoc/objects/Grape/Endpoint/status_i.dat +0 -0
  84. data/.yardoc/objects/Grape/Endpoint/version_i.dat +0 -0
  85. data/.yardoc/objects/Grape/Middleware.dat +0 -0
  86. data/.yardoc/objects/Grape/Middleware/Auth.dat +0 -0
  87. data/.yardoc/objects/Grape/Middleware/Auth/Basic.dat +0 -0
  88. data/.yardoc/objects/Grape/Middleware/Auth/Basic/authenticator_i.dat +0 -0
  89. data/.yardoc/objects/Grape/Middleware/Auth/Basic/basic_request_i.dat +0 -0
  90. data/.yardoc/objects/Grape/Middleware/Auth/Basic/before_i.dat +0 -0
  91. data/.yardoc/objects/Grape/Middleware/Auth/Basic/credentials_i.dat +0 -0
  92. data/.yardoc/objects/Grape/Middleware/Auth/Basic/initialize_i.dat +0 -0
  93. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2.dat +0 -0
  94. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/before_i.dat +0 -0
  95. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/default_options_i.dat +0 -0
  96. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/error_out_i.dat +0 -0
  97. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/parse_authorization_header_i.dat +0 -0
  98. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/token_class_i.dat +0 -0
  99. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/verify_token_i.dat +0 -0
  100. data/.yardoc/objects/Grape/Middleware/Base.dat +0 -0
  101. data/.yardoc/objects/Grape/Middleware/Base/after_i.dat +0 -0
  102. data/.yardoc/objects/Grape/Middleware/Base/app_i.dat +0 -0
  103. data/.yardoc/objects/Grape/Middleware/Base/before_i.dat +0 -0
  104. data/.yardoc/objects/Grape/Middleware/Base/call_21_i.dat +0 -0
  105. data/.yardoc/objects/Grape/Middleware/Base/call_i.dat +0 -0
  106. data/.yardoc/objects/Grape/Middleware/Base/default_options_i.dat +0 -0
  107. data/.yardoc/objects/Grape/Middleware/Base/env_i.dat +0 -0
  108. data/.yardoc/objects/Grape/Middleware/Base/initialize_i.dat +0 -0
  109. data/.yardoc/objects/Grape/Middleware/Base/options_i.dat +0 -0
  110. data/.yardoc/objects/Grape/Middleware/Base/request_i.dat +0 -0
  111. data/.yardoc/objects/Grape/Middleware/Base/response_i.dat +0 -0
  112. data/.yardoc/objects/Grape/Middleware/Error.dat +0 -0
  113. data/.yardoc/objects/Grape/Middleware/Error/call_21_i.dat +0 -0
  114. data/.yardoc/objects/Grape/Middleware/Error/error_response_i.dat +0 -0
  115. data/.yardoc/objects/Grape/Middleware/Formatter.dat +0 -0
  116. data/.yardoc/objects/Grape/Middleware/Formatter/CONTENT_TYPES.dat +0 -0
  117. data/.yardoc/objects/Grape/Middleware/Formatter/after_i.dat +0 -0
  118. data/.yardoc/objects/Grape/Middleware/Formatter/before_i.dat +0 -0
  119. data/.yardoc/objects/Grape/Middleware/Formatter/content_types_i.dat +0 -0
  120. data/.yardoc/objects/Grape/Middleware/Formatter/default_options_i.dat +0 -0
  121. data/.yardoc/objects/Grape/Middleware/Formatter/encode_json_i.dat +0 -0
  122. data/.yardoc/objects/Grape/Middleware/Formatter/encode_txt_i.dat +0 -0
  123. data/.yardoc/objects/Grape/Middleware/Formatter/format_from_extension_i.dat +0 -0
  124. data/.yardoc/objects/Grape/Middleware/Formatter/format_from_header_i.dat +0 -0
  125. data/.yardoc/objects/Grape/Middleware/Formatter/headers_i.dat +0 -0
  126. data/.yardoc/objects/Grape/Middleware/Formatter/mime_array_i.dat +0 -0
  127. data/.yardoc/objects/Grape/Middleware/Formatter/mime_types_i.dat +0 -0
  128. data/.yardoc/objects/Grape/Middleware/Prefixer.dat +0 -0
  129. data/.yardoc/objects/Grape/Middleware/Prefixer/before_i.dat +0 -0
  130. data/.yardoc/objects/Grape/Middleware/Prefixer/prefix_i.dat +0 -0
  131. data/.yardoc/objects/Grape/Middleware/Versioner.dat +0 -0
  132. data/.yardoc/objects/Grape/Middleware/Versioner/before_i.dat +0 -0
  133. data/.yardoc/objects/Grape/Middleware/Versioner/default_options_i.dat +0 -0
  134. data/.yardoc/objects/Grape/MiddlewareStack.dat +0 -0
  135. data/.yardoc/objects/Grape/MiddlewareStack/initialize_i.dat +0 -0
  136. data/.yardoc/objects/Grape/MiddlewareStack/stack_i.dat +0 -0
  137. data/.yardoc/objects/Grape/MiddlewareStack/to_app_i.dat +0 -0
  138. data/.yardoc/objects/Grape/MiddlewareStack/use_i.dat +0 -0
  139. data/.yardoc/objects/root.dat +0 -0
  140. data/.yardoc/proxy_types +0 -2
  141. data/Gemfile.lock +0 -52
  142. data/autotest/discover.rb +0 -1
@@ -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