grape-entity 1.0.2 → 1.0.3
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 +4 -4
- data/CHANGELOG.md +15 -4
- data/lib/grape_entity/entity.rb +6 -9
- data/lib/grape_entity/version.rb +1 -1
- metadata +10 -37
- data/.coveralls.yml +0 -1
- data/.github/dependabot.yml +0 -20
- data/.github/workflows/ci.yml +0 -41
- data/.gitignore +0 -43
- data/.rspec +0 -4
- data/.rubocop.yml +0 -89
- data/.rubocop_todo.yml +0 -55
- data/.yardopts +0 -2
- data/CONTRIBUTING.md +0 -118
- data/Dangerfile +0 -3
- data/Gemfile +0 -28
- data/Guardfile +0 -16
- data/RELEASING.md +0 -84
- data/Rakefile +0 -20
- data/UPGRADING.md +0 -40
- data/bench/serializing.rb +0 -105
- data/grape-entity.gemspec +0 -24
- data/spec/grape_entity/entity_spec.rb +0 -2238
- data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +0 -56
- data/spec/grape_entity/exposure/represent_exposure_spec.rb +0 -30
- data/spec/grape_entity/exposure_spec.rb +0 -100
- data/spec/grape_entity/hash_spec.rb +0 -89
- data/spec/grape_entity/json_spec.rb +0 -7
- data/spec/grape_entity/options_spec.rb +0 -64
- data/spec/spec_helper.rb +0 -31
|
@@ -1,2238 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'ostruct'
|
|
4
|
-
|
|
5
|
-
describe Grape::Entity do
|
|
6
|
-
let(:fresh_class) { Class.new(Grape::Entity) }
|
|
7
|
-
|
|
8
|
-
context 'class methods' do
|
|
9
|
-
subject { fresh_class }
|
|
10
|
-
|
|
11
|
-
describe '.expose' do
|
|
12
|
-
context 'multiple attributes' do
|
|
13
|
-
it 'is able to add multiple exposed attributes with a single call' do
|
|
14
|
-
subject.expose :name, :email, :location
|
|
15
|
-
expect(subject.root_exposures.size).to eq 3
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it 'sets the same options for all.root_exposures passed' do
|
|
19
|
-
subject.expose :name, :email, :location, documentation: true
|
|
20
|
-
subject.root_exposures.each { |v| expect(v.documentation).to eq true }
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
context 'option validation' do
|
|
25
|
-
it 'makes sure that :as only works on single attribute calls' do
|
|
26
|
-
expect { subject.expose :name, :email, as: :foo }.to raise_error ArgumentError
|
|
27
|
-
expect { subject.expose :name, as: :foo }.not_to raise_error
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it 'makes sure that :format_with as a proc cannot be used with a block' do
|
|
31
|
-
# rubocop:disable Style/BlockDelimiters
|
|
32
|
-
expect {
|
|
33
|
-
subject.expose :name, format_with: proc {} do
|
|
34
|
-
p 'hi'
|
|
35
|
-
end
|
|
36
|
-
}.to raise_error ArgumentError
|
|
37
|
-
# rubocop:enable Style/BlockDelimiters
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
it 'makes sure unknown options are not silently ignored' do
|
|
41
|
-
expect { subject.expose :name, unknown: nil }.to raise_error ArgumentError
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
context 'with a :merge option' do
|
|
46
|
-
let(:nested_hash) do
|
|
47
|
-
{ something: { like_nested_hash: true }, special: { like_nested_hash: '12' } }
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
it 'merges an exposure to the root' do
|
|
51
|
-
subject.expose(:something, merge: true)
|
|
52
|
-
expect(subject.represent(nested_hash).serializable_hash).to eq(nested_hash[:something])
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it 'allows to solve collisions providing a lambda to a :merge option' do
|
|
56
|
-
subject.expose(:something, merge: true)
|
|
57
|
-
subject.expose(:special, merge: ->(_, v1, v2) { v1 && v2 ? 'brand new val' : v2 })
|
|
58
|
-
expect(subject.represent(nested_hash).serializable_hash).to eq(like_nested_hash: 'brand new val')
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
context 'and nested object is nil' do
|
|
62
|
-
let(:nested_hash) do
|
|
63
|
-
{ something: nil, special: { like_nested_hash: '12' } }
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
it 'adds nothing to output' do
|
|
67
|
-
subject.expose(:something, merge: true)
|
|
68
|
-
subject.expose(:special)
|
|
69
|
-
expect(subject.represent(nested_hash).serializable_hash).to eq(special: { like_nested_hash: '12' })
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
context 'with :expose_nil option' do
|
|
75
|
-
let(:a) { nil }
|
|
76
|
-
let(:b) { nil }
|
|
77
|
-
let(:c) { 'value' }
|
|
78
|
-
|
|
79
|
-
context 'when model is a PORO' do
|
|
80
|
-
let(:model) { Model.new(a, b, c) }
|
|
81
|
-
|
|
82
|
-
before do
|
|
83
|
-
stub_const 'Model', Class.new
|
|
84
|
-
Model.class_eval do
|
|
85
|
-
attr_accessor :a, :b, :c
|
|
86
|
-
|
|
87
|
-
def initialize(a, b, c)
|
|
88
|
-
@a = a
|
|
89
|
-
@b = b
|
|
90
|
-
@c = c
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
context 'when expose_nil option is not provided' do
|
|
96
|
-
it 'exposes nil attributes' do
|
|
97
|
-
subject.expose(:a)
|
|
98
|
-
subject.expose(:b)
|
|
99
|
-
subject.expose(:c)
|
|
100
|
-
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
context 'when expose_nil option is true' do
|
|
105
|
-
it 'exposes nil attributes' do
|
|
106
|
-
subject.expose(:a, expose_nil: true)
|
|
107
|
-
subject.expose(:b, expose_nil: true)
|
|
108
|
-
subject.expose(:c)
|
|
109
|
-
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
context 'when expose_nil option is false' do
|
|
114
|
-
it 'does not expose nil attributes' do
|
|
115
|
-
subject.expose(:a, expose_nil: false)
|
|
116
|
-
subject.expose(:b, expose_nil: false)
|
|
117
|
-
subject.expose(:c)
|
|
118
|
-
expect(subject.represent(model).serializable_hash).to eq(c: 'value')
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
it 'is only applied per attribute' do
|
|
122
|
-
subject.expose(:a, expose_nil: false)
|
|
123
|
-
subject.expose(:b)
|
|
124
|
-
subject.expose(:c)
|
|
125
|
-
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
it 'raises an error when applied to multiple attribute exposures' do
|
|
129
|
-
expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
context 'when expose_nil option is false and block passed' do
|
|
134
|
-
it 'does not expose if block returns nil' do
|
|
135
|
-
subject.expose(:a, expose_nil: false) do |_obj, options|
|
|
136
|
-
options[:option_a]
|
|
137
|
-
end
|
|
138
|
-
subject.expose(:b)
|
|
139
|
-
subject.expose(:c)
|
|
140
|
-
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
it 'exposes is block returns a value' do
|
|
144
|
-
subject.expose(:a, expose_nil: false) do |_obj, options|
|
|
145
|
-
options[:option_a]
|
|
146
|
-
end
|
|
147
|
-
subject.expose(:b)
|
|
148
|
-
subject.expose(:c)
|
|
149
|
-
expect(subject.represent(model, option_a: 100).serializable_hash).to eq(a: 100, b: nil, c: 'value')
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
context 'when model is a hash' do
|
|
155
|
-
let(:model) { { a: a, b: b, c: c } }
|
|
156
|
-
|
|
157
|
-
context 'when expose_nil option is not provided' do
|
|
158
|
-
it 'exposes nil attributes' do
|
|
159
|
-
subject.expose(:a)
|
|
160
|
-
subject.expose(:b)
|
|
161
|
-
subject.expose(:c)
|
|
162
|
-
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
context 'when expose_nil option is true' do
|
|
167
|
-
it 'exposes nil attributes' do
|
|
168
|
-
subject.expose(:a, expose_nil: true)
|
|
169
|
-
subject.expose(:b, expose_nil: true)
|
|
170
|
-
subject.expose(:c)
|
|
171
|
-
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
context 'when expose_nil option is false' do
|
|
176
|
-
it 'does not expose nil attributes' do
|
|
177
|
-
subject.expose(:a, expose_nil: false)
|
|
178
|
-
subject.expose(:b, expose_nil: false)
|
|
179
|
-
subject.expose(:c)
|
|
180
|
-
expect(subject.represent(model).serializable_hash).to eq(c: 'value')
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
it 'is only applied per attribute' do
|
|
184
|
-
subject.expose(:a, expose_nil: false)
|
|
185
|
-
subject.expose(:b)
|
|
186
|
-
subject.expose(:c)
|
|
187
|
-
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
it 'raises an error when applied to multiple attribute exposures' do
|
|
191
|
-
expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
context 'with nested structures' do
|
|
197
|
-
let(:model) { { a: a, b: b, c: { d: nil, e: nil, f: { g: nil, h: nil } } } }
|
|
198
|
-
|
|
199
|
-
context 'when expose_nil option is false' do
|
|
200
|
-
it 'does not expose nil attributes' do
|
|
201
|
-
subject.expose(:a, expose_nil: false)
|
|
202
|
-
subject.expose(:b)
|
|
203
|
-
subject.expose(:c) do
|
|
204
|
-
subject.expose(:d, expose_nil: false)
|
|
205
|
-
subject.expose(:e)
|
|
206
|
-
subject.expose(:f) do
|
|
207
|
-
subject.expose(:g, expose_nil: false)
|
|
208
|
-
subject.expose(:h)
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: { e: nil, f: { h: nil } })
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
context 'with :default option' do
|
|
219
|
-
let(:a) { nil }
|
|
220
|
-
let(:b) { nil }
|
|
221
|
-
let(:c) { 'value' }
|
|
222
|
-
|
|
223
|
-
context 'when model is a PORO' do
|
|
224
|
-
let(:model) { Model.new(a, b, c) }
|
|
225
|
-
|
|
226
|
-
before do
|
|
227
|
-
stub_const 'Model', Class.new
|
|
228
|
-
Model.class_eval do
|
|
229
|
-
attr_accessor :a, :b, :c
|
|
230
|
-
|
|
231
|
-
def initialize(a, b, c)
|
|
232
|
-
@a = a
|
|
233
|
-
@b = b
|
|
234
|
-
@c = c
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
context 'when default option is not provided' do
|
|
240
|
-
it 'exposes attributes values' do
|
|
241
|
-
subject.expose(:a)
|
|
242
|
-
subject.expose(:b)
|
|
243
|
-
subject.expose(:c)
|
|
244
|
-
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
context 'when default option is set' do
|
|
249
|
-
it 'exposes default values for attributes' do
|
|
250
|
-
subject.expose(:a, default: 'a')
|
|
251
|
-
subject.expose(:b, default: false)
|
|
252
|
-
subject.expose(:c, default: 'c')
|
|
253
|
-
expect(subject.represent(model).serializable_hash).to eq(a: 'a', b: false, c: 'value')
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
context 'when default option is set and block passed' do
|
|
258
|
-
it 'return default value if block returns nil' do
|
|
259
|
-
subject.expose(:a, default: 'a') do |_obj, _options|
|
|
260
|
-
nil
|
|
261
|
-
end
|
|
262
|
-
subject.expose(:b)
|
|
263
|
-
subject.expose(:c)
|
|
264
|
-
expect(subject.represent(model).serializable_hash).to eq(a: 'a', b: nil, c: 'value')
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
it 'return value from block if block returns a value' do
|
|
268
|
-
subject.expose(:a, default: 'a') do |_obj, _options|
|
|
269
|
-
100
|
|
270
|
-
end
|
|
271
|
-
subject.expose(:b)
|
|
272
|
-
subject.expose(:c)
|
|
273
|
-
expect(subject.represent(model).serializable_hash).to eq(a: 100, b: nil, c: 'value')
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
context 'when model is a hash' do
|
|
279
|
-
let(:model) { { a: a, b: b, c: c } }
|
|
280
|
-
|
|
281
|
-
context 'when expose_nil option is not provided' do
|
|
282
|
-
it 'exposes nil attributes' do
|
|
283
|
-
subject.expose(:a)
|
|
284
|
-
subject.expose(:b)
|
|
285
|
-
subject.expose(:c)
|
|
286
|
-
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
context 'when expose_nil option is true' do
|
|
291
|
-
it 'exposes nil attributes' do
|
|
292
|
-
subject.expose(:a, expose_nil: true)
|
|
293
|
-
subject.expose(:b, expose_nil: true)
|
|
294
|
-
subject.expose(:c)
|
|
295
|
-
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
|
296
|
-
end
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
context 'when expose_nil option is false' do
|
|
300
|
-
it 'does not expose nil attributes' do
|
|
301
|
-
subject.expose(:a, expose_nil: false)
|
|
302
|
-
subject.expose(:b, expose_nil: false)
|
|
303
|
-
subject.expose(:c)
|
|
304
|
-
expect(subject.represent(model).serializable_hash).to eq(c: 'value')
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
it 'is only applied per attribute' do
|
|
308
|
-
subject.expose(:a, expose_nil: false)
|
|
309
|
-
subject.expose(:b)
|
|
310
|
-
subject.expose(:c)
|
|
311
|
-
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
it 'raises an error when applied to multiple attribute exposures' do
|
|
315
|
-
expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
context 'with nested structures' do
|
|
321
|
-
let(:model) { { a: a, b: b, c: { d: nil, e: nil, f: { g: nil, h: nil } } } }
|
|
322
|
-
|
|
323
|
-
context 'when expose_nil option is false' do
|
|
324
|
-
it 'does not expose nil attributes' do
|
|
325
|
-
subject.expose(:a, expose_nil: false)
|
|
326
|
-
subject.expose(:b)
|
|
327
|
-
subject.expose(:c) do
|
|
328
|
-
subject.expose(:d, expose_nil: false)
|
|
329
|
-
subject.expose(:e)
|
|
330
|
-
subject.expose(:f) do
|
|
331
|
-
subject.expose(:g, expose_nil: false)
|
|
332
|
-
subject.expose(:h)
|
|
333
|
-
end
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: { e: nil, f: { h: nil } })
|
|
337
|
-
end
|
|
338
|
-
end
|
|
339
|
-
end
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
context 'with a block' do
|
|
343
|
-
it 'errors out if called with multiple attributes' do
|
|
344
|
-
expect { subject.expose(:name, :email) { true } }.to raise_error ArgumentError
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
it 'references an instance of the entity with :using option' do
|
|
348
|
-
module EntitySpec
|
|
349
|
-
class SomeObject1
|
|
350
|
-
attr_accessor :prop1
|
|
351
|
-
|
|
352
|
-
def initialize
|
|
353
|
-
@prop1 = 'value1'
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
class BogusEntity < Grape::Entity
|
|
358
|
-
expose :prop1
|
|
359
|
-
end
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
subject.expose(:bogus, using: EntitySpec::BogusEntity) do |entity|
|
|
363
|
-
entity.prop1 = 'MODIFIED 2'
|
|
364
|
-
entity
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
object = EntitySpec::SomeObject1.new
|
|
368
|
-
value = subject.represent(object).value_for(:bogus)
|
|
369
|
-
expect(value).to be_instance_of EntitySpec::BogusEntity
|
|
370
|
-
|
|
371
|
-
prop1 = value.value_for(:prop1)
|
|
372
|
-
expect(prop1).to eq 'MODIFIED 2'
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
context 'with parameters passed to the block' do
|
|
376
|
-
it 'sets the :proc option in the exposure options' do
|
|
377
|
-
block = ->(_) { true }
|
|
378
|
-
subject.expose :name, using: 'Awesome', &block
|
|
379
|
-
exposure = subject.find_exposure(:name)
|
|
380
|
-
expect(exposure.subexposure.block).to eq(block)
|
|
381
|
-
expect(exposure.using_class_name).to eq('Awesome')
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
it 'references an instance of the entity without any options' do
|
|
385
|
-
subject.expose(:size) { |_| self }
|
|
386
|
-
expect(subject.represent({}).value_for(:size)).to be_an_instance_of fresh_class
|
|
387
|
-
end
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
describe 'blocks' do
|
|
391
|
-
class SomeObject
|
|
392
|
-
def method_without_args
|
|
393
|
-
'result'
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
def method_with_one_arg(_object)
|
|
397
|
-
'result'
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
def method_with_multiple_args(_object, _options)
|
|
401
|
-
'result'
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
def raises_argument_error
|
|
405
|
-
raise ArgumentError, 'something different'
|
|
406
|
-
end
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
describe 'with block passed in' do
|
|
410
|
-
specify do
|
|
411
|
-
subject.expose :that_method_without_args do |object|
|
|
412
|
-
object.method_without_args
|
|
413
|
-
end
|
|
414
|
-
|
|
415
|
-
object = SomeObject.new
|
|
416
|
-
|
|
417
|
-
value = subject.represent(object).value_for(:that_method_without_args)
|
|
418
|
-
expect(value).to eq('result')
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
it 'does not suppress ArgumentError' do
|
|
422
|
-
subject.expose :raises_argument_error do |object|
|
|
423
|
-
object.raises_argument_error
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
object = SomeObject.new
|
|
427
|
-
expect do
|
|
428
|
-
subject.represent(object).value_for(:raises_argument_error)
|
|
429
|
-
end.to raise_error(ArgumentError, 'something different')
|
|
430
|
-
end
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
context 'with block passed in via &' do
|
|
434
|
-
specify do
|
|
435
|
-
subject.expose :that_method_without_args, &:method_without_args
|
|
436
|
-
subject.expose :method_without_args, as: :that_method_without_args_again
|
|
437
|
-
|
|
438
|
-
object = SomeObject.new
|
|
439
|
-
|
|
440
|
-
value = subject.represent(object).value_for(:method_without_args)
|
|
441
|
-
expect(value).to be_nil
|
|
442
|
-
|
|
443
|
-
value = subject.represent(object).value_for(:that_method_without_args)
|
|
444
|
-
expect(value).to eq('result')
|
|
445
|
-
|
|
446
|
-
value = subject.represent(object).value_for(:that_method_without_args_again)
|
|
447
|
-
expect(value).to eq('result')
|
|
448
|
-
end
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
context 'with block passed in via &' do
|
|
452
|
-
specify do
|
|
453
|
-
subject.expose :that_method_with_one_arg, &:method_with_one_arg
|
|
454
|
-
subject.expose :that_method_with_multple_args, &:method_with_multiple_args
|
|
455
|
-
|
|
456
|
-
object = SomeObject.new
|
|
457
|
-
|
|
458
|
-
expect do
|
|
459
|
-
subject.represent(object).value_for(:that_method_with_one_arg)
|
|
460
|
-
end.to raise_error ArgumentError, match(/method expects 1 argument/)
|
|
461
|
-
|
|
462
|
-
expect do
|
|
463
|
-
subject.represent(object).value_for(:that_method_with_multple_args)
|
|
464
|
-
end.to raise_error ArgumentError, match(/method expects 2 arguments/)
|
|
465
|
-
end
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
context 'with symbol-to-proc passed in via &' do
|
|
469
|
-
specify do
|
|
470
|
-
subject.expose :that_undefined_method, &:unknown_method
|
|
471
|
-
|
|
472
|
-
object = SomeObject.new
|
|
473
|
-
|
|
474
|
-
expect do
|
|
475
|
-
subject.represent(object).value_for(:that_undefined_method)
|
|
476
|
-
end.to raise_error ArgumentError, match(/method is not defined in the object/)
|
|
477
|
-
end
|
|
478
|
-
end
|
|
479
|
-
end
|
|
480
|
-
|
|
481
|
-
context 'with no parameters passed to the block' do
|
|
482
|
-
it 'adds a nested exposure' do
|
|
483
|
-
subject.expose :awesome do
|
|
484
|
-
subject.expose :nested do
|
|
485
|
-
subject.expose :moar_nested, as: 'weee'
|
|
486
|
-
end
|
|
487
|
-
subject.expose :another_nested, using: 'Awesome'
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
awesome = subject.find_exposure(:awesome)
|
|
491
|
-
nested = awesome.find_nested_exposure(:nested)
|
|
492
|
-
another_nested = awesome.find_nested_exposure(:another_nested)
|
|
493
|
-
moar_nested = nested.find_nested_exposure(:moar_nested)
|
|
494
|
-
|
|
495
|
-
expect(awesome).to be_nesting
|
|
496
|
-
expect(nested).to_not be_nil
|
|
497
|
-
expect(another_nested).to_not be_nil
|
|
498
|
-
expect(another_nested.using_class_name).to eq('Awesome')
|
|
499
|
-
expect(moar_nested).to_not be_nil
|
|
500
|
-
expect(moar_nested.key(subject)).to eq(:weee)
|
|
501
|
-
end
|
|
502
|
-
|
|
503
|
-
it 'represents the exposure as a hash of its nested.root_exposures' do
|
|
504
|
-
subject.expose :awesome do
|
|
505
|
-
subject.expose(:nested) { |_| 'value' }
|
|
506
|
-
subject.expose(:another_nested) { |_| 'value' }
|
|
507
|
-
end
|
|
508
|
-
|
|
509
|
-
expect(subject.represent({}).value_for(:awesome)).to eq(
|
|
510
|
-
nested: 'value',
|
|
511
|
-
another_nested: 'value'
|
|
512
|
-
)
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
it 'does not represent nested.root_exposures whose conditions are not met' do
|
|
516
|
-
subject.expose :awesome do
|
|
517
|
-
subject.expose(:condition_met, if: ->(_, _) { true }) { |_| 'value' }
|
|
518
|
-
subject.expose(:condition_not_met, if: ->(_, _) { false }) { |_| 'value' }
|
|
519
|
-
end
|
|
520
|
-
|
|
521
|
-
expect(subject.represent({}).value_for(:awesome)).to eq(condition_met: 'value')
|
|
522
|
-
end
|
|
523
|
-
|
|
524
|
-
it 'does not represent attributes, declared inside nested exposure, outside of it' do
|
|
525
|
-
subject.expose :awesome do
|
|
526
|
-
subject.expose(:nested) { |_| 'value' }
|
|
527
|
-
subject.expose(:another_nested) { |_| 'value' }
|
|
528
|
-
subject.expose :second_level_nested do
|
|
529
|
-
subject.expose(:deeply_exposed_attr) { |_| 'value' }
|
|
530
|
-
end
|
|
531
|
-
end
|
|
532
|
-
|
|
533
|
-
expect(subject.represent({}).serializable_hash).to eq(
|
|
534
|
-
awesome: {
|
|
535
|
-
nested: 'value',
|
|
536
|
-
another_nested: 'value',
|
|
537
|
-
second_level_nested: {
|
|
538
|
-
deeply_exposed_attr: 'value'
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
)
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
it 'merges complex nested attributes' do
|
|
545
|
-
class ClassRoom < Grape::Entity
|
|
546
|
-
expose(:parents, using: 'Parent') { |_| [{}, {}] }
|
|
547
|
-
end
|
|
548
|
-
|
|
549
|
-
class Person < Grape::Entity
|
|
550
|
-
expose :user do
|
|
551
|
-
expose(:in_first) { |_| 'value' }
|
|
552
|
-
end
|
|
553
|
-
end
|
|
554
|
-
|
|
555
|
-
class Student < Person
|
|
556
|
-
expose :user do
|
|
557
|
-
expose(:user_id) { |_| 'value' }
|
|
558
|
-
expose(:user_display_id, as: :display_id) { |_| 'value' }
|
|
559
|
-
end
|
|
560
|
-
end
|
|
561
|
-
|
|
562
|
-
class Parent < Person
|
|
563
|
-
expose(:children, using: 'Student') { |_| [{}, {}] }
|
|
564
|
-
end
|
|
565
|
-
|
|
566
|
-
expect(ClassRoom.represent({}).serializable_hash).to eq(
|
|
567
|
-
parents: [
|
|
568
|
-
{
|
|
569
|
-
user: { in_first: 'value' },
|
|
570
|
-
children: [
|
|
571
|
-
{ user: { in_first: 'value', user_id: 'value', display_id: 'value' } },
|
|
572
|
-
{ user: { in_first: 'value', user_id: 'value', display_id: 'value' } }
|
|
573
|
-
]
|
|
574
|
-
},
|
|
575
|
-
{
|
|
576
|
-
user: { in_first: 'value' },
|
|
577
|
-
children: [
|
|
578
|
-
{ user: { in_first: 'value', user_id: 'value', display_id: 'value' } },
|
|
579
|
-
{ user: { in_first: 'value', user_id: 'value', display_id: 'value' } }
|
|
580
|
-
]
|
|
581
|
-
}
|
|
582
|
-
]
|
|
583
|
-
)
|
|
584
|
-
end
|
|
585
|
-
|
|
586
|
-
it 'merges results of deeply nested double.root_exposures inside of nesting exposure' do
|
|
587
|
-
entity = Class.new(Grape::Entity) do
|
|
588
|
-
expose :data do
|
|
589
|
-
expose :something do
|
|
590
|
-
expose(:x) { |_| 'x' }
|
|
591
|
-
end
|
|
592
|
-
expose :something do
|
|
593
|
-
expose(:y) { |_| 'y' }
|
|
594
|
-
end
|
|
595
|
-
end
|
|
596
|
-
end
|
|
597
|
-
expect(entity.represent({}).serializable_hash).to eq(
|
|
598
|
-
data: {
|
|
599
|
-
something: {
|
|
600
|
-
x: 'x',
|
|
601
|
-
y: 'y'
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
)
|
|
605
|
-
end
|
|
606
|
-
|
|
607
|
-
it 'serializes deeply nested presenter exposures' do
|
|
608
|
-
e = Class.new(Grape::Entity) do
|
|
609
|
-
expose :f
|
|
610
|
-
end
|
|
611
|
-
subject.expose :a do
|
|
612
|
-
subject.expose :b do
|
|
613
|
-
subject.expose :c do
|
|
614
|
-
subject.expose :lol, using: e
|
|
615
|
-
end
|
|
616
|
-
end
|
|
617
|
-
end
|
|
618
|
-
expect(subject.represent(lol: { f: 123 }).serializable_hash).to eq(
|
|
619
|
-
a: { b: { c: { lol: { f: 123 } } } }
|
|
620
|
-
)
|
|
621
|
-
end
|
|
622
|
-
|
|
623
|
-
it 'is safe if its nested.root_exposures are safe' do
|
|
624
|
-
subject.with_options safe: true do
|
|
625
|
-
subject.expose :awesome do
|
|
626
|
-
subject.expose(:nested) { |_| 'value' }
|
|
627
|
-
end
|
|
628
|
-
subject.expose :not_awesome do
|
|
629
|
-
subject.expose :nested
|
|
630
|
-
end
|
|
631
|
-
end
|
|
632
|
-
expect(subject.represent({}, serializable: true)).to eq(
|
|
633
|
-
awesome: {
|
|
634
|
-
nested: 'value'
|
|
635
|
-
},
|
|
636
|
-
not_awesome: {
|
|
637
|
-
nested: nil
|
|
638
|
-
}
|
|
639
|
-
)
|
|
640
|
-
end
|
|
641
|
-
it 'merges attriutes if :merge option is passed' do
|
|
642
|
-
user_entity = Class.new(Grape::Entity)
|
|
643
|
-
admin_entity = Class.new(Grape::Entity)
|
|
644
|
-
user_entity.expose(:id, :name)
|
|
645
|
-
admin_entity.expose(:id, :name)
|
|
646
|
-
|
|
647
|
-
subject.expose(:profiles) do
|
|
648
|
-
subject.expose(:users, merge: true, using: user_entity)
|
|
649
|
-
subject.expose(:admins, merge: true, using: admin_entity)
|
|
650
|
-
end
|
|
651
|
-
|
|
652
|
-
subject.expose :awesome do
|
|
653
|
-
subject.expose(:nested, merge: true) { |_| { just_a_key: 'value' } }
|
|
654
|
-
subject.expose(:another_nested, merge: true) { |_| { just_another_key: 'value' } }
|
|
655
|
-
end
|
|
656
|
-
|
|
657
|
-
additional_hash = { users: [{ id: 1, name: 'John' }, { id: 2, name: 'Jay' }],
|
|
658
|
-
admins: [{ id: 3, name: 'Jack' }, { id: 4, name: 'James' }] }
|
|
659
|
-
expect(subject.represent(additional_hash).serializable_hash).to eq(
|
|
660
|
-
profiles: additional_hash[:users] + additional_hash[:admins],
|
|
661
|
-
awesome: { just_a_key: 'value', just_another_key: 'value' }
|
|
662
|
-
)
|
|
663
|
-
end
|
|
664
|
-
end
|
|
665
|
-
end
|
|
666
|
-
|
|
667
|
-
context 'inherited.root_exposures' do
|
|
668
|
-
it 'returns.root_exposures from an ancestor' do
|
|
669
|
-
subject.expose :name, :email
|
|
670
|
-
child_class = Class.new(subject)
|
|
671
|
-
|
|
672
|
-
expect(child_class.root_exposures).to eq(subject.root_exposures)
|
|
673
|
-
end
|
|
674
|
-
|
|
675
|
-
it 'returns.root_exposures from multiple ancestor' do
|
|
676
|
-
subject.expose :name, :email
|
|
677
|
-
parent_class = Class.new(subject)
|
|
678
|
-
child_class = Class.new(parent_class)
|
|
679
|
-
|
|
680
|
-
expect(child_class.root_exposures).to eq(subject.root_exposures)
|
|
681
|
-
end
|
|
682
|
-
|
|
683
|
-
it 'returns descendant.root_exposures as a priority' do
|
|
684
|
-
subject.expose :name, :email
|
|
685
|
-
child_class = Class.new(subject)
|
|
686
|
-
child_class.expose :name do |_|
|
|
687
|
-
'foo'
|
|
688
|
-
end
|
|
689
|
-
|
|
690
|
-
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'bar')
|
|
691
|
-
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'foo')
|
|
692
|
-
end
|
|
693
|
-
|
|
694
|
-
it 'not overrides exposure by default' do
|
|
695
|
-
subject.expose :name
|
|
696
|
-
child_class = Class.new(subject)
|
|
697
|
-
child_class.expose :name, as: :child_name
|
|
698
|
-
|
|
699
|
-
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
|
|
700
|
-
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar', child_name: 'bar')
|
|
701
|
-
end
|
|
702
|
-
|
|
703
|
-
it 'overrides parent class exposure when option is specified' do
|
|
704
|
-
subject.expose :name
|
|
705
|
-
child_class = Class.new(subject)
|
|
706
|
-
child_class.expose :name, as: :child_name, override: true
|
|
707
|
-
|
|
708
|
-
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
|
|
709
|
-
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(child_name: 'bar')
|
|
710
|
-
end
|
|
711
|
-
end
|
|
712
|
-
|
|
713
|
-
context 'register formatters' do
|
|
714
|
-
let(:date_formatter) { ->(date) { date.strftime('%m/%d/%Y') } }
|
|
715
|
-
|
|
716
|
-
it 'registers a formatter' do
|
|
717
|
-
subject.format_with :timestamp, &date_formatter
|
|
718
|
-
|
|
719
|
-
expect(subject.formatters[:timestamp]).not_to be_nil
|
|
720
|
-
end
|
|
721
|
-
|
|
722
|
-
it 'inherits formatters from ancestors' do
|
|
723
|
-
subject.format_with :timestamp, &date_formatter
|
|
724
|
-
child_class = Class.new(subject)
|
|
725
|
-
|
|
726
|
-
expect(child_class.formatters).to eq subject.formatters
|
|
727
|
-
end
|
|
728
|
-
|
|
729
|
-
it 'does not allow registering a formatter without a block' do
|
|
730
|
-
expect { subject.format_with :foo }.to raise_error ArgumentError
|
|
731
|
-
end
|
|
732
|
-
|
|
733
|
-
it 'formats an exposure with a registered formatter' do
|
|
734
|
-
subject.format_with :timestamp do |date|
|
|
735
|
-
date.strftime('%m/%d/%Y')
|
|
736
|
-
end
|
|
737
|
-
|
|
738
|
-
subject.expose :birthday, format_with: :timestamp
|
|
739
|
-
|
|
740
|
-
model = { birthday: Time.gm(2012, 2, 27) }
|
|
741
|
-
expect(subject.new(double(model)).as_json[:birthday]).to eq '02/27/2012'
|
|
742
|
-
end
|
|
743
|
-
|
|
744
|
-
it 'formats an exposure with a :format_with lambda that returns a value from the entity instance' do
|
|
745
|
-
object = {}
|
|
746
|
-
|
|
747
|
-
subject.expose(:size, format_with: ->(_value) { object.class.to_s })
|
|
748
|
-
expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
|
|
749
|
-
end
|
|
750
|
-
|
|
751
|
-
it 'formats an exposure with a :format_with symbol that returns a value from the entity instance' do
|
|
752
|
-
subject.format_with :size_formatter do |_date|
|
|
753
|
-
object.class.to_s
|
|
754
|
-
end
|
|
755
|
-
|
|
756
|
-
object = {}
|
|
757
|
-
|
|
758
|
-
subject.expose(:size, format_with: :size_formatter)
|
|
759
|
-
expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
|
|
760
|
-
end
|
|
761
|
-
|
|
762
|
-
it 'works global on Grape::Entity' do
|
|
763
|
-
Grape::Entity.format_with :size_formatter do |_date|
|
|
764
|
-
object.class.to_s
|
|
765
|
-
end
|
|
766
|
-
object = {}
|
|
767
|
-
|
|
768
|
-
subject.expose(:size, format_with: :size_formatter)
|
|
769
|
-
expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
|
|
770
|
-
end
|
|
771
|
-
end
|
|
772
|
-
|
|
773
|
-
it 'works global on Grape::Entity' do
|
|
774
|
-
Grape::Entity.expose :a
|
|
775
|
-
object = { a: 11, b: 22 }
|
|
776
|
-
expect(Grape::Entity.represent(object).value_for(:a)).to eq 11
|
|
777
|
-
subject.expose :b
|
|
778
|
-
expect(subject.represent(object).value_for(:a)).to eq 11
|
|
779
|
-
expect(subject.represent(object).value_for(:b)).to eq 22
|
|
780
|
-
Grape::Entity.unexpose :a
|
|
781
|
-
end
|
|
782
|
-
end
|
|
783
|
-
|
|
784
|
-
describe '.unexpose' do
|
|
785
|
-
it 'is able to remove exposed attributes' do
|
|
786
|
-
subject.expose :name, :email
|
|
787
|
-
subject.unexpose :email
|
|
788
|
-
|
|
789
|
-
expect(subject.root_exposures.length).to eq 1
|
|
790
|
-
expect(subject.root_exposures[0].attribute).to eq :name
|
|
791
|
-
end
|
|
792
|
-
|
|
793
|
-
context 'inherited.root_exposures' do
|
|
794
|
-
it 'when called from child class, only removes from the attribute from child' do
|
|
795
|
-
subject.expose :name, :email
|
|
796
|
-
child_class = Class.new(subject)
|
|
797
|
-
child_class.unexpose :email
|
|
798
|
-
|
|
799
|
-
expect(child_class.root_exposures.length).to eq 1
|
|
800
|
-
expect(child_class.root_exposures[0].attribute).to eq :name
|
|
801
|
-
expect(subject.root_exposures[0].attribute).to eq :name
|
|
802
|
-
expect(subject.root_exposures[1].attribute).to eq :email
|
|
803
|
-
end
|
|
804
|
-
|
|
805
|
-
context 'when called from the parent class' do
|
|
806
|
-
it 'remove from parent and do not remove from child classes' do
|
|
807
|
-
subject.expose :name, :email
|
|
808
|
-
child_class = Class.new(subject)
|
|
809
|
-
subject.unexpose :email
|
|
810
|
-
|
|
811
|
-
expect(subject.root_exposures.length).to eq 1
|
|
812
|
-
expect(subject.root_exposures[0].attribute).to eq :name
|
|
813
|
-
expect(child_class.root_exposures[0].attribute).to eq :name
|
|
814
|
-
expect(child_class.root_exposures[1].attribute).to eq :email
|
|
815
|
-
end
|
|
816
|
-
end
|
|
817
|
-
end
|
|
818
|
-
|
|
819
|
-
it 'does not allow unexposing inside of nesting exposures' do
|
|
820
|
-
expect do
|
|
821
|
-
Class.new(Grape::Entity) do
|
|
822
|
-
expose :something do
|
|
823
|
-
expose :x
|
|
824
|
-
unexpose :x
|
|
825
|
-
end
|
|
826
|
-
end
|
|
827
|
-
end.to raise_error(/You cannot call 'unexpose`/)
|
|
828
|
-
end
|
|
829
|
-
|
|
830
|
-
it 'works global on Grape::Entity' do
|
|
831
|
-
Grape::Entity.expose :x
|
|
832
|
-
expect(Grape::Entity.root_exposures[0].attribute).to eq(:x)
|
|
833
|
-
Grape::Entity.unexpose :x
|
|
834
|
-
expect(Grape::Entity.root_exposures).to eq([])
|
|
835
|
-
end
|
|
836
|
-
end
|
|
837
|
-
|
|
838
|
-
describe '.with_options' do
|
|
839
|
-
it 'raises an error for unknown options' do
|
|
840
|
-
block = proc do
|
|
841
|
-
with_options(unknown: true) do
|
|
842
|
-
expose :awesome_thing
|
|
843
|
-
end
|
|
844
|
-
end
|
|
845
|
-
|
|
846
|
-
expect { subject.class_eval(&block) }.to raise_error ArgumentError
|
|
847
|
-
end
|
|
848
|
-
|
|
849
|
-
it 'applies the options to all.root_exposures inside' do
|
|
850
|
-
subject.class_eval do
|
|
851
|
-
with_options(if: { awesome: true }) do
|
|
852
|
-
expose :awesome_thing, using: 'Awesome'
|
|
853
|
-
end
|
|
854
|
-
end
|
|
855
|
-
|
|
856
|
-
exposure = subject.find_exposure(:awesome_thing)
|
|
857
|
-
expect(exposure.using_class_name).to eq('Awesome')
|
|
858
|
-
expect(exposure.conditions[0].cond_hash).to eq(awesome: true)
|
|
859
|
-
end
|
|
860
|
-
|
|
861
|
-
it 'allows for nested .with_options' do
|
|
862
|
-
subject.class_eval do
|
|
863
|
-
with_options(if: { awesome: true }) do
|
|
864
|
-
with_options(using: 'Something') do
|
|
865
|
-
expose :awesome_thing
|
|
866
|
-
end
|
|
867
|
-
end
|
|
868
|
-
end
|
|
869
|
-
|
|
870
|
-
exposure = subject.find_exposure(:awesome_thing)
|
|
871
|
-
expect(exposure.using_class_name).to eq('Something')
|
|
872
|
-
expect(exposure.conditions[0].cond_hash).to eq(awesome: true)
|
|
873
|
-
end
|
|
874
|
-
|
|
875
|
-
it 'overrides nested :as option' do
|
|
876
|
-
subject.class_eval do
|
|
877
|
-
with_options(as: :sweet) do
|
|
878
|
-
expose :awesome_thing, as: :extra_smooth
|
|
879
|
-
end
|
|
880
|
-
end
|
|
881
|
-
|
|
882
|
-
exposure = subject.find_exposure(:awesome_thing)
|
|
883
|
-
expect(exposure.key(subject)).to eq :extra_smooth
|
|
884
|
-
end
|
|
885
|
-
|
|
886
|
-
it 'merges nested :if option' do
|
|
887
|
-
match_proc = ->(_obj, _opts) { true }
|
|
888
|
-
|
|
889
|
-
subject.class_eval do
|
|
890
|
-
# Symbol
|
|
891
|
-
with_options(if: :awesome) do
|
|
892
|
-
# Hash
|
|
893
|
-
with_options(if: { awesome: true }) do
|
|
894
|
-
# Proc
|
|
895
|
-
with_options(if: match_proc) do
|
|
896
|
-
# Hash (override existing key and merge new key)
|
|
897
|
-
with_options(if: { awesome: false, less_awesome: true }) do
|
|
898
|
-
expose :awesome_thing
|
|
899
|
-
end
|
|
900
|
-
end
|
|
901
|
-
end
|
|
902
|
-
end
|
|
903
|
-
end
|
|
904
|
-
|
|
905
|
-
exposure = subject.find_exposure(:awesome_thing)
|
|
906
|
-
expect(exposure.conditions.any?(&:inversed?)).to be_falsey
|
|
907
|
-
expect(exposure.conditions[0].symbol).to eq(:awesome)
|
|
908
|
-
expect(exposure.conditions[1].block).to eq(match_proc)
|
|
909
|
-
expect(exposure.conditions[2].cond_hash).to eq(awesome: false, less_awesome: true)
|
|
910
|
-
end
|
|
911
|
-
|
|
912
|
-
it 'merges nested :unless option' do
|
|
913
|
-
match_proc = ->(_, _) { true }
|
|
914
|
-
|
|
915
|
-
subject.class_eval do
|
|
916
|
-
# Symbol
|
|
917
|
-
with_options(unless: :awesome) do
|
|
918
|
-
# Hash
|
|
919
|
-
with_options(unless: { awesome: true }) do
|
|
920
|
-
# Proc
|
|
921
|
-
with_options(unless: match_proc) do
|
|
922
|
-
# Hash (override existing key and merge new key)
|
|
923
|
-
with_options(unless: { awesome: false, less_awesome: true }) do
|
|
924
|
-
expose :awesome_thing
|
|
925
|
-
end
|
|
926
|
-
end
|
|
927
|
-
end
|
|
928
|
-
end
|
|
929
|
-
end
|
|
930
|
-
|
|
931
|
-
exposure = subject.find_exposure(:awesome_thing)
|
|
932
|
-
expect(exposure.conditions.all?(&:inversed?)).to be_truthy
|
|
933
|
-
expect(exposure.conditions[0].symbol).to eq(:awesome)
|
|
934
|
-
expect(exposure.conditions[1].block).to eq(match_proc)
|
|
935
|
-
expect(exposure.conditions[2].cond_hash).to eq(awesome: false, less_awesome: true)
|
|
936
|
-
end
|
|
937
|
-
|
|
938
|
-
it 'overrides nested :using option' do
|
|
939
|
-
subject.class_eval do
|
|
940
|
-
with_options(using: 'Something') do
|
|
941
|
-
expose :awesome_thing, using: 'SomethingElse'
|
|
942
|
-
end
|
|
943
|
-
end
|
|
944
|
-
|
|
945
|
-
exposure = subject.find_exposure(:awesome_thing)
|
|
946
|
-
expect(exposure.using_class_name).to eq('SomethingElse')
|
|
947
|
-
end
|
|
948
|
-
|
|
949
|
-
it 'aliases :with option to :using option' do
|
|
950
|
-
subject.class_eval do
|
|
951
|
-
with_options(using: 'Something') do
|
|
952
|
-
expose :awesome_thing, with: 'SomethingElse'
|
|
953
|
-
end
|
|
954
|
-
end
|
|
955
|
-
|
|
956
|
-
exposure = subject.find_exposure(:awesome_thing)
|
|
957
|
-
expect(exposure.using_class_name).to eq('SomethingElse')
|
|
958
|
-
end
|
|
959
|
-
|
|
960
|
-
it 'overrides nested :proc option' do
|
|
961
|
-
match_proc = ->(_obj, _opts) { 'more awesomer' }
|
|
962
|
-
|
|
963
|
-
subject.class_eval do
|
|
964
|
-
with_options(proc: ->(_obj, _opts) { 'awesome' }) do
|
|
965
|
-
expose :awesome_thing, proc: match_proc
|
|
966
|
-
end
|
|
967
|
-
end
|
|
968
|
-
|
|
969
|
-
exposure = subject.find_exposure(:awesome_thing)
|
|
970
|
-
expect(exposure.block).to eq(match_proc)
|
|
971
|
-
end
|
|
972
|
-
|
|
973
|
-
it 'overrides nested :documentation option' do
|
|
974
|
-
subject.class_eval do
|
|
975
|
-
with_options(documentation: { desc: 'Description.' }) do
|
|
976
|
-
expose :awesome_thing, documentation: { desc: 'Other description.' }
|
|
977
|
-
end
|
|
978
|
-
end
|
|
979
|
-
|
|
980
|
-
exposure = subject.find_exposure(:awesome_thing)
|
|
981
|
-
expect(exposure.documentation).to eq(desc: 'Other description.')
|
|
982
|
-
end
|
|
983
|
-
|
|
984
|
-
it 'propagates expose_nil option' do
|
|
985
|
-
subject.class_eval do
|
|
986
|
-
with_options(expose_nil: false) do
|
|
987
|
-
expose :awesome_thing
|
|
988
|
-
end
|
|
989
|
-
end
|
|
990
|
-
|
|
991
|
-
exposure = subject.find_exposure(:awesome_thing)
|
|
992
|
-
expect(exposure.conditions[0].inversed?).to be true
|
|
993
|
-
expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
|
|
994
|
-
end
|
|
995
|
-
|
|
996
|
-
it 'overrides nested :expose_nil option' do
|
|
997
|
-
subject.class_eval do
|
|
998
|
-
with_options(expose_nil: true) do
|
|
999
|
-
expose :awesome_thing, expose_nil: false
|
|
1000
|
-
expose :other_awesome_thing
|
|
1001
|
-
end
|
|
1002
|
-
end
|
|
1003
|
-
|
|
1004
|
-
exposure = subject.find_exposure(:awesome_thing)
|
|
1005
|
-
expect(exposure.conditions[0].inversed?).to be true
|
|
1006
|
-
expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
|
|
1007
|
-
# Conditions are only added for exposures that do not expose nil
|
|
1008
|
-
exposure = subject.find_exposure(:other_awesome_thing)
|
|
1009
|
-
expect(exposure.conditions[0]).to be_nil
|
|
1010
|
-
end
|
|
1011
|
-
end
|
|
1012
|
-
|
|
1013
|
-
describe '.[]' do
|
|
1014
|
-
it 'returns the input unchanged' do
|
|
1015
|
-
hash = { name: 'Test' }
|
|
1016
|
-
expect(subject[hash]).to eq(hash)
|
|
1017
|
-
end
|
|
1018
|
-
|
|
1019
|
-
it 'returns nil unchanged' do
|
|
1020
|
-
expect(subject[nil]).to be_nil
|
|
1021
|
-
end
|
|
1022
|
-
|
|
1023
|
-
it 'is inherited by subclasses' do
|
|
1024
|
-
subclass = Class.new(subject)
|
|
1025
|
-
expect(subclass[{ id: 1 }]).to eq(id: 1)
|
|
1026
|
-
end
|
|
1027
|
-
end
|
|
1028
|
-
|
|
1029
|
-
describe '.represent' do
|
|
1030
|
-
it 'returns a single entity if called with one object' do
|
|
1031
|
-
expect(subject.represent(Object.new)).to be_kind_of(subject)
|
|
1032
|
-
end
|
|
1033
|
-
|
|
1034
|
-
it 'returns a single entity if called with a hash' do
|
|
1035
|
-
expect(subject.represent({})).to be_kind_of(subject)
|
|
1036
|
-
end
|
|
1037
|
-
|
|
1038
|
-
it 'returns multiple entities if called with a collection' do
|
|
1039
|
-
representation = subject.represent(Array.new(4) { Object.new })
|
|
1040
|
-
expect(representation).to be_kind_of Array
|
|
1041
|
-
expect(representation.size).to eq(4)
|
|
1042
|
-
expect(representation.grep_v(subject)).to be_empty
|
|
1043
|
-
end
|
|
1044
|
-
|
|
1045
|
-
it 'adds the collection: true option if called with a collection' do
|
|
1046
|
-
representation = subject.represent(Array.new(4) { Object.new })
|
|
1047
|
-
representation.each { |r| expect(r.options[:collection]).to be true }
|
|
1048
|
-
end
|
|
1049
|
-
|
|
1050
|
-
it 'returns a serialized hash of a single object if serializable: true' do
|
|
1051
|
-
subject.expose(:awesome) { |_| true }
|
|
1052
|
-
representation = subject.represent(Object.new, serializable: true)
|
|
1053
|
-
expect(representation).to eq(awesome: true)
|
|
1054
|
-
end
|
|
1055
|
-
|
|
1056
|
-
it 'returns a serialized array of hashes of multiple objects if serializable: true' do
|
|
1057
|
-
subject.expose(:awesome) { |_| true }
|
|
1058
|
-
representation = subject.represent(Array.new(2) { Object.new }, serializable: true)
|
|
1059
|
-
expect(representation).to eq([{ awesome: true }, { awesome: true }])
|
|
1060
|
-
end
|
|
1061
|
-
|
|
1062
|
-
it 'returns a serialized hash of a hash' do
|
|
1063
|
-
subject.expose(:awesome)
|
|
1064
|
-
representation = subject.represent({ awesome: true }, serializable: true)
|
|
1065
|
-
expect(representation).to eq(awesome: true)
|
|
1066
|
-
end
|
|
1067
|
-
|
|
1068
|
-
it 'returns a serialized hash of an OpenStruct' do
|
|
1069
|
-
subject.expose(:awesome)
|
|
1070
|
-
representation = subject.represent(OpenStruct.new, serializable: true)
|
|
1071
|
-
expect(representation).to eq(awesome: nil)
|
|
1072
|
-
end
|
|
1073
|
-
|
|
1074
|
-
it 'raises error if field not found' do
|
|
1075
|
-
subject.expose(:awesome)
|
|
1076
|
-
expect do
|
|
1077
|
-
subject.represent(Object.new, serializable: true)
|
|
1078
|
-
end.to raise_error(NoMethodError, /missing attribute `awesome'/)
|
|
1079
|
-
end
|
|
1080
|
-
|
|
1081
|
-
context 'with specified fields' do
|
|
1082
|
-
it 'returns only specified fields with only option' do
|
|
1083
|
-
subject.expose(:id, :name, :phone)
|
|
1084
|
-
representation = subject.represent(OpenStruct.new, only: %i[id name], serializable: true)
|
|
1085
|
-
expect(representation).to eq(id: nil, name: nil)
|
|
1086
|
-
end
|
|
1087
|
-
|
|
1088
|
-
it 'returns all fields except the ones specified in the except option' do
|
|
1089
|
-
subject.expose(:id, :name, :phone)
|
|
1090
|
-
representation = subject.represent(OpenStruct.new, except: [:phone], serializable: true)
|
|
1091
|
-
expect(representation).to eq(id: nil, name: nil)
|
|
1092
|
-
end
|
|
1093
|
-
|
|
1094
|
-
it 'returns only fields specified in the only option and not specified in the except option' do
|
|
1095
|
-
subject.expose(:id, :name, :phone)
|
|
1096
|
-
representation = subject.represent(OpenStruct.new,
|
|
1097
|
-
only: %i[name phone],
|
|
1098
|
-
except: [:phone], serializable: true)
|
|
1099
|
-
expect(representation).to eq(name: nil)
|
|
1100
|
-
end
|
|
1101
|
-
|
|
1102
|
-
context 'with strings or symbols passed to only and except' do
|
|
1103
|
-
let(:object) { OpenStruct.new(user: {}) }
|
|
1104
|
-
|
|
1105
|
-
before do
|
|
1106
|
-
user_entity = Class.new(Grape::Entity)
|
|
1107
|
-
user_entity.expose(:id, :name, :email)
|
|
1108
|
-
|
|
1109
|
-
subject.expose(:id, :name, :phone, :address)
|
|
1110
|
-
subject.expose(:user, using: user_entity)
|
|
1111
|
-
end
|
|
1112
|
-
|
|
1113
|
-
it 'can specify "only" option attributes as strings' do
|
|
1114
|
-
representation = subject.represent(object, only: ['id', 'name', { 'user' => ['email'] }], serializable: true)
|
|
1115
|
-
expect(representation).to eq(id: nil, name: nil, user: { email: nil })
|
|
1116
|
-
end
|
|
1117
|
-
|
|
1118
|
-
it 'can specify "except" option attributes as strings' do
|
|
1119
|
-
representation = subject.represent(object, except: ['id', 'name', { 'user' => ['email'] }], serializable: true)
|
|
1120
|
-
expect(representation).to eq(phone: nil, address: nil, user: { id: nil, name: nil })
|
|
1121
|
-
end
|
|
1122
|
-
|
|
1123
|
-
it 'can specify "only" option attributes as symbols' do
|
|
1124
|
-
representation = subject.represent(object, only: [:name, :phone, { user: [:name] }], serializable: true)
|
|
1125
|
-
expect(representation).to eq(name: nil, phone: nil, user: { name: nil })
|
|
1126
|
-
end
|
|
1127
|
-
|
|
1128
|
-
it 'can specify "except" option attributes as symbols' do
|
|
1129
|
-
representation = subject.represent(object, except: [:name, :phone, { user: [:name] }], serializable: true)
|
|
1130
|
-
expect(representation).to eq(id: nil, address: nil, user: { id: nil, email: nil })
|
|
1131
|
-
end
|
|
1132
|
-
|
|
1133
|
-
it 'can specify "only" attributes as strings and symbols' do
|
|
1134
|
-
representation = subject.represent(object, only: [:id, 'address', { user: [:id, 'name'] }], serializable: true)
|
|
1135
|
-
expect(representation).to eq(id: nil, address: nil, user: { id: nil, name: nil })
|
|
1136
|
-
end
|
|
1137
|
-
|
|
1138
|
-
it 'can specify "except" attributes as strings and symbols' do
|
|
1139
|
-
representation = subject.represent(object, except: [:id, 'address', { user: [:id, 'name'] }], serializable: true)
|
|
1140
|
-
expect(representation).to eq(name: nil, phone: nil, user: { email: nil })
|
|
1141
|
-
end
|
|
1142
|
-
|
|
1143
|
-
context 'with nested attributes' do
|
|
1144
|
-
before do
|
|
1145
|
-
subject.expose :additional do
|
|
1146
|
-
subject.expose :something
|
|
1147
|
-
end
|
|
1148
|
-
end
|
|
1149
|
-
|
|
1150
|
-
it 'preserves nesting' do
|
|
1151
|
-
expect(subject.represent({ something: 123 }, only: [{ additional: [:something] }], serializable: true)).to eq(
|
|
1152
|
-
additional: {
|
|
1153
|
-
something: 123
|
|
1154
|
-
}
|
|
1155
|
-
)
|
|
1156
|
-
end
|
|
1157
|
-
end
|
|
1158
|
-
end
|
|
1159
|
-
|
|
1160
|
-
it 'can specify children attributes with only' do
|
|
1161
|
-
user_entity = Class.new(Grape::Entity)
|
|
1162
|
-
user_entity.expose(:id, :name, :email)
|
|
1163
|
-
|
|
1164
|
-
subject.expose(:id, :name, :phone)
|
|
1165
|
-
subject.expose(:user, using: user_entity)
|
|
1166
|
-
|
|
1167
|
-
representation = subject.represent(OpenStruct.new(user: {}), only: [:id, :name, { user: %i[name email] }], serializable: true)
|
|
1168
|
-
expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
|
|
1169
|
-
end
|
|
1170
|
-
|
|
1171
|
-
it 'can specify children attributes with except' do
|
|
1172
|
-
user_entity = Class.new(Grape::Entity)
|
|
1173
|
-
user_entity.expose(:id, :name, :email)
|
|
1174
|
-
|
|
1175
|
-
subject.expose(:id, :name, :phone)
|
|
1176
|
-
subject.expose(:user, using: user_entity)
|
|
1177
|
-
|
|
1178
|
-
representation = subject.represent(OpenStruct.new(user: {}), except: [:phone, { user: [:id] }], serializable: true)
|
|
1179
|
-
expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
|
|
1180
|
-
end
|
|
1181
|
-
|
|
1182
|
-
it 'can specify children attributes with mixed only and except' do
|
|
1183
|
-
user_entity = Class.new(Grape::Entity)
|
|
1184
|
-
user_entity.expose(:id, :name, :email, :address)
|
|
1185
|
-
|
|
1186
|
-
subject.expose(:id, :name, :phone, :mobile_phone)
|
|
1187
|
-
subject.expose(:user, using: user_entity)
|
|
1188
|
-
|
|
1189
|
-
representation = subject.represent(OpenStruct.new(user: {}),
|
|
1190
|
-
only: [:id, :name, :phone, { user: %i[id name email] }],
|
|
1191
|
-
except: [:phone, { user: [:id] }], serializable: true)
|
|
1192
|
-
expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
|
|
1193
|
-
end
|
|
1194
|
-
|
|
1195
|
-
context 'specify attribute with exposure condition' do
|
|
1196
|
-
it 'returns only specified fields' do
|
|
1197
|
-
subject.expose(:id)
|
|
1198
|
-
subject.with_options(if: { condition: true }) do
|
|
1199
|
-
subject.expose(:name)
|
|
1200
|
-
end
|
|
1201
|
-
|
|
1202
|
-
representation = subject.represent(OpenStruct.new, condition: true, only: %i[id name], serializable: true)
|
|
1203
|
-
expect(representation).to eq(id: nil, name: nil)
|
|
1204
|
-
end
|
|
1205
|
-
|
|
1206
|
-
it 'does not return fields specified in the except option' do
|
|
1207
|
-
subject.expose(:id, :phone)
|
|
1208
|
-
subject.with_options(if: { condition: true }) do
|
|
1209
|
-
subject.expose(:name, :mobile_phone)
|
|
1210
|
-
end
|
|
1211
|
-
|
|
1212
|
-
representation = subject.represent(OpenStruct.new, condition: true, except: %i[phone mobile_phone], serializable: true)
|
|
1213
|
-
expect(representation).to eq(id: nil, name: nil)
|
|
1214
|
-
end
|
|
1215
|
-
|
|
1216
|
-
it 'choses proper exposure according to condition' do
|
|
1217
|
-
strategy1 = ->(_obj, _opts) { 'foo' }
|
|
1218
|
-
strategy2 = ->(_obj, _opts) { 'bar' }
|
|
1219
|
-
|
|
1220
|
-
subject.expose :id, proc: strategy1
|
|
1221
|
-
subject.expose :id, proc: strategy2
|
|
1222
|
-
expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'bar')
|
|
1223
|
-
expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
|
|
1224
|
-
|
|
1225
|
-
subject.unexpose_all
|
|
1226
|
-
|
|
1227
|
-
subject.expose :id, proc: strategy1, if: :condition
|
|
1228
|
-
subject.expose :id, proc: strategy2
|
|
1229
|
-
expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'bar')
|
|
1230
|
-
expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
|
|
1231
|
-
|
|
1232
|
-
subject.unexpose_all
|
|
1233
|
-
|
|
1234
|
-
subject.expose :id, proc: strategy1
|
|
1235
|
-
subject.expose :id, proc: strategy2, if: :condition
|
|
1236
|
-
expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'foo')
|
|
1237
|
-
expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
|
|
1238
|
-
|
|
1239
|
-
subject.unexpose_all
|
|
1240
|
-
|
|
1241
|
-
subject.expose :id, proc: strategy1, if: :condition1
|
|
1242
|
-
subject.expose :id, proc: strategy2, if: :condition2
|
|
1243
|
-
expect(subject.represent({}, condition1: false, condition2: false, serializable: true)).to eq({})
|
|
1244
|
-
expect(subject.represent({}, condition1: false, condition2: true, serializable: true)).to eq(id: 'bar')
|
|
1245
|
-
expect(subject.represent({}, condition1: true, condition2: false, serializable: true)).to eq(id: 'foo')
|
|
1246
|
-
expect(subject.represent({}, condition1: true, condition2: true, serializable: true)).to eq(id: 'bar')
|
|
1247
|
-
end
|
|
1248
|
-
|
|
1249
|
-
it 'does not merge nested exposures with plain hashes' do
|
|
1250
|
-
subject.expose(:id)
|
|
1251
|
-
subject.expose(:info, if: :condition1) do
|
|
1252
|
-
subject.expose :a, :b
|
|
1253
|
-
subject.expose(:additional, if: :condition2) do |_obj, _opts|
|
|
1254
|
-
{
|
|
1255
|
-
x: 11, y: 22, c: 123
|
|
1256
|
-
}
|
|
1257
|
-
end
|
|
1258
|
-
end
|
|
1259
|
-
subject.expose(:info, if: :condition2) do
|
|
1260
|
-
subject.expose(:additional) do
|
|
1261
|
-
subject.expose :c
|
|
1262
|
-
end
|
|
1263
|
-
end
|
|
1264
|
-
subject.expose(:d, as: :info, if: :condition3)
|
|
1265
|
-
|
|
1266
|
-
obj = { id: 123, a: 1, b: 2, c: 3, d: 4 }
|
|
1267
|
-
|
|
1268
|
-
expect(subject.represent(obj, serializable: true)).to eq(id: 123)
|
|
1269
|
-
expect(subject.represent(obj, condition1: true, serializable: true)).to eq(id: 123, info: { a: 1, b: 2 })
|
|
1270
|
-
expect(subject.represent(obj, condition2: true, serializable: true)).to eq(
|
|
1271
|
-
id: 123,
|
|
1272
|
-
info: {
|
|
1273
|
-
additional: {
|
|
1274
|
-
c: 3
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
)
|
|
1278
|
-
expect(subject.represent(obj, condition1: true, condition2: true, serializable: true)).to eq(
|
|
1279
|
-
id: 123,
|
|
1280
|
-
info: {
|
|
1281
|
-
a: 1, b: 2, additional: { c: 3 }
|
|
1282
|
-
}
|
|
1283
|
-
)
|
|
1284
|
-
expect(subject.represent(obj, condition3: true, serializable: true)).to eq(id: 123, info: 4)
|
|
1285
|
-
expect(subject.represent(obj, condition1: true, condition2: true, condition3: true, serializable: true)).to eq(id: 123, info: 4)
|
|
1286
|
-
end
|
|
1287
|
-
end
|
|
1288
|
-
|
|
1289
|
-
context 'attribute with alias' do
|
|
1290
|
-
it 'returns only specified fields' do
|
|
1291
|
-
subject.expose(:id)
|
|
1292
|
-
subject.expose(:name, as: :title)
|
|
1293
|
-
|
|
1294
|
-
representation = subject.represent(OpenStruct.new, condition: true, only: %i[id title], serializable: true)
|
|
1295
|
-
expect(representation).to eq(id: nil, title: nil)
|
|
1296
|
-
end
|
|
1297
|
-
|
|
1298
|
-
it 'does not return fields specified in the except option' do
|
|
1299
|
-
subject.expose(:id)
|
|
1300
|
-
subject.expose(:name, as: :title)
|
|
1301
|
-
subject.expose(:phone, as: :phone_number)
|
|
1302
|
-
|
|
1303
|
-
representation = subject.represent(OpenStruct.new, condition: true, except: [:phone_number], serializable: true)
|
|
1304
|
-
expect(representation).to eq(id: nil, title: nil)
|
|
1305
|
-
end
|
|
1306
|
-
end
|
|
1307
|
-
|
|
1308
|
-
context 'attribute that is an entity itself' do
|
|
1309
|
-
it 'returns correctly the children entity attributes' do
|
|
1310
|
-
user_entity = Class.new(Grape::Entity)
|
|
1311
|
-
user_entity.expose(:id, :name, :email)
|
|
1312
|
-
|
|
1313
|
-
nephew_entity = Class.new(Grape::Entity)
|
|
1314
|
-
nephew_entity.expose(:id, :name, :email)
|
|
1315
|
-
|
|
1316
|
-
subject.expose(:id, :name, :phone)
|
|
1317
|
-
subject.expose(:user, using: user_entity)
|
|
1318
|
-
subject.expose(:nephew, using: nephew_entity)
|
|
1319
|
-
|
|
1320
|
-
representation = subject.represent(OpenStruct.new(user: {}),
|
|
1321
|
-
only: %i[id name user], except: [:nephew], serializable: true)
|
|
1322
|
-
expect(representation).to eq(id: nil, name: nil, user: { id: nil, name: nil, email: nil })
|
|
1323
|
-
end
|
|
1324
|
-
end
|
|
1325
|
-
|
|
1326
|
-
context 'when NameError happens in a parameterized block_exposure' do
|
|
1327
|
-
before do
|
|
1328
|
-
subject.expose :raise_no_method_error do |_|
|
|
1329
|
-
foo
|
|
1330
|
-
end
|
|
1331
|
-
end
|
|
1332
|
-
|
|
1333
|
-
it 'does not cause infinite loop' do
|
|
1334
|
-
expect { subject.represent({}, serializable: true) }.to raise_error(NameError)
|
|
1335
|
-
end
|
|
1336
|
-
end
|
|
1337
|
-
end
|
|
1338
|
-
end
|
|
1339
|
-
|
|
1340
|
-
describe '.present_collection' do
|
|
1341
|
-
it 'make the objects accessible' do
|
|
1342
|
-
subject.present_collection true
|
|
1343
|
-
subject.expose :items
|
|
1344
|
-
|
|
1345
|
-
representation = subject.represent(Array.new(4) { Object.new })
|
|
1346
|
-
expect(representation).to be_kind_of(subject)
|
|
1347
|
-
expect(representation.object).to be_kind_of(Hash)
|
|
1348
|
-
expect(representation.object).to have_key :items
|
|
1349
|
-
expect(representation.object[:items]).to be_kind_of Array
|
|
1350
|
-
expect(representation.object[:items].size).to be 4
|
|
1351
|
-
end
|
|
1352
|
-
|
|
1353
|
-
it 'serializes items with my root name' do
|
|
1354
|
-
subject.present_collection true, :my_items
|
|
1355
|
-
subject.expose :my_items
|
|
1356
|
-
|
|
1357
|
-
representation = subject.represent(Array.new(4) { Object.new }, serializable: true)
|
|
1358
|
-
expect(representation).to be_kind_of(Grape::Entity::Exposure::NestingExposure::OutputBuilder)
|
|
1359
|
-
expect(representation).to be_kind_of(Hash)
|
|
1360
|
-
expect(representation).to have_key :my_items
|
|
1361
|
-
expect(representation[:my_items]).to be_kind_of Array
|
|
1362
|
-
expect(representation[:my_items].size).to be 4
|
|
1363
|
-
end
|
|
1364
|
-
end
|
|
1365
|
-
|
|
1366
|
-
describe '.root' do
|
|
1367
|
-
context 'with singular and plural root keys' do
|
|
1368
|
-
before(:each) do
|
|
1369
|
-
subject.root 'things', 'thing'
|
|
1370
|
-
end
|
|
1371
|
-
|
|
1372
|
-
context 'with a single object' do
|
|
1373
|
-
it 'allows a root element name to be specified' do
|
|
1374
|
-
representation = subject.represent(Object.new)
|
|
1375
|
-
expect(representation).to be_kind_of Hash
|
|
1376
|
-
expect(representation).to have_key 'thing'
|
|
1377
|
-
expect(representation['thing']).to be_kind_of(subject)
|
|
1378
|
-
end
|
|
1379
|
-
end
|
|
1380
|
-
|
|
1381
|
-
context 'with an array of objects' do
|
|
1382
|
-
it 'allows a root element name to be specified' do
|
|
1383
|
-
representation = subject.represent(Array.new(4) { Object.new })
|
|
1384
|
-
expect(representation).to be_kind_of Hash
|
|
1385
|
-
expect(representation).to have_key 'things'
|
|
1386
|
-
expect(representation['things']).to be_kind_of Array
|
|
1387
|
-
expect(representation['things'].size).to eq 4
|
|
1388
|
-
expect(representation['things'].grep_v(subject)).to be_empty
|
|
1389
|
-
end
|
|
1390
|
-
end
|
|
1391
|
-
|
|
1392
|
-
context 'it can be overridden' do
|
|
1393
|
-
it 'can be disabled' do
|
|
1394
|
-
representation = subject.represent(Array.new(4) { Object.new }, root: false)
|
|
1395
|
-
expect(representation).to be_kind_of Array
|
|
1396
|
-
expect(representation.size).to eq 4
|
|
1397
|
-
expect(representation.grep_v(subject)).to be_empty
|
|
1398
|
-
end
|
|
1399
|
-
it 'can use a different name' do
|
|
1400
|
-
representation = subject.represent(Array.new(4) { Object.new }, root: 'others')
|
|
1401
|
-
expect(representation).to be_kind_of Hash
|
|
1402
|
-
expect(representation).to have_key 'others'
|
|
1403
|
-
expect(representation['others']).to be_kind_of Array
|
|
1404
|
-
expect(representation['others'].size).to eq 4
|
|
1405
|
-
expect(representation['others'].grep_v(subject)).to be_empty
|
|
1406
|
-
end
|
|
1407
|
-
end
|
|
1408
|
-
end
|
|
1409
|
-
|
|
1410
|
-
context 'with singular root key' do
|
|
1411
|
-
before(:each) do
|
|
1412
|
-
subject.root nil, 'thing'
|
|
1413
|
-
end
|
|
1414
|
-
|
|
1415
|
-
context 'with a single object' do
|
|
1416
|
-
it 'allows a root element name to be specified' do
|
|
1417
|
-
representation = subject.represent(Object.new)
|
|
1418
|
-
expect(representation).to be_kind_of Hash
|
|
1419
|
-
expect(representation).to have_key 'thing'
|
|
1420
|
-
expect(representation['thing']).to be_kind_of(subject)
|
|
1421
|
-
end
|
|
1422
|
-
end
|
|
1423
|
-
|
|
1424
|
-
context 'with an array of objects' do
|
|
1425
|
-
it 'allows a root element name to be specified' do
|
|
1426
|
-
representation = subject.represent(Array.new(4) { Object.new })
|
|
1427
|
-
expect(representation).to be_kind_of Array
|
|
1428
|
-
expect(representation.size).to eq 4
|
|
1429
|
-
expect(representation.grep_v(subject)).to be_empty
|
|
1430
|
-
end
|
|
1431
|
-
end
|
|
1432
|
-
end
|
|
1433
|
-
|
|
1434
|
-
context 'with plural root key' do
|
|
1435
|
-
before(:each) do
|
|
1436
|
-
subject.root 'things'
|
|
1437
|
-
end
|
|
1438
|
-
|
|
1439
|
-
context 'with a single object' do
|
|
1440
|
-
it 'allows a root element name to be specified' do
|
|
1441
|
-
expect(subject.represent(Object.new)).to be_kind_of(subject)
|
|
1442
|
-
end
|
|
1443
|
-
end
|
|
1444
|
-
|
|
1445
|
-
context 'with an array of objects' do
|
|
1446
|
-
it 'allows a root element name to be specified' do
|
|
1447
|
-
representation = subject.represent(Array.new(4) { Object.new })
|
|
1448
|
-
expect(representation).to be_kind_of Hash
|
|
1449
|
-
expect(representation).to have_key('things')
|
|
1450
|
-
expect(representation['things']).to be_kind_of Array
|
|
1451
|
-
expect(representation['things'].size).to eq 4
|
|
1452
|
-
expect(representation['things'].grep_v(subject)).to be_empty
|
|
1453
|
-
end
|
|
1454
|
-
end
|
|
1455
|
-
end
|
|
1456
|
-
|
|
1457
|
-
context 'inheriting from parent entity' do
|
|
1458
|
-
before(:each) do
|
|
1459
|
-
subject.root 'things', 'thing'
|
|
1460
|
-
end
|
|
1461
|
-
|
|
1462
|
-
it 'inherits single root' do
|
|
1463
|
-
child_class = Class.new(subject)
|
|
1464
|
-
representation = child_class.represent(Object.new)
|
|
1465
|
-
expect(representation).to be_kind_of Hash
|
|
1466
|
-
expect(representation).to have_key 'thing'
|
|
1467
|
-
expect(representation['thing']).to be_kind_of(child_class)
|
|
1468
|
-
end
|
|
1469
|
-
|
|
1470
|
-
it 'inherits array root root' do
|
|
1471
|
-
child_class = Class.new(subject)
|
|
1472
|
-
representation = child_class.represent(Array.new(4) { Object.new })
|
|
1473
|
-
expect(representation).to be_kind_of Hash
|
|
1474
|
-
expect(representation).to have_key('things')
|
|
1475
|
-
expect(representation['things']).to be_kind_of Array
|
|
1476
|
-
expect(representation['things'].size).to eq 4
|
|
1477
|
-
expect(representation['things'].grep_v(child_class)).to be_empty
|
|
1478
|
-
end
|
|
1479
|
-
end
|
|
1480
|
-
end
|
|
1481
|
-
|
|
1482
|
-
describe '#initialize' do
|
|
1483
|
-
it 'takes an object and an optional options hash' do
|
|
1484
|
-
expect { subject.new(Object.new) }.not_to raise_error
|
|
1485
|
-
expect { subject.new }.to raise_error ArgumentError
|
|
1486
|
-
expect { subject.new(Object.new, {}) }.not_to raise_error
|
|
1487
|
-
end
|
|
1488
|
-
|
|
1489
|
-
it 'has attribute readers for the object and options' do
|
|
1490
|
-
entity = subject.new('abc', {})
|
|
1491
|
-
expect(entity.object).to eq 'abc'
|
|
1492
|
-
expect(entity.options).to eq({})
|
|
1493
|
-
end
|
|
1494
|
-
end
|
|
1495
|
-
end
|
|
1496
|
-
|
|
1497
|
-
context 'instance methods' do
|
|
1498
|
-
let(:model) { double(attributes) }
|
|
1499
|
-
|
|
1500
|
-
let(:attributes) do
|
|
1501
|
-
{
|
|
1502
|
-
name: 'Bob Bobson',
|
|
1503
|
-
email: 'bob@example.com',
|
|
1504
|
-
birthday: Time.gm(2012, 2, 27),
|
|
1505
|
-
fantasies: ['Unicorns', 'Double Rainbows', 'Nessy'],
|
|
1506
|
-
characteristics: [
|
|
1507
|
-
{ key: 'hair_color', value: 'brown' }
|
|
1508
|
-
],
|
|
1509
|
-
friends: [
|
|
1510
|
-
double(name: 'Friend 1', email: 'friend1@example.com', characteristics: [], fantasies: [], birthday: Time.gm(2012, 2, 27), friends: []),
|
|
1511
|
-
double(name: 'Friend 2', email: 'friend2@example.com', characteristics: [], fantasies: [], birthday: Time.gm(2012, 2, 27), friends: [])
|
|
1512
|
-
],
|
|
1513
|
-
extra: { key: 'foo', value: 'bar' },
|
|
1514
|
-
nested: [
|
|
1515
|
-
{ name: 'n1', data: { key: 'ex1', value: 'v1' } },
|
|
1516
|
-
{ name: 'n2', data: { key: 'ex2', value: 'v2' } }
|
|
1517
|
-
]
|
|
1518
|
-
}
|
|
1519
|
-
end
|
|
1520
|
-
|
|
1521
|
-
subject { fresh_class.new(model) }
|
|
1522
|
-
|
|
1523
|
-
describe '#serializable_hash' do
|
|
1524
|
-
it 'does not throw an exception if a nil options object is passed' do
|
|
1525
|
-
expect { fresh_class.new(model).serializable_hash(nil) }.not_to raise_error
|
|
1526
|
-
end
|
|
1527
|
-
|
|
1528
|
-
it 'does not blow up when the model is nil' do
|
|
1529
|
-
fresh_class.expose :name
|
|
1530
|
-
expect { fresh_class.new(nil).serializable_hash }.not_to raise_error
|
|
1531
|
-
end
|
|
1532
|
-
|
|
1533
|
-
context 'with safe option' do
|
|
1534
|
-
it 'does not throw an exception when an attribute is not found on the object' do
|
|
1535
|
-
fresh_class.expose :name, :nonexistent_attribute, safe: true
|
|
1536
|
-
expect { fresh_class.new(model).serializable_hash }.not_to raise_error
|
|
1537
|
-
end
|
|
1538
|
-
|
|
1539
|
-
it 'exposes values of private method calls' do
|
|
1540
|
-
some_class = Class.new do
|
|
1541
|
-
define_method :name do
|
|
1542
|
-
true
|
|
1543
|
-
end
|
|
1544
|
-
private :name
|
|
1545
|
-
end
|
|
1546
|
-
fresh_class.expose :name, safe: true
|
|
1547
|
-
expect(fresh_class.new(some_class.new).serializable_hash).to eq(name: true)
|
|
1548
|
-
end
|
|
1549
|
-
|
|
1550
|
-
it "does expose attributes that don't exist on the object" do
|
|
1551
|
-
fresh_class.expose :email, :nonexistent_attribute, :name, safe: true
|
|
1552
|
-
|
|
1553
|
-
res = fresh_class.new(model).serializable_hash
|
|
1554
|
-
expect(res).to have_key :email
|
|
1555
|
-
expect(res).to have_key :nonexistent_attribute
|
|
1556
|
-
expect(res).to have_key :name
|
|
1557
|
-
end
|
|
1558
|
-
|
|
1559
|
-
it "does expose attributes that don't exist on the object as nil" do
|
|
1560
|
-
fresh_class.expose :email, :nonexistent_attribute, :name, safe: true
|
|
1561
|
-
|
|
1562
|
-
res = fresh_class.new(model).serializable_hash
|
|
1563
|
-
expect(res[:nonexistent_attribute]).to eq(nil)
|
|
1564
|
-
end
|
|
1565
|
-
|
|
1566
|
-
it 'does expose attributes marked as safe if model is a hash object' do
|
|
1567
|
-
fresh_class.expose :name, safe: true
|
|
1568
|
-
|
|
1569
|
-
res = fresh_class.new(name: 'myname').serializable_hash
|
|
1570
|
-
expect(res).to have_key :name
|
|
1571
|
-
end
|
|
1572
|
-
|
|
1573
|
-
it "does expose attributes that don't exist on the object as nil if criteria is true" do
|
|
1574
|
-
fresh_class.expose :email
|
|
1575
|
-
fresh_class.expose :nonexistent_attribute, safe: true, if: ->(_obj, _opts) { false }
|
|
1576
|
-
fresh_class.expose :nonexistent_attribute2, safe: true, if: ->(_obj, _opts) { true }
|
|
1577
|
-
|
|
1578
|
-
res = fresh_class.new(model).serializable_hash
|
|
1579
|
-
expect(res).to have_key :email
|
|
1580
|
-
expect(res).not_to have_key :nonexistent_attribute
|
|
1581
|
-
expect(res).to have_key :nonexistent_attribute2
|
|
1582
|
-
end
|
|
1583
|
-
end
|
|
1584
|
-
|
|
1585
|
-
context 'without safe option' do
|
|
1586
|
-
it 'throws an exception when an attribute is not found on the object' do
|
|
1587
|
-
fresh_class.expose :name, :nonexistent_attribute
|
|
1588
|
-
expect { fresh_class.new(model).serializable_hash }.to raise_error NoMethodError
|
|
1589
|
-
end
|
|
1590
|
-
|
|
1591
|
-
it "exposes attributes that don't exist on the object only when they are generated by a block" do
|
|
1592
|
-
fresh_class.expose :nonexistent_attribute do |_model, _opts|
|
|
1593
|
-
'well, I do exist after all'
|
|
1594
|
-
end
|
|
1595
|
-
res = fresh_class.new(model).serializable_hash
|
|
1596
|
-
expect(res).to have_key :nonexistent_attribute
|
|
1597
|
-
end
|
|
1598
|
-
|
|
1599
|
-
it 'does not expose attributes that are generated by a block but have not passed criteria' do
|
|
1600
|
-
fresh_class.expose :nonexistent_attribute,
|
|
1601
|
-
proc: ->(_model, _opts) { 'I exist, but it is not yet my time to shine' },
|
|
1602
|
-
if: ->(_model, _opts) { false }
|
|
1603
|
-
res = fresh_class.new(model).serializable_hash
|
|
1604
|
-
expect(res).not_to have_key :nonexistent_attribute
|
|
1605
|
-
end
|
|
1606
|
-
end
|
|
1607
|
-
|
|
1608
|
-
it "exposes attributes that don't exist on the object only when they are generated by a block with options" do
|
|
1609
|
-
module EntitySpec
|
|
1610
|
-
class TestEntity < Grape::Entity
|
|
1611
|
-
end
|
|
1612
|
-
end
|
|
1613
|
-
|
|
1614
|
-
fresh_class.expose :nonexistent_attribute, using: EntitySpec::TestEntity do |_model, _opts|
|
|
1615
|
-
'well, I do exist after all'
|
|
1616
|
-
end
|
|
1617
|
-
res = fresh_class.new(model).serializable_hash
|
|
1618
|
-
expect(res).to have_key :nonexistent_attribute
|
|
1619
|
-
end
|
|
1620
|
-
|
|
1621
|
-
it 'exposes attributes defined through module inclusion' do
|
|
1622
|
-
module SharedAttributes
|
|
1623
|
-
def a_value
|
|
1624
|
-
3.14
|
|
1625
|
-
end
|
|
1626
|
-
end
|
|
1627
|
-
fresh_class.include(SharedAttributes)
|
|
1628
|
-
fresh_class.expose :a_value
|
|
1629
|
-
res = fresh_class.new(model).serializable_hash
|
|
1630
|
-
expect(res[:a_value]).to eq(3.14)
|
|
1631
|
-
end
|
|
1632
|
-
|
|
1633
|
-
it 'does not expose attributes that are generated by a block but have not passed criteria' do
|
|
1634
|
-
fresh_class.expose :nonexistent_attribute,
|
|
1635
|
-
proc: ->(_, _) { 'I exist, but it is not yet my time to shine' },
|
|
1636
|
-
if: ->(_, _) { false }
|
|
1637
|
-
res = fresh_class.new(model).serializable_hash
|
|
1638
|
-
expect(res).not_to have_key :nonexistent_attribute
|
|
1639
|
-
end
|
|
1640
|
-
|
|
1641
|
-
context '#serializable_hash' do
|
|
1642
|
-
module EntitySpec
|
|
1643
|
-
class EmbeddedExample
|
|
1644
|
-
def serializable_hash(_opts = {})
|
|
1645
|
-
{ abc: 'def' }
|
|
1646
|
-
end
|
|
1647
|
-
end
|
|
1648
|
-
|
|
1649
|
-
class EmbeddedExampleWithHash
|
|
1650
|
-
def name
|
|
1651
|
-
'abc'
|
|
1652
|
-
end
|
|
1653
|
-
|
|
1654
|
-
def embedded
|
|
1655
|
-
{ a: nil, b: EmbeddedExample.new }
|
|
1656
|
-
end
|
|
1657
|
-
end
|
|
1658
|
-
|
|
1659
|
-
class EmbeddedExampleWithMany
|
|
1660
|
-
def name
|
|
1661
|
-
'abc'
|
|
1662
|
-
end
|
|
1663
|
-
|
|
1664
|
-
def embedded
|
|
1665
|
-
[EmbeddedExample.new, EmbeddedExample.new]
|
|
1666
|
-
end
|
|
1667
|
-
end
|
|
1668
|
-
|
|
1669
|
-
class EmbeddedExampleWithOne
|
|
1670
|
-
def name
|
|
1671
|
-
'abc'
|
|
1672
|
-
end
|
|
1673
|
-
|
|
1674
|
-
def embedded
|
|
1675
|
-
EmbeddedExample.new
|
|
1676
|
-
end
|
|
1677
|
-
end
|
|
1678
|
-
end
|
|
1679
|
-
|
|
1680
|
-
it 'serializes embedded objects which respond to #serializable_hash' do
|
|
1681
|
-
fresh_class.expose :name, :embedded
|
|
1682
|
-
presenter = fresh_class.new(EntitySpec::EmbeddedExampleWithOne.new)
|
|
1683
|
-
expect(presenter.serializable_hash).to eq(name: 'abc', embedded: { abc: 'def' })
|
|
1684
|
-
end
|
|
1685
|
-
|
|
1686
|
-
it 'serializes embedded arrays of objects which respond to #serializable_hash' do
|
|
1687
|
-
fresh_class.expose :name, :embedded
|
|
1688
|
-
presenter = fresh_class.new(EntitySpec::EmbeddedExampleWithMany.new)
|
|
1689
|
-
expect(presenter.serializable_hash).to eq(name: 'abc', embedded: [{ abc: 'def' }, { abc: 'def' }])
|
|
1690
|
-
end
|
|
1691
|
-
|
|
1692
|
-
it 'serializes embedded hashes of objects which respond to #serializable_hash' do
|
|
1693
|
-
fresh_class.expose :name, :embedded
|
|
1694
|
-
presenter = fresh_class.new(EntitySpec::EmbeddedExampleWithHash.new)
|
|
1695
|
-
expect(presenter.serializable_hash).to eq(name: 'abc', embedded: { a: nil, b: { abc: 'def' } })
|
|
1696
|
-
end
|
|
1697
|
-
end
|
|
1698
|
-
|
|
1699
|
-
context '#attr_path' do
|
|
1700
|
-
it 'for all kinds of attributes' do
|
|
1701
|
-
module EntitySpec
|
|
1702
|
-
class EmailEntity < Grape::Entity
|
|
1703
|
-
expose(:email, as: :addr) { |_, o| o[:attr_path].join('/') }
|
|
1704
|
-
end
|
|
1705
|
-
|
|
1706
|
-
class UserEntity < Grape::Entity
|
|
1707
|
-
expose(:name, as: :full_name) { |_, o| o[:attr_path].join('/') }
|
|
1708
|
-
expose :email, using: 'EntitySpec::EmailEntity'
|
|
1709
|
-
end
|
|
1710
|
-
|
|
1711
|
-
class ExtraEntity < Grape::Entity
|
|
1712
|
-
expose(:key) { |_, o| o[:attr_path].join('/') }
|
|
1713
|
-
expose(:value) { |_, o| o[:attr_path].join('/') }
|
|
1714
|
-
end
|
|
1715
|
-
|
|
1716
|
-
class NestedEntity < Grape::Entity
|
|
1717
|
-
expose(:name) { |_, o| o[:attr_path].join('/') }
|
|
1718
|
-
expose :data, using: 'EntitySpec::ExtraEntity'
|
|
1719
|
-
end
|
|
1720
|
-
end
|
|
1721
|
-
|
|
1722
|
-
fresh_class.class_eval do
|
|
1723
|
-
expose(:id) { |_, o| o[:attr_path].join('/') }
|
|
1724
|
-
expose(:foo, as: :bar) { |_, o| o[:attr_path].join('/') }
|
|
1725
|
-
expose :title do
|
|
1726
|
-
expose :full do
|
|
1727
|
-
expose(:prefix, as: :pref) { |_, o| o[:attr_path].join('/') }
|
|
1728
|
-
expose(:main) { |_, o| o[:attr_path].join('/') }
|
|
1729
|
-
end
|
|
1730
|
-
end
|
|
1731
|
-
expose :friends, as: :social, using: 'EntitySpec::UserEntity'
|
|
1732
|
-
expose :extra, using: 'EntitySpec::ExtraEntity'
|
|
1733
|
-
expose :nested, using: 'EntitySpec::NestedEntity'
|
|
1734
|
-
end
|
|
1735
|
-
|
|
1736
|
-
expect(subject.serializable_hash).to eq(
|
|
1737
|
-
id: 'id',
|
|
1738
|
-
bar: 'bar',
|
|
1739
|
-
title: { full: { pref: 'title/full/pref', main: 'title/full/main' } },
|
|
1740
|
-
social: [
|
|
1741
|
-
{ full_name: 'social/full_name', email: { addr: 'social/email/addr' } },
|
|
1742
|
-
{ full_name: 'social/full_name', email: { addr: 'social/email/addr' } }
|
|
1743
|
-
],
|
|
1744
|
-
extra: { key: 'extra/key', value: 'extra/value' },
|
|
1745
|
-
nested: [
|
|
1746
|
-
{ name: 'nested/name', data: { key: 'nested/data/key', value: 'nested/data/value' } },
|
|
1747
|
-
{ name: 'nested/name', data: { key: 'nested/data/key', value: 'nested/data/value' } }
|
|
1748
|
-
]
|
|
1749
|
-
)
|
|
1750
|
-
end
|
|
1751
|
-
|
|
1752
|
-
it 'allows customize path of an attribute' do
|
|
1753
|
-
module EntitySpec
|
|
1754
|
-
class CharacterEntity < Grape::Entity
|
|
1755
|
-
expose(:key) { |_, o| o[:attr_path].join('/') }
|
|
1756
|
-
expose(:value) { |_, o| o[:attr_path].join('/') }
|
|
1757
|
-
end
|
|
1758
|
-
end
|
|
1759
|
-
|
|
1760
|
-
fresh_class.class_eval do
|
|
1761
|
-
expose :characteristics, using: EntitySpec::CharacterEntity,
|
|
1762
|
-
attr_path: ->(_obj, _opts) { :character }
|
|
1763
|
-
end
|
|
1764
|
-
|
|
1765
|
-
expect(subject.serializable_hash).to eq(
|
|
1766
|
-
characteristics: [
|
|
1767
|
-
{ key: 'character/key', value: 'character/value' }
|
|
1768
|
-
]
|
|
1769
|
-
)
|
|
1770
|
-
end
|
|
1771
|
-
|
|
1772
|
-
it 'can drop one nest level by set path_for to nil' do
|
|
1773
|
-
module EntitySpec
|
|
1774
|
-
class NoPathCharacterEntity < Grape::Entity
|
|
1775
|
-
expose(:key) { |_, o| o[:attr_path].join('/') }
|
|
1776
|
-
expose(:value) { |_, o| o[:attr_path].join('/') }
|
|
1777
|
-
end
|
|
1778
|
-
end
|
|
1779
|
-
|
|
1780
|
-
fresh_class.class_eval do
|
|
1781
|
-
expose :characteristics, using: EntitySpec::NoPathCharacterEntity, attr_path: proc {}
|
|
1782
|
-
end
|
|
1783
|
-
|
|
1784
|
-
expect(subject.serializable_hash).to eq(
|
|
1785
|
-
characteristics: [
|
|
1786
|
-
{ key: 'key', value: 'value' }
|
|
1787
|
-
]
|
|
1788
|
-
)
|
|
1789
|
-
end
|
|
1790
|
-
end
|
|
1791
|
-
|
|
1792
|
-
context 'with projections passed in options' do
|
|
1793
|
-
it 'allows to pass different :only and :except params using the same instance' do
|
|
1794
|
-
fresh_class.expose :a, :b, :c
|
|
1795
|
-
presenter = fresh_class.new(a: 1, b: 2, c: 3)
|
|
1796
|
-
expect(presenter.serializable_hash(only: %i[a b])).to eq(a: 1, b: 2)
|
|
1797
|
-
expect(presenter.serializable_hash(only: %i[b c])).to eq(b: 2, c: 3)
|
|
1798
|
-
end
|
|
1799
|
-
end
|
|
1800
|
-
end
|
|
1801
|
-
|
|
1802
|
-
describe '#to_json' do
|
|
1803
|
-
before do
|
|
1804
|
-
fresh_class.expose :name
|
|
1805
|
-
end
|
|
1806
|
-
|
|
1807
|
-
it 'returns a json' do
|
|
1808
|
-
expect(fresh_class.new(model).to_json).to eq(JSON.generate(attributes.slice(:name)))
|
|
1809
|
-
end
|
|
1810
|
-
end
|
|
1811
|
-
|
|
1812
|
-
describe '#inspect' do
|
|
1813
|
-
before do
|
|
1814
|
-
fresh_class.class_eval do
|
|
1815
|
-
expose :name, :email
|
|
1816
|
-
end
|
|
1817
|
-
end
|
|
1818
|
-
|
|
1819
|
-
it 'does not serialize delegator or options' do
|
|
1820
|
-
data = subject.inspect
|
|
1821
|
-
expect(data).to include 'name='
|
|
1822
|
-
expect(data).to include 'email='
|
|
1823
|
-
expect(data).to_not include '@options'
|
|
1824
|
-
expect(data).to_not include '@delegator'
|
|
1825
|
-
end
|
|
1826
|
-
|
|
1827
|
-
it 'returns a nil string when subject is nil' do
|
|
1828
|
-
data = subject.class.new(nil).inspect
|
|
1829
|
-
expect(data).to include 'nil'
|
|
1830
|
-
end
|
|
1831
|
-
end
|
|
1832
|
-
|
|
1833
|
-
describe '#value_for' do
|
|
1834
|
-
before do
|
|
1835
|
-
fresh_class.class_eval do
|
|
1836
|
-
expose :name, :email
|
|
1837
|
-
expose :friends, using: self
|
|
1838
|
-
expose :computed do |_, options|
|
|
1839
|
-
options[:awesome]
|
|
1840
|
-
end
|
|
1841
|
-
|
|
1842
|
-
expose :birthday, format_with: :timestamp
|
|
1843
|
-
|
|
1844
|
-
def timestamp(date)
|
|
1845
|
-
date.strftime('%m/%d/%Y')
|
|
1846
|
-
end
|
|
1847
|
-
|
|
1848
|
-
expose :fantasies, format_with: ->(f) { f.reverse }
|
|
1849
|
-
end
|
|
1850
|
-
end
|
|
1851
|
-
|
|
1852
|
-
it 'passes through bare expose attributes' do
|
|
1853
|
-
expect(subject.value_for(:name)).to eq attributes[:name]
|
|
1854
|
-
end
|
|
1855
|
-
|
|
1856
|
-
it 'instantiates a representation if that is called for' do
|
|
1857
|
-
rep = subject.value_for(:friends)
|
|
1858
|
-
expect(rep.grep_v(fresh_class)).to be_empty
|
|
1859
|
-
expect(rep.first.serializable_hash[:name]).to eq 'Friend 1'
|
|
1860
|
-
expect(rep.last.serializable_hash[:name]).to eq 'Friend 2'
|
|
1861
|
-
end
|
|
1862
|
-
|
|
1863
|
-
context 'child representations' do
|
|
1864
|
-
after { EntitySpec::FriendEntity.unexpose_all }
|
|
1865
|
-
|
|
1866
|
-
it 'disables root key name for child representations' do
|
|
1867
|
-
module EntitySpec
|
|
1868
|
-
class FriendEntity < Grape::Entity
|
|
1869
|
-
root 'friends', 'friend'
|
|
1870
|
-
expose :name, :email
|
|
1871
|
-
end
|
|
1872
|
-
end
|
|
1873
|
-
|
|
1874
|
-
fresh_class.class_eval do
|
|
1875
|
-
expose :friends, using: EntitySpec::FriendEntity
|
|
1876
|
-
end
|
|
1877
|
-
|
|
1878
|
-
rep = subject.value_for(:friends)
|
|
1879
|
-
expect(rep).to be_kind_of Array
|
|
1880
|
-
expect(rep.grep_v(EntitySpec::FriendEntity)).to be_empty
|
|
1881
|
-
expect(rep.first.serializable_hash[:name]).to eq 'Friend 1'
|
|
1882
|
-
expect(rep.last.serializable_hash[:name]).to eq 'Friend 2'
|
|
1883
|
-
end
|
|
1884
|
-
|
|
1885
|
-
it 'passes through the proc which returns an array of objects with custom options(:using)' do
|
|
1886
|
-
module EntitySpec
|
|
1887
|
-
class FriendEntity < Grape::Entity
|
|
1888
|
-
root 'friends', 'friend'
|
|
1889
|
-
expose :name, :email
|
|
1890
|
-
end
|
|
1891
|
-
end
|
|
1892
|
-
|
|
1893
|
-
fresh_class.class_eval do
|
|
1894
|
-
expose :custom_friends, using: EntitySpec::FriendEntity do |user, _opts|
|
|
1895
|
-
user.friends
|
|
1896
|
-
end
|
|
1897
|
-
end
|
|
1898
|
-
|
|
1899
|
-
rep = subject.value_for(:custom_friends)
|
|
1900
|
-
expect(rep).to be_kind_of Array
|
|
1901
|
-
expect(rep.grep_v(EntitySpec::FriendEntity)).to be_empty
|
|
1902
|
-
expect(rep.first.serializable_hash).to eq(name: 'Friend 1', email: 'friend1@example.com')
|
|
1903
|
-
expect(rep.last.serializable_hash).to eq(name: 'Friend 2', email: 'friend2@example.com')
|
|
1904
|
-
end
|
|
1905
|
-
|
|
1906
|
-
it 'passes through the proc which returns single object with custom options(:using)' do
|
|
1907
|
-
module EntitySpec
|
|
1908
|
-
class FriendEntity < Grape::Entity
|
|
1909
|
-
root 'friends', 'friend'
|
|
1910
|
-
expose :name, :email
|
|
1911
|
-
end
|
|
1912
|
-
end
|
|
1913
|
-
|
|
1914
|
-
fresh_class.class_eval do
|
|
1915
|
-
expose :first_friend, using: EntitySpec::FriendEntity do |user, _opts|
|
|
1916
|
-
user.friends.first
|
|
1917
|
-
end
|
|
1918
|
-
end
|
|
1919
|
-
|
|
1920
|
-
rep = subject.value_for(:first_friend)
|
|
1921
|
-
expect(rep).to be_kind_of EntitySpec::FriendEntity
|
|
1922
|
-
expect(rep.serializable_hash).to eq(name: 'Friend 1', email: 'friend1@example.com')
|
|
1923
|
-
end
|
|
1924
|
-
|
|
1925
|
-
it 'passes through the proc which returns empty with custom options(:using)' do
|
|
1926
|
-
module EntitySpec
|
|
1927
|
-
class FriendEntity < Grape::Entity
|
|
1928
|
-
root 'friends', 'friend'
|
|
1929
|
-
expose :name, :email
|
|
1930
|
-
end
|
|
1931
|
-
end
|
|
1932
|
-
|
|
1933
|
-
# rubocop:disable Lint/EmptyBlock
|
|
1934
|
-
fresh_class.class_eval do
|
|
1935
|
-
expose :first_friend, using: EntitySpec::FriendEntity do |_user, _opts|
|
|
1936
|
-
end
|
|
1937
|
-
end
|
|
1938
|
-
# rubocop:enable Lint/EmptyBlock
|
|
1939
|
-
|
|
1940
|
-
rep = subject.value_for(:first_friend)
|
|
1941
|
-
expect(rep).to be_kind_of EntitySpec::FriendEntity
|
|
1942
|
-
expect(rep.serializable_hash).to be_nil
|
|
1943
|
-
end
|
|
1944
|
-
|
|
1945
|
-
it 'passes through exposed entity with key and value attributes' do
|
|
1946
|
-
module EntitySpec
|
|
1947
|
-
class CharacteristicsEntity < Grape::Entity
|
|
1948
|
-
root 'characteristics', 'characteristic'
|
|
1949
|
-
expose :key, :value
|
|
1950
|
-
end
|
|
1951
|
-
end
|
|
1952
|
-
|
|
1953
|
-
fresh_class.class_eval do
|
|
1954
|
-
expose :characteristics, using: EntitySpec::CharacteristicsEntity
|
|
1955
|
-
end
|
|
1956
|
-
|
|
1957
|
-
rep = subject.value_for(:characteristics)
|
|
1958
|
-
expect(rep).to be_kind_of Array
|
|
1959
|
-
expect(rep.grep_v(EntitySpec::CharacteristicsEntity)).to be_empty
|
|
1960
|
-
expect(rep.first.serializable_hash[:key]).to eq 'hair_color'
|
|
1961
|
-
expect(rep.first.serializable_hash[:value]).to eq 'brown'
|
|
1962
|
-
end
|
|
1963
|
-
|
|
1964
|
-
it 'passes through custom options' do
|
|
1965
|
-
module EntitySpec
|
|
1966
|
-
class FriendEntity < Grape::Entity
|
|
1967
|
-
root 'friends', 'friend'
|
|
1968
|
-
expose :name
|
|
1969
|
-
expose :email, if: { user_type: :admin }
|
|
1970
|
-
end
|
|
1971
|
-
end
|
|
1972
|
-
|
|
1973
|
-
fresh_class.class_eval do
|
|
1974
|
-
expose :friends, using: EntitySpec::FriendEntity
|
|
1975
|
-
end
|
|
1976
|
-
|
|
1977
|
-
rep = subject.value_for(:friends)
|
|
1978
|
-
expect(rep).to be_kind_of Array
|
|
1979
|
-
expect(rep.grep_v(EntitySpec::FriendEntity)).to be_empty
|
|
1980
|
-
expect(rep.first.serializable_hash[:email]).to be_nil
|
|
1981
|
-
expect(rep.last.serializable_hash[:email]).to be_nil
|
|
1982
|
-
|
|
1983
|
-
rep = subject.value_for(:friends, Grape::Entity::Options.new(user_type: :admin))
|
|
1984
|
-
expect(rep).to be_kind_of Array
|
|
1985
|
-
expect(rep.grep_v(EntitySpec::FriendEntity)).to be_empty
|
|
1986
|
-
expect(rep.first.serializable_hash[:email]).to eq 'friend1@example.com'
|
|
1987
|
-
expect(rep.last.serializable_hash[:email]).to eq 'friend2@example.com'
|
|
1988
|
-
end
|
|
1989
|
-
|
|
1990
|
-
it 'ignores the :collection parameter in the source options' do
|
|
1991
|
-
module EntitySpec
|
|
1992
|
-
class FriendEntity < Grape::Entity
|
|
1993
|
-
root 'friends', 'friend'
|
|
1994
|
-
expose :name
|
|
1995
|
-
expose :email, if: { collection: true }
|
|
1996
|
-
end
|
|
1997
|
-
end
|
|
1998
|
-
|
|
1999
|
-
fresh_class.class_eval do
|
|
2000
|
-
expose :friends, using: EntitySpec::FriendEntity
|
|
2001
|
-
end
|
|
2002
|
-
|
|
2003
|
-
rep = subject.value_for(:friends, Grape::Entity::Options.new(collection: false))
|
|
2004
|
-
expect(rep).to be_kind_of Array
|
|
2005
|
-
expect(rep.grep_v(EntitySpec::FriendEntity)).to be_empty
|
|
2006
|
-
expect(rep.first.serializable_hash[:email]).to eq 'friend1@example.com'
|
|
2007
|
-
expect(rep.last.serializable_hash[:email]).to eq 'friend2@example.com'
|
|
2008
|
-
end
|
|
2009
|
-
end
|
|
2010
|
-
|
|
2011
|
-
it 'calls through to the proc if there is one' do
|
|
2012
|
-
expect(subject.value_for(:computed, Grape::Entity::Options.new(awesome: 123))).to eq 123
|
|
2013
|
-
end
|
|
2014
|
-
|
|
2015
|
-
it 'returns a formatted value if format_with is passed' do
|
|
2016
|
-
expect(subject.value_for(:birthday)).to eq '02/27/2012'
|
|
2017
|
-
end
|
|
2018
|
-
|
|
2019
|
-
it 'returns a formatted value if format_with is passed a lambda' do
|
|
2020
|
-
expect(subject.value_for(:fantasies)).to eq ['Nessy', 'Double Rainbows', 'Unicorns']
|
|
2021
|
-
end
|
|
2022
|
-
|
|
2023
|
-
context 'delegate_attribute' do
|
|
2024
|
-
module EntitySpec
|
|
2025
|
-
class DelegatingEntity < Grape::Entity
|
|
2026
|
-
root 'friends', 'friend'
|
|
2027
|
-
expose :name
|
|
2028
|
-
expose :email
|
|
2029
|
-
expose :system
|
|
2030
|
-
|
|
2031
|
-
private
|
|
2032
|
-
|
|
2033
|
-
def name
|
|
2034
|
-
'cooler name'
|
|
2035
|
-
end
|
|
2036
|
-
end
|
|
2037
|
-
end
|
|
2038
|
-
|
|
2039
|
-
it 'tries instance methods on the entity first' do
|
|
2040
|
-
friend = double('Friend', name: 'joe', email: 'joe@example.com')
|
|
2041
|
-
rep = EntitySpec::DelegatingEntity.new(friend)
|
|
2042
|
-
expect(rep.value_for(:name)).to eq 'cooler name'
|
|
2043
|
-
expect(rep.value_for(:email)).to eq 'joe@example.com'
|
|
2044
|
-
|
|
2045
|
-
another_friend = double('Friend', email: 'joe@example.com')
|
|
2046
|
-
rep = EntitySpec::DelegatingEntity.new(another_friend)
|
|
2047
|
-
expect(rep.value_for(:name)).to eq 'cooler name'
|
|
2048
|
-
end
|
|
2049
|
-
|
|
2050
|
-
it 'does not delegate Kernel methods' do
|
|
2051
|
-
foo = double 'Foo', system: 'System'
|
|
2052
|
-
rep = EntitySpec::DelegatingEntity.new foo
|
|
2053
|
-
expect(rep.value_for(:system)).to eq 'System'
|
|
2054
|
-
end
|
|
2055
|
-
|
|
2056
|
-
module EntitySpec
|
|
2057
|
-
class DerivedEntity < DelegatingEntity
|
|
2058
|
-
end
|
|
2059
|
-
end
|
|
2060
|
-
|
|
2061
|
-
it 'derived entity get methods from base entity' do
|
|
2062
|
-
foo = double 'Foo', name: 'joe'
|
|
2063
|
-
rep = EntitySpec::DerivedEntity.new foo
|
|
2064
|
-
expect(rep.value_for(:name)).to eq 'cooler name'
|
|
2065
|
-
end
|
|
2066
|
-
end
|
|
2067
|
-
|
|
2068
|
-
context 'using' do
|
|
2069
|
-
before do
|
|
2070
|
-
module EntitySpec
|
|
2071
|
-
class UserEntity < Grape::Entity
|
|
2072
|
-
expose :name, :email
|
|
2073
|
-
end
|
|
2074
|
-
end
|
|
2075
|
-
end
|
|
2076
|
-
it 'string' do
|
|
2077
|
-
fresh_class.class_eval do
|
|
2078
|
-
expose :friends, using: 'EntitySpec::UserEntity'
|
|
2079
|
-
end
|
|
2080
|
-
|
|
2081
|
-
rep = subject.value_for(:friends)
|
|
2082
|
-
expect(rep).to be_kind_of Array
|
|
2083
|
-
expect(rep.size).to eq 2
|
|
2084
|
-
expect(rep.all?(EntitySpec::UserEntity)).to be true
|
|
2085
|
-
end
|
|
2086
|
-
|
|
2087
|
-
it 'class' do
|
|
2088
|
-
fresh_class.class_eval do
|
|
2089
|
-
expose :friends, using: EntitySpec::UserEntity
|
|
2090
|
-
end
|
|
2091
|
-
|
|
2092
|
-
rep = subject.value_for(:friends)
|
|
2093
|
-
expect(rep).to be_kind_of Array
|
|
2094
|
-
expect(rep.size).to eq 2
|
|
2095
|
-
expect(rep.all?(EntitySpec::UserEntity)).to be true
|
|
2096
|
-
end
|
|
2097
|
-
end
|
|
2098
|
-
end
|
|
2099
|
-
|
|
2100
|
-
describe '.documentation' do
|
|
2101
|
-
it 'returns an empty hash is no documentation is provided' do
|
|
2102
|
-
fresh_class.expose :name
|
|
2103
|
-
|
|
2104
|
-
expect(subject.documentation).to eq({})
|
|
2105
|
-
end
|
|
2106
|
-
|
|
2107
|
-
it 'returns each defined documentation hash' do
|
|
2108
|
-
doc = { type: 'foo', desc: 'bar' }
|
|
2109
|
-
fresh_class.expose :name, documentation: doc
|
|
2110
|
-
fresh_class.expose :email, documentation: doc
|
|
2111
|
-
fresh_class.expose :birthday
|
|
2112
|
-
|
|
2113
|
-
expect(subject.documentation).to eq(name: doc, email: doc)
|
|
2114
|
-
end
|
|
2115
|
-
|
|
2116
|
-
it 'returns each defined documentation hash with :as param considering' do
|
|
2117
|
-
doc = { type: 'foo', desc: 'bar' }
|
|
2118
|
-
fresh_class.expose :name, documentation: doc, as: :label
|
|
2119
|
-
fresh_class.expose :email, documentation: doc
|
|
2120
|
-
fresh_class.expose :birthday
|
|
2121
|
-
|
|
2122
|
-
expect(subject.documentation).to eq(label: doc, email: doc)
|
|
2123
|
-
end
|
|
2124
|
-
|
|
2125
|
-
it 'resets memoization when exposing additional attributes' do
|
|
2126
|
-
fresh_class.expose :x, documentation: { desc: 'just x' }
|
|
2127
|
-
expect(fresh_class.instance_variable_get(:@documentation)).to be_nil
|
|
2128
|
-
doc1 = fresh_class.documentation
|
|
2129
|
-
expect(fresh_class.instance_variable_get(:@documentation)).not_to be_nil
|
|
2130
|
-
fresh_class.expose :y, documentation: { desc: 'just y' }
|
|
2131
|
-
expect(fresh_class.instance_variable_get(:@documentation)).to be_nil
|
|
2132
|
-
doc2 = fresh_class.documentation
|
|
2133
|
-
expect(doc1).to eq(x: { desc: 'just x' })
|
|
2134
|
-
expect(doc2).to eq(x: { desc: 'just x' }, y: { desc: 'just y' })
|
|
2135
|
-
end
|
|
2136
|
-
|
|
2137
|
-
context 'inherited documentation' do
|
|
2138
|
-
it 'returns documentation from ancestor' do
|
|
2139
|
-
doc = { type: 'foo', desc: 'bar' }
|
|
2140
|
-
fresh_class.expose :name, documentation: doc
|
|
2141
|
-
child_class = Class.new(fresh_class)
|
|
2142
|
-
child_class.expose :email, documentation: doc
|
|
2143
|
-
|
|
2144
|
-
expect(fresh_class.documentation).to eq(name: doc)
|
|
2145
|
-
expect(child_class.documentation).to eq(name: doc, email: doc)
|
|
2146
|
-
end
|
|
2147
|
-
|
|
2148
|
-
it 'obeys unexposed attributes in subclass' do
|
|
2149
|
-
doc = { type: 'foo', desc: 'bar' }
|
|
2150
|
-
fresh_class.expose :name, documentation: doc
|
|
2151
|
-
fresh_class.expose :email, documentation: doc
|
|
2152
|
-
child_class = Class.new(fresh_class)
|
|
2153
|
-
child_class.unexpose :email
|
|
2154
|
-
|
|
2155
|
-
expect(fresh_class.documentation).to eq(name: doc, email: doc)
|
|
2156
|
-
expect(child_class.documentation).to eq(name: doc)
|
|
2157
|
-
end
|
|
2158
|
-
|
|
2159
|
-
it 'obeys re-exposed attributes in subclass' do
|
|
2160
|
-
doc = { type: 'foo', desc: 'bar' }
|
|
2161
|
-
fresh_class.expose :name, documentation: doc
|
|
2162
|
-
fresh_class.expose :email, documentation: doc
|
|
2163
|
-
|
|
2164
|
-
child_class = Class.new(fresh_class)
|
|
2165
|
-
child_class.unexpose :email
|
|
2166
|
-
|
|
2167
|
-
nephew_class = Class.new(child_class)
|
|
2168
|
-
new_doc = { type: 'todler', descr: '???' }
|
|
2169
|
-
nephew_class.expose :email, documentation: new_doc
|
|
2170
|
-
|
|
2171
|
-
expect(fresh_class.documentation).to eq(name: doc, email: doc)
|
|
2172
|
-
expect(child_class.documentation).to eq(name: doc)
|
|
2173
|
-
expect(nephew_class.documentation).to eq(name: doc, email: new_doc)
|
|
2174
|
-
end
|
|
2175
|
-
|
|
2176
|
-
it 'includes only root exposures' do
|
|
2177
|
-
fresh_class.expose :name, documentation: { desc: 'foo' }
|
|
2178
|
-
fresh_class.expose :nesting do
|
|
2179
|
-
fresh_class.expose :smth, documentation: { desc: 'should not be seen' }
|
|
2180
|
-
end
|
|
2181
|
-
expect(fresh_class.documentation).to eq(name: { desc: 'foo' })
|
|
2182
|
-
end
|
|
2183
|
-
end
|
|
2184
|
-
end
|
|
2185
|
-
|
|
2186
|
-
describe '::DSL' do
|
|
2187
|
-
subject { Class.new }
|
|
2188
|
-
|
|
2189
|
-
it 'creates an Entity class when called' do
|
|
2190
|
-
expect(subject).not_to be_const_defined :Entity
|
|
2191
|
-
subject.send(:include, Grape::Entity::DSL)
|
|
2192
|
-
expect(subject).to be_const_defined :Entity
|
|
2193
|
-
end
|
|
2194
|
-
|
|
2195
|
-
context 'pre-mixed' do
|
|
2196
|
-
before { subject.send(:include, Grape::Entity::DSL) }
|
|
2197
|
-
|
|
2198
|
-
it 'is able to define entity traits through DSL' do
|
|
2199
|
-
subject.entity do
|
|
2200
|
-
expose :name
|
|
2201
|
-
end
|
|
2202
|
-
|
|
2203
|
-
expect(subject.entity_class.root_exposures).not_to be_empty
|
|
2204
|
-
end
|
|
2205
|
-
|
|
2206
|
-
it 'is able to expose straight from the class' do
|
|
2207
|
-
subject.entity :name, :email
|
|
2208
|
-
expect(subject.entity_class.root_exposures.size).to eq 2
|
|
2209
|
-
end
|
|
2210
|
-
|
|
2211
|
-
it 'is able to mix field and advanced.root_exposures' do
|
|
2212
|
-
subject.entity :name, :email do
|
|
2213
|
-
expose :third
|
|
2214
|
-
end
|
|
2215
|
-
expect(subject.entity_class.root_exposures.size).to eq 3
|
|
2216
|
-
end
|
|
2217
|
-
|
|
2218
|
-
context 'instance' do
|
|
2219
|
-
let(:instance) { subject.new }
|
|
2220
|
-
|
|
2221
|
-
describe '#entity' do
|
|
2222
|
-
it 'is an instance of the entity class' do
|
|
2223
|
-
expect(instance.entity).to be_kind_of(subject.entity_class)
|
|
2224
|
-
end
|
|
2225
|
-
|
|
2226
|
-
it 'has an object of itself' do
|
|
2227
|
-
expect(instance.entity.object).to eq instance
|
|
2228
|
-
end
|
|
2229
|
-
|
|
2230
|
-
it 'instantiates with options if provided' do
|
|
2231
|
-
expect(instance.entity(awesome: true).options).to eq(awesome: true)
|
|
2232
|
-
end
|
|
2233
|
-
end
|
|
2234
|
-
end
|
|
2235
|
-
end
|
|
2236
|
-
end
|
|
2237
|
-
end
|
|
2238
|
-
end
|