grape-entity 0.4.8 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.coveralls.yml +1 -0
- data/.github/dependabot.yml +20 -0
- data/.github/workflows/ci.yml +41 -0
- data/.gitignore +5 -1
- data/.rspec +2 -1
- data/.rubocop.yml +85 -2
- data/.rubocop_todo.yml +41 -33
- data/CHANGELOG.md +243 -37
- data/CONTRIBUTING.md +1 -1
- data/Dangerfile +3 -0
- data/Gemfile +11 -7
- data/Guardfile +4 -2
- data/LICENSE +1 -1
- data/README.md +272 -19
- data/Rakefile +9 -11
- data/UPGRADING.md +35 -0
- data/bench/serializing.rb +7 -0
- data/grape-entity.gemspec +13 -8
- data/lib/grape-entity.rb +2 -0
- data/lib/grape_entity/condition/base.rb +37 -0
- data/lib/grape_entity/condition/block_condition.rb +23 -0
- data/lib/grape_entity/condition/hash_condition.rb +27 -0
- data/lib/grape_entity/condition/symbol_condition.rb +23 -0
- data/lib/grape_entity/condition.rb +35 -0
- data/lib/grape_entity/delegator/base.rb +7 -0
- data/lib/grape_entity/delegator/fetchable_object.rb +2 -0
- data/lib/grape_entity/delegator/hash_object.rb +4 -2
- data/lib/grape_entity/delegator/openstruct_object.rb +2 -0
- data/lib/grape_entity/delegator/plain_object.rb +2 -0
- data/lib/grape_entity/delegator.rb +14 -9
- data/lib/grape_entity/deprecated.rb +13 -0
- data/lib/grape_entity/entity.rb +192 -258
- data/lib/grape_entity/exposure/base.rb +138 -0
- data/lib/grape_entity/exposure/block_exposure.rb +31 -0
- data/lib/grape_entity/exposure/delegator_exposure.rb +13 -0
- data/lib/grape_entity/exposure/formatter_block_exposure.rb +27 -0
- data/lib/grape_entity/exposure/formatter_exposure.rb +32 -0
- data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +83 -0
- data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +66 -0
- data/lib/grape_entity/exposure/nesting_exposure.rb +133 -0
- data/lib/grape_entity/exposure/represent_exposure.rb +50 -0
- data/lib/grape_entity/exposure.rb +105 -0
- data/lib/grape_entity/options.rb +132 -0
- data/lib/grape_entity/version.rb +3 -1
- data/lib/grape_entity.rb +9 -2
- data/spec/grape_entity/entity_spec.rb +903 -184
- data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +58 -0
- data/spec/grape_entity/exposure/represent_exposure_spec.rb +32 -0
- data/spec/grape_entity/exposure_spec.rb +102 -0
- data/spec/grape_entity/hash_spec.rb +91 -0
- data/spec/grape_entity/options_spec.rb +66 -0
- data/spec/spec_helper.rb +21 -2
- metadata +91 -18
- data/.travis.yml +0 -19
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'ostruct'
|
3
5
|
|
@@ -11,12 +13,12 @@ describe Grape::Entity do
|
|
11
13
|
context 'multiple attributes' do
|
12
14
|
it 'is able to add multiple exposed attributes with a single call' do
|
13
15
|
subject.expose :name, :email, :location
|
14
|
-
expect(subject.
|
16
|
+
expect(subject.root_exposures.size).to eq 3
|
15
17
|
end
|
16
18
|
|
17
|
-
it 'sets the same options for all
|
19
|
+
it 'sets the same options for all.root_exposures passed' do
|
18
20
|
subject.expose :name, :email, :location, documentation: true
|
19
|
-
subject.
|
21
|
+
subject.root_exposures.each { |v| expect(v.documentation).to eq true }
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
@@ -27,7 +29,9 @@ describe Grape::Entity do
|
|
27
29
|
end
|
28
30
|
|
29
31
|
it 'makes sure that :format_with as a proc cannot be used with a block' do
|
30
|
-
|
32
|
+
# rubocop:disable Style/BlockDelimiters
|
33
|
+
expect { subject.expose :name, format_with: proc {} do p 'hi' end }.to raise_error ArgumentError
|
34
|
+
# rubocop:enable Style/BlockDelimiters
|
31
35
|
end
|
32
36
|
|
33
37
|
it 'makes sure unknown options are not silently ignored' do
|
@@ -35,6 +39,303 @@ describe Grape::Entity do
|
|
35
39
|
end
|
36
40
|
end
|
37
41
|
|
42
|
+
context 'with a :merge option' do
|
43
|
+
let(:nested_hash) do
|
44
|
+
{ something: { like_nested_hash: true }, special: { like_nested_hash: '12' } }
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'merges an exposure to the root' do
|
48
|
+
subject.expose(:something, merge: true)
|
49
|
+
expect(subject.represent(nested_hash).serializable_hash).to eq(nested_hash[:something])
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'allows to solve collisions providing a lambda to a :merge option' do
|
53
|
+
subject.expose(:something, merge: true)
|
54
|
+
subject.expose(:special, merge: ->(_, v1, v2) { v1 && v2 ? 'brand new val' : v2 })
|
55
|
+
expect(subject.represent(nested_hash).serializable_hash).to eq(like_nested_hash: 'brand new val')
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'and nested object is nil' do
|
59
|
+
let(:nested_hash) do
|
60
|
+
{ something: nil, special: { like_nested_hash: '12' } }
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'adds nothing to output' do
|
64
|
+
subject.expose(:something, merge: true)
|
65
|
+
subject.expose(:special)
|
66
|
+
expect(subject.represent(nested_hash).serializable_hash).to eq(special: { like_nested_hash: '12' })
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'with :expose_nil option' do
|
72
|
+
let(:a) { nil }
|
73
|
+
let(:b) { nil }
|
74
|
+
let(:c) { 'value' }
|
75
|
+
|
76
|
+
context 'when model is a PORO' do
|
77
|
+
let(:model) { Model.new(a, b, c) }
|
78
|
+
|
79
|
+
before do
|
80
|
+
stub_const 'Model', Class.new
|
81
|
+
Model.class_eval do
|
82
|
+
attr_accessor :a, :b, :c
|
83
|
+
|
84
|
+
def initialize(a, b, c)
|
85
|
+
@a = a
|
86
|
+
@b = b
|
87
|
+
@c = c
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when expose_nil option is not provided' do
|
93
|
+
it 'exposes nil attributes' do
|
94
|
+
subject.expose(:a)
|
95
|
+
subject.expose(:b)
|
96
|
+
subject.expose(:c)
|
97
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when expose_nil option is true' do
|
102
|
+
it 'exposes nil attributes' do
|
103
|
+
subject.expose(:a, expose_nil: true)
|
104
|
+
subject.expose(:b, expose_nil: true)
|
105
|
+
subject.expose(:c)
|
106
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'when expose_nil option is false' do
|
111
|
+
it 'does not expose nil attributes' do
|
112
|
+
subject.expose(:a, expose_nil: false)
|
113
|
+
subject.expose(:b, expose_nil: false)
|
114
|
+
subject.expose(:c)
|
115
|
+
expect(subject.represent(model).serializable_hash).to eq(c: 'value')
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'is only applied per attribute' do
|
119
|
+
subject.expose(:a, expose_nil: false)
|
120
|
+
subject.expose(:b)
|
121
|
+
subject.expose(:c)
|
122
|
+
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'raises an error when applied to multiple attribute exposures' do
|
126
|
+
expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'when expose_nil option is false and block passed' do
|
131
|
+
it 'does not expose if block returns nil' do
|
132
|
+
subject.expose(:a, expose_nil: false) do |_obj, _options|
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
subject.expose(:b)
|
136
|
+
subject.expose(:c)
|
137
|
+
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'exposes is block returns a value' do
|
141
|
+
subject.expose(:a, expose_nil: false) do |_obj, _options|
|
142
|
+
100
|
143
|
+
end
|
144
|
+
subject.expose(:b)
|
145
|
+
subject.expose(:c)
|
146
|
+
expect(subject.represent(model).serializable_hash).to eq(a: 100, b: nil, c: 'value')
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'when model is a hash' do
|
152
|
+
let(:model) { { a: a, b: b, c: c } }
|
153
|
+
|
154
|
+
context 'when expose_nil option is not provided' do
|
155
|
+
it 'exposes nil attributes' do
|
156
|
+
subject.expose(:a)
|
157
|
+
subject.expose(:b)
|
158
|
+
subject.expose(:c)
|
159
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'when expose_nil option is true' do
|
164
|
+
it 'exposes nil attributes' do
|
165
|
+
subject.expose(:a, expose_nil: true)
|
166
|
+
subject.expose(:b, expose_nil: true)
|
167
|
+
subject.expose(:c)
|
168
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'when expose_nil option is false' do
|
173
|
+
it 'does not expose nil attributes' do
|
174
|
+
subject.expose(:a, expose_nil: false)
|
175
|
+
subject.expose(:b, expose_nil: false)
|
176
|
+
subject.expose(:c)
|
177
|
+
expect(subject.represent(model).serializable_hash).to eq(c: 'value')
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'is only applied per attribute' do
|
181
|
+
subject.expose(:a, expose_nil: false)
|
182
|
+
subject.expose(:b)
|
183
|
+
subject.expose(:c)
|
184
|
+
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'raises an error when applied to multiple attribute exposures' do
|
188
|
+
expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'with nested structures' do
|
194
|
+
let(:model) { { a: a, b: b, c: { d: nil, e: nil, f: { g: nil, h: nil } } } }
|
195
|
+
|
196
|
+
context 'when expose_nil option is false' do
|
197
|
+
it 'does not expose nil attributes' do
|
198
|
+
subject.expose(:a, expose_nil: false)
|
199
|
+
subject.expose(:b)
|
200
|
+
subject.expose(:c) do
|
201
|
+
subject.expose(:d, expose_nil: false)
|
202
|
+
subject.expose(:e)
|
203
|
+
subject.expose(:f) do
|
204
|
+
subject.expose(:g, expose_nil: false)
|
205
|
+
subject.expose(:h)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: { e: nil, f: { h: nil } })
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context 'with :default option' do
|
216
|
+
let(:a) { nil }
|
217
|
+
let(:b) { nil }
|
218
|
+
let(:c) { 'value' }
|
219
|
+
|
220
|
+
context 'when model is a PORO' do
|
221
|
+
let(:model) { Model.new(a, b, c) }
|
222
|
+
|
223
|
+
before do
|
224
|
+
stub_const 'Model', Class.new
|
225
|
+
Model.class_eval do
|
226
|
+
attr_accessor :a, :b, :c
|
227
|
+
|
228
|
+
def initialize(a, b, c)
|
229
|
+
@a = a
|
230
|
+
@b = b
|
231
|
+
@c = c
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
context 'when default option is not provided' do
|
237
|
+
it 'exposes attributes values' do
|
238
|
+
subject.expose(:a)
|
239
|
+
subject.expose(:b)
|
240
|
+
subject.expose(:c)
|
241
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
context 'when default option is set' do
|
246
|
+
it 'exposes default values for attributes' do
|
247
|
+
subject.expose(:a, default: 'a')
|
248
|
+
subject.expose(:b, default: 'b')
|
249
|
+
subject.expose(:c, default: 'c')
|
250
|
+
expect(subject.represent(model).serializable_hash).to eq(a: 'a', b: 'b', c: 'value')
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'when default option is set and block passed' do
|
255
|
+
it 'return default value if block returns nil' do
|
256
|
+
subject.expose(:a, default: 'a') do |_obj, _options|
|
257
|
+
nil
|
258
|
+
end
|
259
|
+
subject.expose(:b)
|
260
|
+
subject.expose(:c)
|
261
|
+
expect(subject.represent(model).serializable_hash).to eq(a: 'a', b: nil, c: 'value')
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'return value from block if block returns a value' do
|
265
|
+
subject.expose(:a, default: 'a') do |_obj, _options|
|
266
|
+
100
|
267
|
+
end
|
268
|
+
subject.expose(:b)
|
269
|
+
subject.expose(:c)
|
270
|
+
expect(subject.represent(model).serializable_hash).to eq(a: 100, b: nil, c: 'value')
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
context 'when model is a hash' do
|
276
|
+
let(:model) { { a: a, b: b, c: c } }
|
277
|
+
|
278
|
+
context 'when expose_nil option is not provided' do
|
279
|
+
it 'exposes nil attributes' do
|
280
|
+
subject.expose(:a)
|
281
|
+
subject.expose(:b)
|
282
|
+
subject.expose(:c)
|
283
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
context 'when expose_nil option is true' do
|
288
|
+
it 'exposes nil attributes' do
|
289
|
+
subject.expose(:a, expose_nil: true)
|
290
|
+
subject.expose(:b, expose_nil: true)
|
291
|
+
subject.expose(:c)
|
292
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
context 'when expose_nil option is false' do
|
297
|
+
it 'does not expose nil attributes' do
|
298
|
+
subject.expose(:a, expose_nil: false)
|
299
|
+
subject.expose(:b, expose_nil: false)
|
300
|
+
subject.expose(:c)
|
301
|
+
expect(subject.represent(model).serializable_hash).to eq(c: 'value')
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'is only applied per attribute' do
|
305
|
+
subject.expose(:a, expose_nil: false)
|
306
|
+
subject.expose(:b)
|
307
|
+
subject.expose(:c)
|
308
|
+
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
|
309
|
+
end
|
310
|
+
|
311
|
+
it 'raises an error when applied to multiple attribute exposures' do
|
312
|
+
expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
context 'with nested structures' do
|
318
|
+
let(:model) { { a: a, b: b, c: { d: nil, e: nil, f: { g: nil, h: nil } } } }
|
319
|
+
|
320
|
+
context 'when expose_nil option is false' do
|
321
|
+
it 'does not expose nil attributes' do
|
322
|
+
subject.expose(:a, expose_nil: false)
|
323
|
+
subject.expose(:b)
|
324
|
+
subject.expose(:c) do
|
325
|
+
subject.expose(:d, expose_nil: false)
|
326
|
+
subject.expose(:e)
|
327
|
+
subject.expose(:f) do
|
328
|
+
subject.expose(:g, expose_nil: false)
|
329
|
+
subject.expose(:h)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: { e: nil, f: { h: nil } })
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
38
339
|
context 'with a block' do
|
39
340
|
it 'errors out if called with multiple attributes' do
|
40
341
|
expect { subject.expose(:name, :email) { true } }.to raise_error ArgumentError
|
@@ -61,10 +362,10 @@ describe Grape::Entity do
|
|
61
362
|
end
|
62
363
|
|
63
364
|
object = EntitySpec::SomeObject1.new
|
64
|
-
value = subject.represent(object).
|
365
|
+
value = subject.represent(object).value_for(:bogus)
|
65
366
|
expect(value).to be_instance_of EntitySpec::BogusEntity
|
66
367
|
|
67
|
-
prop1 = value.
|
368
|
+
prop1 = value.value_for(:prop1)
|
68
369
|
expect(prop1).to eq 'MODIFIED 2'
|
69
370
|
end
|
70
371
|
|
@@ -72,12 +373,76 @@ describe Grape::Entity do
|
|
72
373
|
it 'sets the :proc option in the exposure options' do
|
73
374
|
block = ->(_) { true }
|
74
375
|
subject.expose :name, using: 'Awesome', &block
|
75
|
-
|
376
|
+
exposure = subject.find_exposure(:name)
|
377
|
+
expect(exposure.subexposure.block).to eq(block)
|
378
|
+
expect(exposure.using_class_name).to eq('Awesome')
|
76
379
|
end
|
77
380
|
|
78
381
|
it 'references an instance of the entity without any options' do
|
79
382
|
subject.expose(:size) { |_| self }
|
80
|
-
expect(subject.represent({}).
|
383
|
+
expect(subject.represent({}).value_for(:size)).to be_an_instance_of fresh_class
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
describe 'blocks' do
|
388
|
+
class SomeObject
|
389
|
+
def method_without_args
|
390
|
+
'result'
|
391
|
+
end
|
392
|
+
|
393
|
+
def raises_argument_error
|
394
|
+
raise ArgumentError, 'something different'
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
describe 'with block passed in' do
|
399
|
+
specify do
|
400
|
+
subject.expose :that_method_without_args do |object|
|
401
|
+
object.method_without_args
|
402
|
+
end
|
403
|
+
|
404
|
+
object = SomeObject.new
|
405
|
+
|
406
|
+
value = subject.represent(object).value_for(:that_method_without_args)
|
407
|
+
expect(value).to eq('result')
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'does not suppress ArgumentError' do
|
411
|
+
subject.expose :raises_argument_error do |object|
|
412
|
+
object.raises_argument_error
|
413
|
+
end
|
414
|
+
|
415
|
+
object = SomeObject.new
|
416
|
+
expect do
|
417
|
+
subject.represent(object).value_for(:raises_argument_error)
|
418
|
+
end.to raise_error(ArgumentError, 'something different')
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
context 'with block passed in via &' do
|
423
|
+
if RUBY_VERSION.start_with?('3')
|
424
|
+
specify do
|
425
|
+
subject.expose :that_method_without_args, &:method_without_args
|
426
|
+
subject.expose :method_without_args, as: :that_method_without_args_again
|
427
|
+
|
428
|
+
object = SomeObject.new
|
429
|
+
expect do
|
430
|
+
subject.represent(object).value_for(:that_method_without_args)
|
431
|
+
end.to raise_error Grape::Entity::Deprecated
|
432
|
+
|
433
|
+
value2 = subject.represent(object).value_for(:that_method_without_args_again)
|
434
|
+
expect(value2).to eq('result')
|
435
|
+
end
|
436
|
+
else
|
437
|
+
specify do
|
438
|
+
subject.expose :that_method_without_args_again, &:method_without_args
|
439
|
+
|
440
|
+
object = SomeObject.new
|
441
|
+
|
442
|
+
value2 = subject.represent(object).value_for(:that_method_without_args_again)
|
443
|
+
expect(value2).to eq('result')
|
444
|
+
end
|
445
|
+
end
|
81
446
|
end
|
82
447
|
end
|
83
448
|
|
@@ -90,33 +455,38 @@ describe Grape::Entity do
|
|
90
455
|
subject.expose :another_nested, using: 'Awesome'
|
91
456
|
end
|
92
457
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
)
|
458
|
+
awesome = subject.find_exposure(:awesome)
|
459
|
+
nested = awesome.find_nested_exposure(:nested)
|
460
|
+
another_nested = awesome.find_nested_exposure(:another_nested)
|
461
|
+
moar_nested = nested.find_nested_exposure(:moar_nested)
|
462
|
+
|
463
|
+
expect(awesome).to be_nesting
|
464
|
+
expect(nested).to_not be_nil
|
465
|
+
expect(another_nested).to_not be_nil
|
466
|
+
expect(another_nested.using_class_name).to eq('Awesome')
|
467
|
+
expect(moar_nested).to_not be_nil
|
468
|
+
expect(moar_nested.key(subject)).to eq(:weee)
|
99
469
|
end
|
100
470
|
|
101
|
-
it 'represents the exposure as a hash of its nested
|
471
|
+
it 'represents the exposure as a hash of its nested.root_exposures' do
|
102
472
|
subject.expose :awesome do
|
103
473
|
subject.expose(:nested) { |_| 'value' }
|
104
474
|
subject.expose(:another_nested) { |_| 'value' }
|
105
475
|
end
|
106
476
|
|
107
|
-
expect(subject.represent({}).
|
477
|
+
expect(subject.represent({}).value_for(:awesome)).to eq(
|
108
478
|
nested: 'value',
|
109
479
|
another_nested: 'value'
|
110
480
|
)
|
111
481
|
end
|
112
482
|
|
113
|
-
it 'does not represent nested
|
483
|
+
it 'does not represent nested.root_exposures whose conditions are not met' do
|
114
484
|
subject.expose :awesome do
|
115
485
|
subject.expose(:condition_met, if: ->(_, _) { true }) { |_| 'value' }
|
116
486
|
subject.expose(:condition_not_met, if: ->(_, _) { false }) { |_| 'value' }
|
117
487
|
end
|
118
488
|
|
119
|
-
expect(subject.represent({}).
|
489
|
+
expect(subject.represent({}).value_for(:awesome)).to eq(condition_met: 'value')
|
120
490
|
end
|
121
491
|
|
122
492
|
it 'does not represent attributes, declared inside nested exposure, outside of it' do
|
@@ -139,7 +509,7 @@ describe Grape::Entity do
|
|
139
509
|
)
|
140
510
|
end
|
141
511
|
|
142
|
-
it 'complex nested attributes' do
|
512
|
+
it 'merges complex nested attributes' do
|
143
513
|
class ClassRoom < Grape::Entity
|
144
514
|
expose(:parents, using: 'Parent') { |_| [{}, {}] }
|
145
515
|
end
|
@@ -181,7 +551,44 @@ describe Grape::Entity do
|
|
181
551
|
)
|
182
552
|
end
|
183
553
|
|
184
|
-
it '
|
554
|
+
it 'merges results of deeply nested double.root_exposures inside of nesting exposure' do
|
555
|
+
entity = Class.new(Grape::Entity) do
|
556
|
+
expose :data do
|
557
|
+
expose :something do
|
558
|
+
expose(:x) { |_| 'x' }
|
559
|
+
end
|
560
|
+
expose :something do
|
561
|
+
expose(:y) { |_| 'y' }
|
562
|
+
end
|
563
|
+
end
|
564
|
+
end
|
565
|
+
expect(entity.represent({}).serializable_hash).to eq(
|
566
|
+
data: {
|
567
|
+
something: {
|
568
|
+
x: 'x',
|
569
|
+
y: 'y'
|
570
|
+
}
|
571
|
+
}
|
572
|
+
)
|
573
|
+
end
|
574
|
+
|
575
|
+
it 'serializes deeply nested presenter exposures' do
|
576
|
+
e = Class.new(Grape::Entity) do
|
577
|
+
expose :f
|
578
|
+
end
|
579
|
+
subject.expose :a do
|
580
|
+
subject.expose :b do
|
581
|
+
subject.expose :c do
|
582
|
+
subject.expose :lol, using: e
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
expect(subject.represent(lol: { f: 123 }).serializable_hash).to eq(
|
587
|
+
a: { b: { c: { lol: { f: 123 } } } }
|
588
|
+
)
|
589
|
+
end
|
590
|
+
|
591
|
+
it 'is safe if its nested.root_exposures are safe' do
|
185
592
|
subject.with_options safe: true do
|
186
593
|
subject.expose :awesome do
|
187
594
|
subject.expose(:nested) { |_| 'value' }
|
@@ -199,34 +606,75 @@ describe Grape::Entity do
|
|
199
606
|
}
|
200
607
|
)
|
201
608
|
end
|
609
|
+
it 'merges attriutes if :merge option is passed' do
|
610
|
+
user_entity = Class.new(Grape::Entity)
|
611
|
+
admin_entity = Class.new(Grape::Entity)
|
612
|
+
user_entity.expose(:id, :name)
|
613
|
+
admin_entity.expose(:id, :name)
|
614
|
+
|
615
|
+
subject.expose(:profiles) do
|
616
|
+
subject.expose(:users, merge: true, using: user_entity)
|
617
|
+
subject.expose(:admins, merge: true, using: admin_entity)
|
618
|
+
end
|
619
|
+
|
620
|
+
subject.expose :awesome do
|
621
|
+
subject.expose(:nested, merge: true) { |_| { just_a_key: 'value' } }
|
622
|
+
subject.expose(:another_nested, merge: true) { |_| { just_another_key: 'value' } }
|
623
|
+
end
|
624
|
+
|
625
|
+
additional_hash = { users: [{ id: 1, name: 'John' }, { id: 2, name: 'Jay' }],
|
626
|
+
admins: [{ id: 3, name: 'Jack' }, { id: 4, name: 'James' }] }
|
627
|
+
expect(subject.represent(additional_hash).serializable_hash).to eq(
|
628
|
+
profiles: additional_hash[:users] + additional_hash[:admins],
|
629
|
+
awesome: { just_a_key: 'value', just_another_key: 'value' }
|
630
|
+
)
|
631
|
+
end
|
202
632
|
end
|
203
633
|
end
|
204
634
|
|
205
|
-
context 'inherited
|
206
|
-
it 'returns
|
635
|
+
context 'inherited.root_exposures' do
|
636
|
+
it 'returns.root_exposures from an ancestor' do
|
207
637
|
subject.expose :name, :email
|
208
638
|
child_class = Class.new(subject)
|
209
639
|
|
210
|
-
expect(child_class.
|
640
|
+
expect(child_class.root_exposures).to eq(subject.root_exposures)
|
211
641
|
end
|
212
642
|
|
213
|
-
it 'returns
|
643
|
+
it 'returns.root_exposures from multiple ancestor' do
|
214
644
|
subject.expose :name, :email
|
215
645
|
parent_class = Class.new(subject)
|
216
646
|
child_class = Class.new(parent_class)
|
217
647
|
|
218
|
-
expect(child_class.
|
648
|
+
expect(child_class.root_exposures).to eq(subject.root_exposures)
|
219
649
|
end
|
220
650
|
|
221
|
-
it 'returns descendant
|
651
|
+
it 'returns descendant.root_exposures as a priority' do
|
222
652
|
subject.expose :name, :email
|
223
653
|
child_class = Class.new(subject)
|
224
654
|
child_class.expose :name do |_|
|
225
655
|
'foo'
|
226
656
|
end
|
227
657
|
|
228
|
-
expect(subject.
|
229
|
-
expect(child_class.
|
658
|
+
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'bar')
|
659
|
+
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'foo')
|
660
|
+
end
|
661
|
+
|
662
|
+
it 'not overrides exposure by default' do
|
663
|
+
subject.expose :name
|
664
|
+
child_class = Class.new(subject)
|
665
|
+
child_class.expose :name, as: :child_name
|
666
|
+
|
667
|
+
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
|
668
|
+
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar', child_name: 'bar')
|
669
|
+
end
|
670
|
+
|
671
|
+
it 'overrides parent class exposure when option is specified' do
|
672
|
+
subject.expose :name
|
673
|
+
child_class = Class.new(subject)
|
674
|
+
child_class.expose :name, as: :child_name, override: true
|
675
|
+
|
676
|
+
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
|
677
|
+
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(child_name: 'bar')
|
230
678
|
end
|
231
679
|
end
|
232
680
|
|
@@ -257,46 +705,46 @@ describe Grape::Entity do
|
|
257
705
|
|
258
706
|
subject.expose :birthday, format_with: :timestamp
|
259
707
|
|
260
|
-
model
|
708
|
+
model = { birthday: Time.gm(2012, 2, 27) }
|
261
709
|
expect(subject.new(double(model)).as_json[:birthday]).to eq '02/27/2012'
|
262
710
|
end
|
263
711
|
|
264
712
|
it 'formats an exposure with a :format_with lambda that returns a value from the entity instance' do
|
265
713
|
object = {}
|
266
714
|
|
267
|
-
subject.expose(:size, format_with: ->(_value) {
|
268
|
-
expect(subject.represent(object).
|
715
|
+
subject.expose(:size, format_with: ->(_value) { object.class.to_s })
|
716
|
+
expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
|
269
717
|
end
|
270
718
|
|
271
719
|
it 'formats an exposure with a :format_with symbol that returns a value from the entity instance' do
|
272
720
|
subject.format_with :size_formatter do |_date|
|
273
|
-
|
721
|
+
object.class.to_s
|
274
722
|
end
|
275
723
|
|
276
724
|
object = {}
|
277
725
|
|
278
726
|
subject.expose(:size, format_with: :size_formatter)
|
279
|
-
expect(subject.represent(object).
|
727
|
+
expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
|
280
728
|
end
|
281
729
|
|
282
730
|
it 'works global on Grape::Entity' do
|
283
731
|
Grape::Entity.format_with :size_formatter do |_date|
|
284
|
-
|
732
|
+
object.class.to_s
|
285
733
|
end
|
286
734
|
object = {}
|
287
735
|
|
288
736
|
subject.expose(:size, format_with: :size_formatter)
|
289
|
-
expect(subject.represent(object).
|
737
|
+
expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
|
290
738
|
end
|
291
739
|
end
|
292
740
|
|
293
741
|
it 'works global on Grape::Entity' do
|
294
742
|
Grape::Entity.expose :a
|
295
743
|
object = { a: 11, b: 22 }
|
296
|
-
expect(Grape::Entity.represent(object).
|
744
|
+
expect(Grape::Entity.represent(object).value_for(:a)).to eq 11
|
297
745
|
subject.expose :b
|
298
|
-
expect(subject.represent(object).
|
299
|
-
expect(subject.represent(object).
|
746
|
+
expect(subject.represent(object).value_for(:a)).to eq 11
|
747
|
+
expect(subject.represent(object).value_for(:b)).to eq 22
|
300
748
|
Grape::Entity.unexpose :a
|
301
749
|
end
|
302
750
|
end
|
@@ -306,17 +754,20 @@ describe Grape::Entity do
|
|
306
754
|
subject.expose :name, :email
|
307
755
|
subject.unexpose :email
|
308
756
|
|
309
|
-
expect(subject.
|
757
|
+
expect(subject.root_exposures.length).to eq 1
|
758
|
+
expect(subject.root_exposures[0].attribute).to eq :name
|
310
759
|
end
|
311
760
|
|
312
|
-
context 'inherited
|
761
|
+
context 'inherited.root_exposures' do
|
313
762
|
it 'when called from child class, only removes from the attribute from child' do
|
314
763
|
subject.expose :name, :email
|
315
764
|
child_class = Class.new(subject)
|
316
765
|
child_class.unexpose :email
|
317
766
|
|
318
|
-
expect(child_class.
|
319
|
-
expect(
|
767
|
+
expect(child_class.root_exposures.length).to eq 1
|
768
|
+
expect(child_class.root_exposures[0].attribute).to eq :name
|
769
|
+
expect(subject.root_exposures[0].attribute).to eq :name
|
770
|
+
expect(subject.root_exposures[1].attribute).to eq :email
|
320
771
|
end
|
321
772
|
|
322
773
|
context 'when called from the parent class' do
|
@@ -325,17 +776,30 @@ describe Grape::Entity do
|
|
325
776
|
child_class = Class.new(subject)
|
326
777
|
subject.unexpose :email
|
327
778
|
|
328
|
-
expect(subject.
|
329
|
-
expect(
|
779
|
+
expect(subject.root_exposures.length).to eq 1
|
780
|
+
expect(subject.root_exposures[0].attribute).to eq :name
|
781
|
+
expect(child_class.root_exposures[0].attribute).to eq :name
|
782
|
+
expect(child_class.root_exposures[1].attribute).to eq :email
|
330
783
|
end
|
331
784
|
end
|
332
785
|
end
|
333
786
|
|
787
|
+
it 'does not allow unexposing inside of nesting exposures' do
|
788
|
+
expect do
|
789
|
+
Class.new(Grape::Entity) do
|
790
|
+
expose :something do
|
791
|
+
expose :x
|
792
|
+
unexpose :x
|
793
|
+
end
|
794
|
+
end
|
795
|
+
end.to raise_error(/You cannot call 'unexpose`/)
|
796
|
+
end
|
797
|
+
|
334
798
|
it 'works global on Grape::Entity' do
|
335
|
-
Grape::Entity.expose :
|
336
|
-
expect(Grape::Entity.
|
337
|
-
Grape::Entity.unexpose :
|
338
|
-
expect(Grape::Entity.
|
799
|
+
Grape::Entity.expose :x
|
800
|
+
expect(Grape::Entity.root_exposures[0].attribute).to eq(:x)
|
801
|
+
Grape::Entity.unexpose :x
|
802
|
+
expect(Grape::Entity.root_exposures).to eq([])
|
339
803
|
end
|
340
804
|
end
|
341
805
|
|
@@ -350,14 +814,16 @@ describe Grape::Entity do
|
|
350
814
|
expect { subject.class_eval(&block) }.to raise_error ArgumentError
|
351
815
|
end
|
352
816
|
|
353
|
-
it 'applies the options to all
|
817
|
+
it 'applies the options to all.root_exposures inside' do
|
354
818
|
subject.class_eval do
|
355
819
|
with_options(if: { awesome: true }) do
|
356
820
|
expose :awesome_thing, using: 'Awesome'
|
357
821
|
end
|
358
822
|
end
|
359
823
|
|
360
|
-
|
824
|
+
exposure = subject.find_exposure(:awesome_thing)
|
825
|
+
expect(exposure.using_class_name).to eq('Awesome')
|
826
|
+
expect(exposure.conditions[0].cond_hash).to eq(awesome: true)
|
361
827
|
end
|
362
828
|
|
363
829
|
it 'allows for nested .with_options' do
|
@@ -369,7 +835,9 @@ describe Grape::Entity do
|
|
369
835
|
end
|
370
836
|
end
|
371
837
|
|
372
|
-
|
838
|
+
exposure = subject.find_exposure(:awesome_thing)
|
839
|
+
expect(exposure.using_class_name).to eq('Something')
|
840
|
+
expect(exposure.conditions[0].cond_hash).to eq(awesome: true)
|
373
841
|
end
|
374
842
|
|
375
843
|
it 'overrides nested :as option' do
|
@@ -379,7 +847,8 @@ describe Grape::Entity do
|
|
379
847
|
end
|
380
848
|
end
|
381
849
|
|
382
|
-
|
850
|
+
exposure = subject.find_exposure(:awesome_thing)
|
851
|
+
expect(exposure.key(subject)).to eq :extra_smooth
|
383
852
|
end
|
384
853
|
|
385
854
|
it 'merges nested :if option' do
|
@@ -401,10 +870,11 @@ describe Grape::Entity do
|
|
401
870
|
end
|
402
871
|
end
|
403
872
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
)
|
873
|
+
exposure = subject.find_exposure(:awesome_thing)
|
874
|
+
expect(exposure.conditions.any?(&:inversed?)).to be_falsey
|
875
|
+
expect(exposure.conditions[0].symbol).to eq(:awesome)
|
876
|
+
expect(exposure.conditions[1].block).to eq(match_proc)
|
877
|
+
expect(exposure.conditions[2].cond_hash).to eq(awesome: false, less_awesome: true)
|
408
878
|
end
|
409
879
|
|
410
880
|
it 'merges nested :unless option' do
|
@@ -426,10 +896,11 @@ describe Grape::Entity do
|
|
426
896
|
end
|
427
897
|
end
|
428
898
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
)
|
899
|
+
exposure = subject.find_exposure(:awesome_thing)
|
900
|
+
expect(exposure.conditions.all?(&:inversed?)).to be_truthy
|
901
|
+
expect(exposure.conditions[0].symbol).to eq(:awesome)
|
902
|
+
expect(exposure.conditions[1].block).to eq(match_proc)
|
903
|
+
expect(exposure.conditions[2].cond_hash).to eq(awesome: false, less_awesome: true)
|
433
904
|
end
|
434
905
|
|
435
906
|
it 'overrides nested :using option' do
|
@@ -439,7 +910,8 @@ describe Grape::Entity do
|
|
439
910
|
end
|
440
911
|
end
|
441
912
|
|
442
|
-
|
913
|
+
exposure = subject.find_exposure(:awesome_thing)
|
914
|
+
expect(exposure.using_class_name).to eq('SomethingElse')
|
443
915
|
end
|
444
916
|
|
445
917
|
it 'aliases :with option to :using option' do
|
@@ -448,7 +920,9 @@ describe Grape::Entity do
|
|
448
920
|
expose :awesome_thing, with: 'SomethingElse'
|
449
921
|
end
|
450
922
|
end
|
451
|
-
|
923
|
+
|
924
|
+
exposure = subject.find_exposure(:awesome_thing)
|
925
|
+
expect(exposure.using_class_name).to eq('SomethingElse')
|
452
926
|
end
|
453
927
|
|
454
928
|
it 'overrides nested :proc option' do
|
@@ -460,7 +934,8 @@ describe Grape::Entity do
|
|
460
934
|
end
|
461
935
|
end
|
462
936
|
|
463
|
-
|
937
|
+
exposure = subject.find_exposure(:awesome_thing)
|
938
|
+
expect(exposure.block).to eq(match_proc)
|
464
939
|
end
|
465
940
|
|
466
941
|
it 'overrides nested :documentation option' do
|
@@ -470,7 +945,36 @@ describe Grape::Entity do
|
|
470
945
|
end
|
471
946
|
end
|
472
947
|
|
473
|
-
|
948
|
+
exposure = subject.find_exposure(:awesome_thing)
|
949
|
+
expect(exposure.documentation).to eq(desc: 'Other description.')
|
950
|
+
end
|
951
|
+
|
952
|
+
it 'propagates expose_nil option' do
|
953
|
+
subject.class_eval do
|
954
|
+
with_options(expose_nil: false) do
|
955
|
+
expose :awesome_thing
|
956
|
+
end
|
957
|
+
end
|
958
|
+
|
959
|
+
exposure = subject.find_exposure(:awesome_thing)
|
960
|
+
expect(exposure.conditions[0].inversed?).to be true
|
961
|
+
expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
|
962
|
+
end
|
963
|
+
|
964
|
+
it 'overrides nested :expose_nil option' do
|
965
|
+
subject.class_eval do
|
966
|
+
with_options(expose_nil: true) do
|
967
|
+
expose :awesome_thing, expose_nil: false
|
968
|
+
expose :other_awesome_thing
|
969
|
+
end
|
970
|
+
end
|
971
|
+
|
972
|
+
exposure = subject.find_exposure(:awesome_thing)
|
973
|
+
expect(exposure.conditions[0].inversed?).to be true
|
974
|
+
expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
|
975
|
+
# Conditions are only added for exposures that do not expose nil
|
976
|
+
exposure = subject.find_exposure(:other_awesome_thing)
|
977
|
+
expect(exposure.conditions[0]).to be_nil
|
474
978
|
end
|
475
979
|
end
|
476
980
|
|
@@ -484,14 +988,14 @@ describe Grape::Entity do
|
|
484
988
|
end
|
485
989
|
|
486
990
|
it 'returns multiple entities if called with a collection' do
|
487
|
-
representation = subject.represent(4
|
991
|
+
representation = subject.represent(Array.new(4) { Object.new })
|
488
992
|
expect(representation).to be_kind_of Array
|
489
993
|
expect(representation.size).to eq(4)
|
490
994
|
expect(representation.reject { |r| r.is_a?(subject) }).to be_empty
|
491
995
|
end
|
492
996
|
|
493
997
|
it 'adds the collection: true option if called with a collection' do
|
494
|
-
representation = subject.represent(4
|
998
|
+
representation = subject.represent(Array.new(4) { Object.new })
|
495
999
|
representation.each { |r| expect(r.options[:collection]).to be true }
|
496
1000
|
end
|
497
1001
|
|
@@ -503,7 +1007,7 @@ describe Grape::Entity do
|
|
503
1007
|
|
504
1008
|
it 'returns a serialized array of hashes of multiple objects if serializable: true' do
|
505
1009
|
subject.expose(:awesome) { |_| true }
|
506
|
-
representation = subject.represent(2
|
1010
|
+
representation = subject.represent(Array.new(2) { Object.new }, serializable: true)
|
507
1011
|
expect(representation).to eq([{ awesome: true }, { awesome: true }])
|
508
1012
|
end
|
509
1013
|
|
@@ -529,7 +1033,7 @@ describe Grape::Entity do
|
|
529
1033
|
context 'with specified fields' do
|
530
1034
|
it 'returns only specified fields with only option' do
|
531
1035
|
subject.expose(:id, :name, :phone)
|
532
|
-
representation = subject.represent(OpenStruct.new, only: [
|
1036
|
+
representation = subject.represent(OpenStruct.new, only: %i[id name], serializable: true)
|
533
1037
|
expect(representation).to eq(id: nil, name: nil)
|
534
1038
|
end
|
535
1039
|
|
@@ -542,7 +1046,7 @@ describe Grape::Entity do
|
|
542
1046
|
it 'returns only fields specified in the only option and not specified in the except option' do
|
543
1047
|
subject.expose(:id, :name, :phone)
|
544
1048
|
representation = subject.represent(OpenStruct.new,
|
545
|
-
only: [
|
1049
|
+
only: %i[name phone],
|
546
1050
|
except: [:phone], serializable: true)
|
547
1051
|
expect(representation).to eq(name: nil)
|
548
1052
|
end
|
@@ -587,6 +1091,22 @@ describe Grape::Entity do
|
|
587
1091
|
representation = subject.represent(object, except: [:id, 'address', { user: [:id, 'name'] }], serializable: true)
|
588
1092
|
expect(representation).to eq(name: nil, phone: nil, user: { email: nil })
|
589
1093
|
end
|
1094
|
+
|
1095
|
+
context 'with nested attributes' do
|
1096
|
+
before do
|
1097
|
+
subject.expose :additional do
|
1098
|
+
subject.expose :something
|
1099
|
+
end
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
it 'preserves nesting' do
|
1103
|
+
expect(subject.represent({ something: 123 }, only: [{ additional: [:something] }], serializable: true)).to eq(
|
1104
|
+
additional: {
|
1105
|
+
something: 123
|
1106
|
+
}
|
1107
|
+
)
|
1108
|
+
end
|
1109
|
+
end
|
590
1110
|
end
|
591
1111
|
|
592
1112
|
it 'can specify children attributes with only' do
|
@@ -596,7 +1116,7 @@ describe Grape::Entity do
|
|
596
1116
|
subject.expose(:id, :name, :phone)
|
597
1117
|
subject.expose(:user, using: user_entity)
|
598
1118
|
|
599
|
-
representation = subject.represent(OpenStruct.new(user: {}), only: [:id, :name, { user: [
|
1119
|
+
representation = subject.represent(OpenStruct.new(user: {}), only: [:id, :name, { user: %i[name email] }], serializable: true)
|
600
1120
|
expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
|
601
1121
|
end
|
602
1122
|
|
@@ -619,7 +1139,7 @@ describe Grape::Entity do
|
|
619
1139
|
subject.expose(:user, using: user_entity)
|
620
1140
|
|
621
1141
|
representation = subject.represent(OpenStruct.new(user: {}),
|
622
|
-
only: [:id, :name, :phone, user: [
|
1142
|
+
only: [:id, :name, :phone, { user: %i[id name email] }],
|
623
1143
|
except: [:phone, { user: [:id] }], serializable: true)
|
624
1144
|
expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
|
625
1145
|
end
|
@@ -631,7 +1151,7 @@ describe Grape::Entity do
|
|
631
1151
|
subject.expose(:name)
|
632
1152
|
end
|
633
1153
|
|
634
|
-
representation = subject.represent(OpenStruct.new, condition: true, only: [
|
1154
|
+
representation = subject.represent(OpenStruct.new, condition: true, only: %i[id name], serializable: true)
|
635
1155
|
expect(representation).to eq(id: nil, name: nil)
|
636
1156
|
end
|
637
1157
|
|
@@ -641,9 +1161,81 @@ describe Grape::Entity do
|
|
641
1161
|
subject.expose(:name, :mobile_phone)
|
642
1162
|
end
|
643
1163
|
|
644
|
-
representation = subject.represent(OpenStruct.new, condition: true, except: [
|
1164
|
+
representation = subject.represent(OpenStruct.new, condition: true, except: %i[phone mobile_phone], serializable: true)
|
645
1165
|
expect(representation).to eq(id: nil, name: nil)
|
646
1166
|
end
|
1167
|
+
|
1168
|
+
it 'choses proper exposure according to condition' do
|
1169
|
+
strategy1 = ->(_obj, _opts) { 'foo' }
|
1170
|
+
strategy2 = ->(_obj, _opts) { 'bar' }
|
1171
|
+
|
1172
|
+
subject.expose :id, proc: strategy1
|
1173
|
+
subject.expose :id, proc: strategy2
|
1174
|
+
expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'bar')
|
1175
|
+
expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
|
1176
|
+
|
1177
|
+
subject.unexpose_all
|
1178
|
+
|
1179
|
+
subject.expose :id, proc: strategy1, if: :condition
|
1180
|
+
subject.expose :id, proc: strategy2
|
1181
|
+
expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'bar')
|
1182
|
+
expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
|
1183
|
+
|
1184
|
+
subject.unexpose_all
|
1185
|
+
|
1186
|
+
subject.expose :id, proc: strategy1
|
1187
|
+
subject.expose :id, proc: strategy2, if: :condition
|
1188
|
+
expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'foo')
|
1189
|
+
expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
|
1190
|
+
|
1191
|
+
subject.unexpose_all
|
1192
|
+
|
1193
|
+
subject.expose :id, proc: strategy1, if: :condition1
|
1194
|
+
subject.expose :id, proc: strategy2, if: :condition2
|
1195
|
+
expect(subject.represent({}, condition1: false, condition2: false, serializable: true)).to eq({})
|
1196
|
+
expect(subject.represent({}, condition1: false, condition2: true, serializable: true)).to eq(id: 'bar')
|
1197
|
+
expect(subject.represent({}, condition1: true, condition2: false, serializable: true)).to eq(id: 'foo')
|
1198
|
+
expect(subject.represent({}, condition1: true, condition2: true, serializable: true)).to eq(id: 'bar')
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
it 'does not merge nested exposures with plain hashes' do
|
1202
|
+
subject.expose(:id)
|
1203
|
+
subject.expose(:info, if: :condition1) do
|
1204
|
+
subject.expose :a, :b
|
1205
|
+
subject.expose(:additional, if: :condition2) do |_obj, _opts|
|
1206
|
+
{
|
1207
|
+
x: 11, y: 22, c: 123
|
1208
|
+
}
|
1209
|
+
end
|
1210
|
+
end
|
1211
|
+
subject.expose(:info, if: :condition2) do
|
1212
|
+
subject.expose(:additional) do
|
1213
|
+
subject.expose :c
|
1214
|
+
end
|
1215
|
+
end
|
1216
|
+
subject.expose(:d, as: :info, if: :condition3)
|
1217
|
+
|
1218
|
+
obj = { id: 123, a: 1, b: 2, c: 3, d: 4 }
|
1219
|
+
|
1220
|
+
expect(subject.represent(obj, serializable: true)).to eq(id: 123)
|
1221
|
+
expect(subject.represent(obj, condition1: true, serializable: true)).to eq(id: 123, info: { a: 1, b: 2 })
|
1222
|
+
expect(subject.represent(obj, condition2: true, serializable: true)).to eq(
|
1223
|
+
id: 123,
|
1224
|
+
info: {
|
1225
|
+
additional: {
|
1226
|
+
c: 3
|
1227
|
+
}
|
1228
|
+
}
|
1229
|
+
)
|
1230
|
+
expect(subject.represent(obj, condition1: true, condition2: true, serializable: true)).to eq(
|
1231
|
+
id: 123,
|
1232
|
+
info: {
|
1233
|
+
a: 1, b: 2, additional: { c: 3 }
|
1234
|
+
}
|
1235
|
+
)
|
1236
|
+
expect(subject.represent(obj, condition3: true, serializable: true)).to eq(id: 123, info: 4)
|
1237
|
+
expect(subject.represent(obj, condition1: true, condition2: true, condition3: true, serializable: true)).to eq(id: 123, info: 4)
|
1238
|
+
end
|
647
1239
|
end
|
648
1240
|
|
649
1241
|
context 'attribute with alias' do
|
@@ -651,7 +1243,7 @@ describe Grape::Entity do
|
|
651
1243
|
subject.expose(:id)
|
652
1244
|
subject.expose(:name, as: :title)
|
653
1245
|
|
654
|
-
representation = subject.represent(OpenStruct.new, condition: true, only: [
|
1246
|
+
representation = subject.represent(OpenStruct.new, condition: true, only: %i[id title], serializable: true)
|
655
1247
|
expect(representation).to eq(id: nil, title: nil)
|
656
1248
|
end
|
657
1249
|
|
@@ -678,10 +1270,22 @@ describe Grape::Entity do
|
|
678
1270
|
subject.expose(:nephew, using: nephew_entity)
|
679
1271
|
|
680
1272
|
representation = subject.represent(OpenStruct.new(user: {}),
|
681
|
-
only: [
|
1273
|
+
only: %i[id name user], except: [:nephew], serializable: true)
|
682
1274
|
expect(representation).to eq(id: nil, name: nil, user: { id: nil, name: nil, email: nil })
|
683
1275
|
end
|
684
1276
|
end
|
1277
|
+
|
1278
|
+
context 'when NameError happens in a parameterized block_exposure' do
|
1279
|
+
before do
|
1280
|
+
subject.expose :raise_no_method_error do |_|
|
1281
|
+
foo
|
1282
|
+
end
|
1283
|
+
end
|
1284
|
+
|
1285
|
+
it 'does not cause infinite loop' do
|
1286
|
+
expect { subject.represent({}, serializable: true) }.to raise_error(NameError)
|
1287
|
+
end
|
1288
|
+
end
|
685
1289
|
end
|
686
1290
|
end
|
687
1291
|
|
@@ -690,7 +1294,7 @@ describe Grape::Entity do
|
|
690
1294
|
subject.present_collection true
|
691
1295
|
subject.expose :items
|
692
1296
|
|
693
|
-
representation = subject.represent(4
|
1297
|
+
representation = subject.represent(Array.new(4) { Object.new })
|
694
1298
|
expect(representation).to be_kind_of(subject)
|
695
1299
|
expect(representation.object).to be_kind_of(Hash)
|
696
1300
|
expect(representation.object).to have_key :items
|
@@ -702,7 +1306,8 @@ describe Grape::Entity do
|
|
702
1306
|
subject.present_collection true, :my_items
|
703
1307
|
subject.expose :my_items
|
704
1308
|
|
705
|
-
representation = subject.represent(4
|
1309
|
+
representation = subject.represent(Array.new(4) { Object.new }, serializable: true)
|
1310
|
+
expect(representation).to be_kind_of(Grape::Entity::Exposure::NestingExposure::OutputBuilder)
|
706
1311
|
expect(representation).to be_kind_of(Hash)
|
707
1312
|
expect(representation).to have_key :my_items
|
708
1313
|
expect(representation[:my_items]).to be_kind_of Array
|
@@ -727,7 +1332,7 @@ describe Grape::Entity do
|
|
727
1332
|
|
728
1333
|
context 'with an array of objects' do
|
729
1334
|
it 'allows a root element name to be specified' do
|
730
|
-
representation = subject.represent(4
|
1335
|
+
representation = subject.represent(Array.new(4) { Object.new })
|
731
1336
|
expect(representation).to be_kind_of Hash
|
732
1337
|
expect(representation).to have_key 'things'
|
733
1338
|
expect(representation['things']).to be_kind_of Array
|
@@ -738,13 +1343,13 @@ describe Grape::Entity do
|
|
738
1343
|
|
739
1344
|
context 'it can be overridden' do
|
740
1345
|
it 'can be disabled' do
|
741
|
-
representation = subject.represent(4
|
1346
|
+
representation = subject.represent(Array.new(4) { Object.new }, root: false)
|
742
1347
|
expect(representation).to be_kind_of Array
|
743
1348
|
expect(representation.size).to eq 4
|
744
1349
|
expect(representation.reject { |r| r.is_a?(subject) }).to be_empty
|
745
1350
|
end
|
746
1351
|
it 'can use a different name' do
|
747
|
-
representation = subject.represent(4
|
1352
|
+
representation = subject.represent(Array.new(4) { Object.new }, root: 'others')
|
748
1353
|
expect(representation).to be_kind_of Hash
|
749
1354
|
expect(representation).to have_key 'others'
|
750
1355
|
expect(representation['others']).to be_kind_of Array
|
@@ -770,7 +1375,7 @@ describe Grape::Entity do
|
|
770
1375
|
|
771
1376
|
context 'with an array of objects' do
|
772
1377
|
it 'allows a root element name to be specified' do
|
773
|
-
representation = subject.represent(4
|
1378
|
+
representation = subject.represent(Array.new(4) { Object.new })
|
774
1379
|
expect(representation).to be_kind_of Array
|
775
1380
|
expect(representation.size).to eq 4
|
776
1381
|
expect(representation.reject { |r| r.is_a?(subject) }).to be_empty
|
@@ -791,7 +1396,7 @@ describe Grape::Entity do
|
|
791
1396
|
|
792
1397
|
context 'with an array of objects' do
|
793
1398
|
it 'allows a root element name to be specified' do
|
794
|
-
representation = subject.represent(4
|
1399
|
+
representation = subject.represent(Array.new(4) { Object.new })
|
795
1400
|
expect(representation).to be_kind_of Hash
|
796
1401
|
expect(representation).to have_key('things')
|
797
1402
|
expect(representation['things']).to be_kind_of Array
|
@@ -816,7 +1421,7 @@ describe Grape::Entity do
|
|
816
1421
|
|
817
1422
|
it 'inherits array root root' do
|
818
1423
|
child_class = Class.new(subject)
|
819
|
-
representation = child_class.represent(4
|
1424
|
+
representation = child_class.represent(Array.new(4) { Object.new })
|
820
1425
|
expect(representation).to be_kind_of Hash
|
821
1426
|
expect(representation).to have_key('things')
|
822
1427
|
expect(representation['things']).to be_kind_of Array
|
@@ -856,6 +1461,11 @@ describe Grape::Entity do
|
|
856
1461
|
friends: [
|
857
1462
|
double(name: 'Friend 1', email: 'friend1@example.com', characteristics: [], fantasies: [], birthday: Time.gm(2012, 2, 27), friends: []),
|
858
1463
|
double(name: 'Friend 2', email: 'friend2@example.com', characteristics: [], fantasies: [], birthday: Time.gm(2012, 2, 27), friends: [])
|
1464
|
+
],
|
1465
|
+
extra: { key: 'foo', value: 'bar' },
|
1466
|
+
nested: [
|
1467
|
+
{ name: 'n1', data: { key: 'ex1', value: 'v1' } },
|
1468
|
+
{ name: 'n2', data: { key: 'ex2', value: 'v2' } }
|
859
1469
|
]
|
860
1470
|
}
|
861
1471
|
end
|
@@ -889,7 +1499,7 @@ describe Grape::Entity do
|
|
889
1499
|
expect(fresh_class.new(some_class.new).serializable_hash).to eq(name: true)
|
890
1500
|
end
|
891
1501
|
|
892
|
-
it "does expose attributes that don't exist on the object
|
1502
|
+
it "does expose attributes that don't exist on the object" do
|
893
1503
|
fresh_class.expose :email, :nonexistent_attribute, :name, safe: true
|
894
1504
|
|
895
1505
|
res = fresh_class.new(model).serializable_hash
|
@@ -898,6 +1508,13 @@ describe Grape::Entity do
|
|
898
1508
|
expect(res).to have_key :name
|
899
1509
|
end
|
900
1510
|
|
1511
|
+
it "does expose attributes that don't exist on the object as nil" do
|
1512
|
+
fresh_class.expose :email, :nonexistent_attribute, :name, safe: true
|
1513
|
+
|
1514
|
+
res = fresh_class.new(model).serializable_hash
|
1515
|
+
expect(res[:nonexistent_attribute]).to eq(nil)
|
1516
|
+
end
|
1517
|
+
|
901
1518
|
it 'does expose attributes marked as safe if model is a hash object' do
|
902
1519
|
fresh_class.expose :name, safe: true
|
903
1520
|
|
@@ -920,7 +1537,7 @@ describe Grape::Entity do
|
|
920
1537
|
context 'without safe option' do
|
921
1538
|
it 'throws an exception when an attribute is not found on the object' do
|
922
1539
|
fresh_class.expose :name, :nonexistent_attribute
|
923
|
-
expect { fresh_class.new(model).serializable_hash }.to raise_error
|
1540
|
+
expect { fresh_class.new(model).serializable_hash }.to raise_error NoMethodError
|
924
1541
|
end
|
925
1542
|
|
926
1543
|
it "exposes attributes that don't exist on the object only when they are generated by a block" do
|
@@ -953,6 +1570,18 @@ describe Grape::Entity do
|
|
953
1570
|
expect(res).to have_key :nonexistent_attribute
|
954
1571
|
end
|
955
1572
|
|
1573
|
+
it 'exposes attributes defined through module inclusion' do
|
1574
|
+
module SharedAttributes
|
1575
|
+
def a_value
|
1576
|
+
3.14
|
1577
|
+
end
|
1578
|
+
end
|
1579
|
+
fresh_class.include(SharedAttributes)
|
1580
|
+
fresh_class.expose :a_value
|
1581
|
+
res = fresh_class.new(model).serializable_hash
|
1582
|
+
expect(res[:a_value]).to eq(3.14)
|
1583
|
+
end
|
1584
|
+
|
956
1585
|
it 'does not expose attributes that are generated by a block but have not passed criteria' do
|
957
1586
|
fresh_class.expose :nonexistent_attribute,
|
958
1587
|
proc: ->(_, _) { 'I exist, but it is not yet my time to shine' },
|
@@ -1018,6 +1647,124 @@ describe Grape::Entity do
|
|
1018
1647
|
expect(presenter.serializable_hash).to eq(name: 'abc', embedded: { a: nil, b: { abc: 'def' } })
|
1019
1648
|
end
|
1020
1649
|
end
|
1650
|
+
|
1651
|
+
context '#attr_path' do
|
1652
|
+
it 'for all kinds of attributes' do
|
1653
|
+
module EntitySpec
|
1654
|
+
class EmailEntity < Grape::Entity
|
1655
|
+
expose(:email, as: :addr) { |_, o| o[:attr_path].join('/') }
|
1656
|
+
end
|
1657
|
+
|
1658
|
+
class UserEntity < Grape::Entity
|
1659
|
+
expose(:name, as: :full_name) { |_, o| o[:attr_path].join('/') }
|
1660
|
+
expose :email, using: 'EntitySpec::EmailEntity'
|
1661
|
+
end
|
1662
|
+
|
1663
|
+
class ExtraEntity < Grape::Entity
|
1664
|
+
expose(:key) { |_, o| o[:attr_path].join('/') }
|
1665
|
+
expose(:value) { |_, o| o[:attr_path].join('/') }
|
1666
|
+
end
|
1667
|
+
|
1668
|
+
class NestedEntity < Grape::Entity
|
1669
|
+
expose(:name) { |_, o| o[:attr_path].join('/') }
|
1670
|
+
expose :data, using: 'EntitySpec::ExtraEntity'
|
1671
|
+
end
|
1672
|
+
end
|
1673
|
+
|
1674
|
+
fresh_class.class_eval do
|
1675
|
+
expose(:id) { |_, o| o[:attr_path].join('/') }
|
1676
|
+
expose(:foo, as: :bar) { |_, o| o[:attr_path].join('/') }
|
1677
|
+
expose :title do
|
1678
|
+
expose :full do
|
1679
|
+
expose(:prefix, as: :pref) { |_, o| o[:attr_path].join('/') }
|
1680
|
+
expose(:main) { |_, o| o[:attr_path].join('/') }
|
1681
|
+
end
|
1682
|
+
end
|
1683
|
+
expose :friends, as: :social, using: 'EntitySpec::UserEntity'
|
1684
|
+
expose :extra, using: 'EntitySpec::ExtraEntity'
|
1685
|
+
expose :nested, using: 'EntitySpec::NestedEntity'
|
1686
|
+
end
|
1687
|
+
|
1688
|
+
expect(subject.serializable_hash).to eq(
|
1689
|
+
id: 'id',
|
1690
|
+
bar: 'bar',
|
1691
|
+
title: { full: { pref: 'title/full/pref', main: 'title/full/main' } },
|
1692
|
+
social: [
|
1693
|
+
{ full_name: 'social/full_name', email: { addr: 'social/email/addr' } },
|
1694
|
+
{ full_name: 'social/full_name', email: { addr: 'social/email/addr' } }
|
1695
|
+
],
|
1696
|
+
extra: { key: 'extra/key', value: 'extra/value' },
|
1697
|
+
nested: [
|
1698
|
+
{ name: 'nested/name', data: { key: 'nested/data/key', value: 'nested/data/value' } },
|
1699
|
+
{ name: 'nested/name', data: { key: 'nested/data/key', value: 'nested/data/value' } }
|
1700
|
+
]
|
1701
|
+
)
|
1702
|
+
end
|
1703
|
+
|
1704
|
+
it 'allows customize path of an attribute' do
|
1705
|
+
module EntitySpec
|
1706
|
+
class CharacterEntity < Grape::Entity
|
1707
|
+
expose(:key) { |_, o| o[:attr_path].join('/') }
|
1708
|
+
expose(:value) { |_, o| o[:attr_path].join('/') }
|
1709
|
+
end
|
1710
|
+
end
|
1711
|
+
|
1712
|
+
fresh_class.class_eval do
|
1713
|
+
expose :characteristics, using: EntitySpec::CharacterEntity,
|
1714
|
+
attr_path: ->(_obj, _opts) { :character }
|
1715
|
+
end
|
1716
|
+
|
1717
|
+
expect(subject.serializable_hash).to eq(
|
1718
|
+
characteristics: [
|
1719
|
+
{ key: 'character/key', value: 'character/value' }
|
1720
|
+
]
|
1721
|
+
)
|
1722
|
+
end
|
1723
|
+
|
1724
|
+
it 'can drop one nest level by set path_for to nil' do
|
1725
|
+
module EntitySpec
|
1726
|
+
class NoPathCharacterEntity < Grape::Entity
|
1727
|
+
expose(:key) { |_, o| o[:attr_path].join('/') }
|
1728
|
+
expose(:value) { |_, o| o[:attr_path].join('/') }
|
1729
|
+
end
|
1730
|
+
end
|
1731
|
+
|
1732
|
+
fresh_class.class_eval do
|
1733
|
+
expose :characteristics, using: EntitySpec::NoPathCharacterEntity, attr_path: proc {}
|
1734
|
+
end
|
1735
|
+
|
1736
|
+
expect(subject.serializable_hash).to eq(
|
1737
|
+
characteristics: [
|
1738
|
+
{ key: 'key', value: 'value' }
|
1739
|
+
]
|
1740
|
+
)
|
1741
|
+
end
|
1742
|
+
end
|
1743
|
+
|
1744
|
+
context 'with projections passed in options' do
|
1745
|
+
it 'allows to pass different :only and :except params using the same instance' do
|
1746
|
+
fresh_class.expose :a, :b, :c
|
1747
|
+
presenter = fresh_class.new(a: 1, b: 2, c: 3)
|
1748
|
+
expect(presenter.serializable_hash(only: %i[a b])).to eq(a: 1, b: 2)
|
1749
|
+
expect(presenter.serializable_hash(only: %i[b c])).to eq(b: 2, c: 3)
|
1750
|
+
end
|
1751
|
+
end
|
1752
|
+
end
|
1753
|
+
|
1754
|
+
describe '#inspect' do
|
1755
|
+
before do
|
1756
|
+
fresh_class.class_eval do
|
1757
|
+
expose :name, :email
|
1758
|
+
end
|
1759
|
+
end
|
1760
|
+
|
1761
|
+
it 'does not serialize delegator or options' do
|
1762
|
+
data = subject.inspect
|
1763
|
+
expect(data).to include 'name='
|
1764
|
+
expect(data).to include 'email='
|
1765
|
+
expect(data).to_not include '@options'
|
1766
|
+
expect(data).to_not include '@delegator'
|
1767
|
+
end
|
1021
1768
|
end
|
1022
1769
|
|
1023
1770
|
describe '#value_for' do
|
@@ -1040,17 +1787,19 @@ describe Grape::Entity do
|
|
1040
1787
|
end
|
1041
1788
|
|
1042
1789
|
it 'passes through bare expose attributes' do
|
1043
|
-
expect(subject.
|
1790
|
+
expect(subject.value_for(:name)).to eq attributes[:name]
|
1044
1791
|
end
|
1045
1792
|
|
1046
1793
|
it 'instantiates a representation if that is called for' do
|
1047
|
-
rep = subject.
|
1794
|
+
rep = subject.value_for(:friends)
|
1048
1795
|
expect(rep.reject { |r| r.is_a?(fresh_class) }).to be_empty
|
1049
1796
|
expect(rep.first.serializable_hash[:name]).to eq 'Friend 1'
|
1050
1797
|
expect(rep.last.serializable_hash[:name]).to eq 'Friend 2'
|
1051
1798
|
end
|
1052
1799
|
|
1053
1800
|
context 'child representations' do
|
1801
|
+
after { EntitySpec::FriendEntity.unexpose_all }
|
1802
|
+
|
1054
1803
|
it 'disables root key name for child representations' do
|
1055
1804
|
module EntitySpec
|
1056
1805
|
class FriendEntity < Grape::Entity
|
@@ -1063,7 +1812,7 @@ describe Grape::Entity do
|
|
1063
1812
|
expose :friends, using: EntitySpec::FriendEntity
|
1064
1813
|
end
|
1065
1814
|
|
1066
|
-
rep = subject.
|
1815
|
+
rep = subject.value_for(:friends)
|
1067
1816
|
expect(rep).to be_kind_of Array
|
1068
1817
|
expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
|
1069
1818
|
expect(rep.first.serializable_hash[:name]).to eq 'Friend 1'
|
@@ -1084,7 +1833,7 @@ describe Grape::Entity do
|
|
1084
1833
|
end
|
1085
1834
|
end
|
1086
1835
|
|
1087
|
-
rep = subject.
|
1836
|
+
rep = subject.value_for(:custom_friends)
|
1088
1837
|
expect(rep).to be_kind_of Array
|
1089
1838
|
expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
|
1090
1839
|
expect(rep.first.serializable_hash).to eq(name: 'Friend 1', email: 'friend1@example.com')
|
@@ -1105,7 +1854,7 @@ describe Grape::Entity do
|
|
1105
1854
|
end
|
1106
1855
|
end
|
1107
1856
|
|
1108
|
-
rep = subject.
|
1857
|
+
rep = subject.value_for(:first_friend)
|
1109
1858
|
expect(rep).to be_kind_of EntitySpec::FriendEntity
|
1110
1859
|
expect(rep.serializable_hash).to eq(name: 'Friend 1', email: 'friend1@example.com')
|
1111
1860
|
end
|
@@ -1118,12 +1867,14 @@ describe Grape::Entity do
|
|
1118
1867
|
end
|
1119
1868
|
end
|
1120
1869
|
|
1870
|
+
# rubocop:disable Lint/EmptyBlock
|
1121
1871
|
fresh_class.class_eval do
|
1122
1872
|
expose :first_friend, using: EntitySpec::FriendEntity do |_user, _opts|
|
1123
1873
|
end
|
1124
1874
|
end
|
1875
|
+
# rubocop:enable Lint/EmptyBlock
|
1125
1876
|
|
1126
|
-
rep = subject.
|
1877
|
+
rep = subject.value_for(:first_friend)
|
1127
1878
|
expect(rep).to be_kind_of EntitySpec::FriendEntity
|
1128
1879
|
expect(rep.serializable_hash).to be_nil
|
1129
1880
|
end
|
@@ -1140,7 +1891,7 @@ describe Grape::Entity do
|
|
1140
1891
|
expose :characteristics, using: EntitySpec::CharacteristicsEntity
|
1141
1892
|
end
|
1142
1893
|
|
1143
|
-
rep = subject.
|
1894
|
+
rep = subject.value_for(:characteristics)
|
1144
1895
|
expect(rep).to be_kind_of Array
|
1145
1896
|
expect(rep.reject { |r| r.is_a?(EntitySpec::CharacteristicsEntity) }).to be_empty
|
1146
1897
|
expect(rep.first.serializable_hash[:key]).to eq 'hair_color'
|
@@ -1160,13 +1911,13 @@ describe Grape::Entity do
|
|
1160
1911
|
expose :friends, using: EntitySpec::FriendEntity
|
1161
1912
|
end
|
1162
1913
|
|
1163
|
-
rep = subject.
|
1914
|
+
rep = subject.value_for(:friends)
|
1164
1915
|
expect(rep).to be_kind_of Array
|
1165
1916
|
expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
|
1166
1917
|
expect(rep.first.serializable_hash[:email]).to be_nil
|
1167
1918
|
expect(rep.last.serializable_hash[:email]).to be_nil
|
1168
1919
|
|
1169
|
-
rep = subject.
|
1920
|
+
rep = subject.value_for(:friends, Grape::Entity::Options.new(user_type: :admin))
|
1170
1921
|
expect(rep).to be_kind_of Array
|
1171
1922
|
expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
|
1172
1923
|
expect(rep.first.serializable_hash[:email]).to eq 'friend1@example.com'
|
@@ -1186,7 +1937,7 @@ describe Grape::Entity do
|
|
1186
1937
|
expose :friends, using: EntitySpec::FriendEntity
|
1187
1938
|
end
|
1188
1939
|
|
1189
|
-
rep = subject.
|
1940
|
+
rep = subject.value_for(:friends, Grape::Entity::Options.new(collection: false))
|
1190
1941
|
expect(rep).to be_kind_of Array
|
1191
1942
|
expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
|
1192
1943
|
expect(rep.first.serializable_hash[:email]).to eq 'friend1@example.com'
|
@@ -1195,23 +1946,24 @@ describe Grape::Entity do
|
|
1195
1946
|
end
|
1196
1947
|
|
1197
1948
|
it 'calls through to the proc if there is one' do
|
1198
|
-
expect(subject.
|
1949
|
+
expect(subject.value_for(:computed, Grape::Entity::Options.new(awesome: 123))).to eq 123
|
1199
1950
|
end
|
1200
1951
|
|
1201
1952
|
it 'returns a formatted value if format_with is passed' do
|
1202
|
-
expect(subject.
|
1953
|
+
expect(subject.value_for(:birthday)).to eq '02/27/2012'
|
1203
1954
|
end
|
1204
1955
|
|
1205
1956
|
it 'returns a formatted value if format_with is passed a lambda' do
|
1206
|
-
expect(subject.
|
1957
|
+
expect(subject.value_for(:fantasies)).to eq ['Nessy', 'Double Rainbows', 'Unicorns']
|
1207
1958
|
end
|
1208
1959
|
|
1209
|
-
|
1960
|
+
context 'delegate_attribute' do
|
1210
1961
|
module EntitySpec
|
1211
1962
|
class DelegatingEntity < Grape::Entity
|
1212
1963
|
root 'friends', 'friend'
|
1213
1964
|
expose :name
|
1214
1965
|
expose :email
|
1966
|
+
expose :system
|
1215
1967
|
|
1216
1968
|
private
|
1217
1969
|
|
@@ -1221,14 +1973,33 @@ describe Grape::Entity do
|
|
1221
1973
|
end
|
1222
1974
|
end
|
1223
1975
|
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1976
|
+
it 'tries instance methods on the entity first' do
|
1977
|
+
friend = double('Friend', name: 'joe', email: 'joe@example.com')
|
1978
|
+
rep = EntitySpec::DelegatingEntity.new(friend)
|
1979
|
+
expect(rep.value_for(:name)).to eq 'cooler name'
|
1980
|
+
expect(rep.value_for(:email)).to eq 'joe@example.com'
|
1981
|
+
|
1982
|
+
another_friend = double('Friend', email: 'joe@example.com')
|
1983
|
+
rep = EntitySpec::DelegatingEntity.new(another_friend)
|
1984
|
+
expect(rep.value_for(:name)).to eq 'cooler name'
|
1985
|
+
end
|
1986
|
+
|
1987
|
+
it 'does not delegate Kernel methods' do
|
1988
|
+
foo = double 'Foo', system: 'System'
|
1989
|
+
rep = EntitySpec::DelegatingEntity.new foo
|
1990
|
+
expect(rep.value_for(:system)).to eq 'System'
|
1991
|
+
end
|
1228
1992
|
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1993
|
+
module EntitySpec
|
1994
|
+
class DerivedEntity < DelegatingEntity
|
1995
|
+
end
|
1996
|
+
end
|
1997
|
+
|
1998
|
+
it 'derived entity get methods from base entity' do
|
1999
|
+
foo = double 'Foo', name: 'joe'
|
2000
|
+
rep = EntitySpec::DerivedEntity.new foo
|
2001
|
+
expect(rep.value_for(:name)).to eq 'cooler name'
|
2002
|
+
end
|
1232
2003
|
end
|
1233
2004
|
|
1234
2005
|
context 'using' do
|
@@ -1244,7 +2015,7 @@ describe Grape::Entity do
|
|
1244
2015
|
expose :friends, using: 'EntitySpec::UserEntity'
|
1245
2016
|
end
|
1246
2017
|
|
1247
|
-
rep = subject.
|
2018
|
+
rep = subject.value_for(:friends)
|
1248
2019
|
expect(rep).to be_kind_of Array
|
1249
2020
|
expect(rep.size).to eq 2
|
1250
2021
|
expect(rep.all? { |r| r.is_a?(EntitySpec::UserEntity) }).to be true
|
@@ -1255,7 +2026,7 @@ describe Grape::Entity do
|
|
1255
2026
|
expose :friends, using: EntitySpec::UserEntity
|
1256
2027
|
end
|
1257
2028
|
|
1258
|
-
rep = subject.
|
2029
|
+
rep = subject.value_for(:friends)
|
1259
2030
|
expect(rep).to be_kind_of Array
|
1260
2031
|
expect(rep.size).to eq 2
|
1261
2032
|
expect(rep.all? { |r| r.is_a?(EntitySpec::UserEntity) }).to be true
|
@@ -1263,7 +2034,7 @@ describe Grape::Entity do
|
|
1263
2034
|
end
|
1264
2035
|
end
|
1265
2036
|
|
1266
|
-
describe '
|
2037
|
+
describe '.documentation' do
|
1267
2038
|
it 'returns an empty hash is no documentation is provided' do
|
1268
2039
|
fresh_class.expose :name
|
1269
2040
|
|
@@ -1288,6 +2059,18 @@ describe Grape::Entity do
|
|
1288
2059
|
expect(subject.documentation).to eq(label: doc, email: doc)
|
1289
2060
|
end
|
1290
2061
|
|
2062
|
+
it 'resets memoization when exposing additional attributes' do
|
2063
|
+
fresh_class.expose :x, documentation: { desc: 'just x' }
|
2064
|
+
expect(fresh_class.instance_variable_get(:@documentation)).to be_nil
|
2065
|
+
doc1 = fresh_class.documentation
|
2066
|
+
expect(fresh_class.instance_variable_get(:@documentation)).not_to be_nil
|
2067
|
+
fresh_class.expose :y, documentation: { desc: 'just y' }
|
2068
|
+
expect(fresh_class.instance_variable_get(:@documentation)).to be_nil
|
2069
|
+
doc2 = fresh_class.documentation
|
2070
|
+
expect(doc1).to eq(x: { desc: 'just x' })
|
2071
|
+
expect(doc2).to eq(x: { desc: 'just x' }, y: { desc: 'just y' })
|
2072
|
+
end
|
2073
|
+
|
1291
2074
|
context 'inherited documentation' do
|
1292
2075
|
it 'returns documentation from ancestor' do
|
1293
2076
|
doc = { type: 'foo', desc: 'bar' }
|
@@ -1326,78 +2109,14 @@ describe Grape::Entity do
|
|
1326
2109
|
expect(child_class.documentation).to eq(name: doc)
|
1327
2110
|
expect(nephew_class.documentation).to eq(name: doc, email: new_doc)
|
1328
2111
|
end
|
1329
|
-
end
|
1330
|
-
end
|
1331
|
-
|
1332
|
-
describe '#key_for' do
|
1333
|
-
it 'returns the attribute if no :as is set' do
|
1334
|
-
fresh_class.expose :name
|
1335
|
-
expect(subject.class.send(:key_for, :name)).to eq :name
|
1336
|
-
end
|
1337
2112
|
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
1345
|
-
expect(subject.class.send(:key_for, 'name')).to eq :nombre
|
1346
|
-
end
|
1347
|
-
end
|
1348
|
-
|
1349
|
-
describe '#conditions_met?' do
|
1350
|
-
it 'only passes through hash :if exposure if all attributes match' do
|
1351
|
-
exposure_options = { if: { condition1: true, condition2: true } }
|
1352
|
-
|
1353
|
-
expect(subject.send(:conditions_met?, exposure_options, {})).to be false
|
1354
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be false
|
1355
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true)).to be true
|
1356
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: false, condition2: true)).to be false
|
1357
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true, other: true)).to be true
|
1358
|
-
end
|
1359
|
-
|
1360
|
-
it 'looks for presence/truthiness if a symbol is passed' do
|
1361
|
-
exposure_options = { if: :condition1 }
|
1362
|
-
|
1363
|
-
expect(subject.send(:conditions_met?, exposure_options, {})).to be false
|
1364
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be true
|
1365
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: false)).to be false
|
1366
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: nil)).to be false
|
1367
|
-
end
|
1368
|
-
|
1369
|
-
it 'looks for absence/falsiness if a symbol is passed' do
|
1370
|
-
exposure_options = { unless: :condition1 }
|
1371
|
-
|
1372
|
-
expect(subject.send(:conditions_met?, exposure_options, {})).to be true
|
1373
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be false
|
1374
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: false)).to be true
|
1375
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: nil)).to be true
|
1376
|
-
end
|
1377
|
-
|
1378
|
-
it 'only passes through proc :if exposure if it returns truthy value' do
|
1379
|
-
exposure_options = { if: ->(_, opts) { opts[:true] } }
|
1380
|
-
|
1381
|
-
expect(subject.send(:conditions_met?, exposure_options, true: false)).to be false
|
1382
|
-
expect(subject.send(:conditions_met?, exposure_options, true: true)).to be true
|
1383
|
-
end
|
1384
|
-
|
1385
|
-
it 'only passes through hash :unless exposure if any attributes do not match' do
|
1386
|
-
exposure_options = { unless: { condition1: true, condition2: true } }
|
1387
|
-
|
1388
|
-
expect(subject.send(:conditions_met?, exposure_options, {})).to be true
|
1389
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be false
|
1390
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true)).to be false
|
1391
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: false, condition2: true)).to be false
|
1392
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true, other: true)).to be false
|
1393
|
-
expect(subject.send(:conditions_met?, exposure_options, condition1: false, condition2: false)).to be true
|
1394
|
-
end
|
1395
|
-
|
1396
|
-
it 'only passes through proc :unless exposure if it returns falsy value' do
|
1397
|
-
exposure_options = { unless: ->(_, opts) { opts[:true] == true } }
|
1398
|
-
|
1399
|
-
expect(subject.send(:conditions_met?, exposure_options, true: false)).to be true
|
1400
|
-
expect(subject.send(:conditions_met?, exposure_options, true: true)).to be false
|
2113
|
+
it 'includes only root exposures' do
|
2114
|
+
fresh_class.expose :name, documentation: { desc: 'foo' }
|
2115
|
+
fresh_class.expose :nesting do
|
2116
|
+
fresh_class.expose :smth, documentation: { desc: 'should not be seen' }
|
2117
|
+
end
|
2118
|
+
expect(fresh_class.documentation).to eq(name: { desc: 'foo' })
|
2119
|
+
end
|
1401
2120
|
end
|
1402
2121
|
end
|
1403
2122
|
|
@@ -1418,19 +2137,19 @@ describe Grape::Entity do
|
|
1418
2137
|
expose :name
|
1419
2138
|
end
|
1420
2139
|
|
1421
|
-
expect(subject.entity_class.
|
2140
|
+
expect(subject.entity_class.root_exposures).not_to be_empty
|
1422
2141
|
end
|
1423
2142
|
|
1424
2143
|
it 'is able to expose straight from the class' do
|
1425
2144
|
subject.entity :name, :email
|
1426
|
-
expect(subject.entity_class.
|
2145
|
+
expect(subject.entity_class.root_exposures.size).to eq 2
|
1427
2146
|
end
|
1428
2147
|
|
1429
|
-
it 'is able to mix field and advanced
|
2148
|
+
it 'is able to mix field and advanced.root_exposures' do
|
1430
2149
|
subject.entity :name, :email do
|
1431
2150
|
expose :third
|
1432
2151
|
end
|
1433
|
-
expect(subject.entity_class.
|
2152
|
+
expect(subject.entity_class.root_exposures.size).to eq 3
|
1434
2153
|
end
|
1435
2154
|
|
1436
2155
|
context 'instance' do
|