grape-entity 0.6.0 → 0.10.1
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 +14 -0
- data/.github/workflows/rubocop.yml +26 -0
- data/.github/workflows/ruby.yml +26 -0
- data/.gitignore +5 -1
- data/.rspec +2 -1
- data/.rubocop.yml +82 -2
- data/.rubocop_todo.yml +16 -33
- data/CHANGELOG.md +120 -0
- data/Dangerfile +2 -0
- data/Gemfile +8 -8
- data/Guardfile +4 -2
- data/README.md +168 -7
- data/Rakefile +2 -2
- data/UPGRADING.md +19 -2
- data/bench/serializing.rb +7 -0
- data/grape-entity.gemspec +10 -8
- data/lib/grape-entity.rb +2 -0
- data/lib/grape_entity/condition/base.rb +3 -1
- data/lib/grape_entity/condition/block_condition.rb +3 -1
- data/lib/grape_entity/condition/hash_condition.rb +2 -0
- data/lib/grape_entity/condition/symbol_condition.rb +2 -0
- data/lib/grape_entity/condition.rb +20 -11
- 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 +115 -38
- data/lib/grape_entity/exposure/base.rb +27 -11
- data/lib/grape_entity/exposure/block_exposure.rb +2 -0
- data/lib/grape_entity/exposure/delegator_exposure.rb +2 -0
- data/lib/grape_entity/exposure/formatter_block_exposure.rb +2 -0
- data/lib/grape_entity/exposure/formatter_exposure.rb +2 -0
- data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +27 -15
- data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +8 -2
- data/lib/grape_entity/exposure/nesting_exposure.rb +36 -30
- data/lib/grape_entity/exposure/represent_exposure.rb +3 -1
- data/lib/grape_entity/exposure.rb +69 -41
- data/lib/grape_entity/options.rb +44 -58
- data/lib/grape_entity/version.rb +3 -1
- data/lib/grape_entity.rb +3 -0
- data/spec/grape_entity/entity_spec.rb +405 -48
- data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +6 -4
- data/spec/grape_entity/exposure/represent_exposure_spec.rb +5 -3
- data/spec/grape_entity/exposure_spec.rb +14 -2
- data/spec/grape_entity/hash_spec.rb +52 -1
- data/spec/grape_entity/options_spec.rb +66 -0
- data/spec/spec_helper.rb +17 -0
- metadata +35 -45
- data/.travis.yml +0 -26
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'ostruct'
|
3
5
|
|
@@ -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
|
@@ -64,6 +68,274 @@ describe Grape::Entity do
|
|
64
68
|
end
|
65
69
|
end
|
66
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
|
+
|
67
339
|
context 'with a block' do
|
68
340
|
it 'errors out if called with multiple attributes' do
|
69
341
|
expect { subject.expose(:name, :email) { true } }.to raise_error ArgumentError
|
@@ -112,6 +384,53 @@ describe Grape::Entity do
|
|
112
384
|
end
|
113
385
|
end
|
114
386
|
|
387
|
+
describe 'blocks' do
|
388
|
+
class SomeObject
|
389
|
+
def method_without_args
|
390
|
+
'result'
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
describe 'with block passed in' do
|
395
|
+
specify do
|
396
|
+
subject.expose :that_method_without_args do |object|
|
397
|
+
object.method_without_args
|
398
|
+
end
|
399
|
+
|
400
|
+
object = SomeObject.new
|
401
|
+
|
402
|
+
value = subject.represent(object).value_for(:that_method_without_args)
|
403
|
+
expect(value).to eq('result')
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
context 'with block passed in via &' do
|
408
|
+
if RUBY_VERSION.start_with?('3')
|
409
|
+
specify do
|
410
|
+
subject.expose :that_method_without_args, &:method_without_args
|
411
|
+
subject.expose :method_without_args, as: :that_method_without_args_again
|
412
|
+
|
413
|
+
object = SomeObject.new
|
414
|
+
expect do
|
415
|
+
subject.represent(object).value_for(:that_method_without_args)
|
416
|
+
end.to raise_error Grape::Entity::Deprecated
|
417
|
+
|
418
|
+
value2 = subject.represent(object).value_for(:that_method_without_args_again)
|
419
|
+
expect(value2).to eq('result')
|
420
|
+
end
|
421
|
+
else
|
422
|
+
specify do
|
423
|
+
subject.expose :that_method_without_args_again, &:method_without_args
|
424
|
+
|
425
|
+
object = SomeObject.new
|
426
|
+
|
427
|
+
value2 = subject.represent(object).value_for(:that_method_without_args_again)
|
428
|
+
expect(value2).to eq('result')
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
115
434
|
context 'with no parameters passed to the block' do
|
116
435
|
it 'adds a nested exposure' do
|
117
436
|
subject.expose :awesome do
|
@@ -131,7 +450,7 @@ describe Grape::Entity do
|
|
131
450
|
expect(another_nested).to_not be_nil
|
132
451
|
expect(another_nested.using_class_name).to eq('Awesome')
|
133
452
|
expect(moar_nested).to_not be_nil
|
134
|
-
expect(moar_nested.key).to eq(:weee)
|
453
|
+
expect(moar_nested.key(subject)).to eq(:weee)
|
135
454
|
end
|
136
455
|
|
137
456
|
it 'represents the exposure as a hash of its nested.root_exposures' do
|
@@ -324,6 +643,24 @@ describe Grape::Entity do
|
|
324
643
|
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'bar')
|
325
644
|
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'foo')
|
326
645
|
end
|
646
|
+
|
647
|
+
it 'not overrides exposure by default' do
|
648
|
+
subject.expose :name
|
649
|
+
child_class = Class.new(subject)
|
650
|
+
child_class.expose :name, as: :child_name
|
651
|
+
|
652
|
+
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
|
653
|
+
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar', child_name: 'bar')
|
654
|
+
end
|
655
|
+
|
656
|
+
it 'overrides parent class exposure when option is specified' do
|
657
|
+
subject.expose :name
|
658
|
+
child_class = Class.new(subject)
|
659
|
+
child_class.expose :name, as: :child_name, override: true
|
660
|
+
|
661
|
+
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
|
662
|
+
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(child_name: 'bar')
|
663
|
+
end
|
327
664
|
end
|
328
665
|
|
329
666
|
context 'register formatters' do
|
@@ -496,7 +833,7 @@ describe Grape::Entity do
|
|
496
833
|
end
|
497
834
|
|
498
835
|
exposure = subject.find_exposure(:awesome_thing)
|
499
|
-
expect(exposure.key).to eq :extra_smooth
|
836
|
+
expect(exposure.key(subject)).to eq :extra_smooth
|
500
837
|
end
|
501
838
|
|
502
839
|
it 'merges nested :if option' do
|
@@ -596,6 +933,34 @@ describe Grape::Entity do
|
|
596
933
|
exposure = subject.find_exposure(:awesome_thing)
|
597
934
|
expect(exposure.documentation).to eq(desc: 'Other description.')
|
598
935
|
end
|
936
|
+
|
937
|
+
it 'propagates expose_nil option' do
|
938
|
+
subject.class_eval do
|
939
|
+
with_options(expose_nil: false) do
|
940
|
+
expose :awesome_thing
|
941
|
+
end
|
942
|
+
end
|
943
|
+
|
944
|
+
exposure = subject.find_exposure(:awesome_thing)
|
945
|
+
expect(exposure.conditions[0].inversed?).to be true
|
946
|
+
expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
|
947
|
+
end
|
948
|
+
|
949
|
+
it 'overrides nested :expose_nil option' do
|
950
|
+
subject.class_eval do
|
951
|
+
with_options(expose_nil: true) do
|
952
|
+
expose :awesome_thing, expose_nil: false
|
953
|
+
expose :other_awesome_thing
|
954
|
+
end
|
955
|
+
end
|
956
|
+
|
957
|
+
exposure = subject.find_exposure(:awesome_thing)
|
958
|
+
expect(exposure.conditions[0].inversed?).to be true
|
959
|
+
expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
|
960
|
+
# Conditions are only added for exposures that do not expose nil
|
961
|
+
exposure = subject.find_exposure(:other_awesome_thing)
|
962
|
+
expect(exposure.conditions[0]).to be_nil
|
963
|
+
end
|
599
964
|
end
|
600
965
|
|
601
966
|
describe '.represent' do
|
@@ -653,7 +1018,7 @@ describe Grape::Entity do
|
|
653
1018
|
context 'with specified fields' do
|
654
1019
|
it 'returns only specified fields with only option' do
|
655
1020
|
subject.expose(:id, :name, :phone)
|
656
|
-
representation = subject.represent(OpenStruct.new, only: [
|
1021
|
+
representation = subject.represent(OpenStruct.new, only: %i[id name], serializable: true)
|
657
1022
|
expect(representation).to eq(id: nil, name: nil)
|
658
1023
|
end
|
659
1024
|
|
@@ -666,7 +1031,7 @@ describe Grape::Entity do
|
|
666
1031
|
it 'returns only fields specified in the only option and not specified in the except option' do
|
667
1032
|
subject.expose(:id, :name, :phone)
|
668
1033
|
representation = subject.represent(OpenStruct.new,
|
669
|
-
only: [
|
1034
|
+
only: %i[name phone],
|
670
1035
|
except: [:phone], serializable: true)
|
671
1036
|
expect(representation).to eq(name: nil)
|
672
1037
|
end
|
@@ -736,7 +1101,7 @@ describe Grape::Entity do
|
|
736
1101
|
subject.expose(:id, :name, :phone)
|
737
1102
|
subject.expose(:user, using: user_entity)
|
738
1103
|
|
739
|
-
representation = subject.represent(OpenStruct.new(user: {}), only: [:id, :name, { user: [
|
1104
|
+
representation = subject.represent(OpenStruct.new(user: {}), only: [:id, :name, { user: %i[name email] }], serializable: true)
|
740
1105
|
expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
|
741
1106
|
end
|
742
1107
|
|
@@ -759,7 +1124,7 @@ describe Grape::Entity do
|
|
759
1124
|
subject.expose(:user, using: user_entity)
|
760
1125
|
|
761
1126
|
representation = subject.represent(OpenStruct.new(user: {}),
|
762
|
-
only: [:id, :name, :phone, user: [
|
1127
|
+
only: [:id, :name, :phone, { user: %i[id name email] }],
|
763
1128
|
except: [:phone, { user: [:id] }], serializable: true)
|
764
1129
|
expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
|
765
1130
|
end
|
@@ -771,7 +1136,7 @@ describe Grape::Entity do
|
|
771
1136
|
subject.expose(:name)
|
772
1137
|
end
|
773
1138
|
|
774
|
-
representation = subject.represent(OpenStruct.new, condition: true, only: [
|
1139
|
+
representation = subject.represent(OpenStruct.new, condition: true, only: %i[id name], serializable: true)
|
775
1140
|
expect(representation).to eq(id: nil, name: nil)
|
776
1141
|
end
|
777
1142
|
|
@@ -781,7 +1146,7 @@ describe Grape::Entity do
|
|
781
1146
|
subject.expose(:name, :mobile_phone)
|
782
1147
|
end
|
783
1148
|
|
784
|
-
representation = subject.represent(OpenStruct.new, condition: true, except: [
|
1149
|
+
representation = subject.represent(OpenStruct.new, condition: true, except: %i[phone mobile_phone], serializable: true)
|
785
1150
|
expect(representation).to eq(id: nil, name: nil)
|
786
1151
|
end
|
787
1152
|
|
@@ -863,7 +1228,7 @@ describe Grape::Entity do
|
|
863
1228
|
subject.expose(:id)
|
864
1229
|
subject.expose(:name, as: :title)
|
865
1230
|
|
866
|
-
representation = subject.represent(OpenStruct.new, condition: true, only: [
|
1231
|
+
representation = subject.represent(OpenStruct.new, condition: true, only: %i[id title], serializable: true)
|
867
1232
|
expect(representation).to eq(id: nil, title: nil)
|
868
1233
|
end
|
869
1234
|
|
@@ -890,10 +1255,22 @@ describe Grape::Entity do
|
|
890
1255
|
subject.expose(:nephew, using: nephew_entity)
|
891
1256
|
|
892
1257
|
representation = subject.represent(OpenStruct.new(user: {}),
|
893
|
-
only: [
|
1258
|
+
only: %i[id name user], except: [:nephew], serializable: true)
|
894
1259
|
expect(representation).to eq(id: nil, name: nil, user: { id: nil, name: nil, email: nil })
|
895
1260
|
end
|
896
1261
|
end
|
1262
|
+
|
1263
|
+
context 'when NameError happens in a parameterized block_exposure' do
|
1264
|
+
before do
|
1265
|
+
subject.expose :raise_no_method_error do |_|
|
1266
|
+
foo
|
1267
|
+
end
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
it 'does not cause infinite loop' do
|
1271
|
+
expect { subject.represent({}, serializable: true) }.to raise_error(NameError)
|
1272
|
+
end
|
1273
|
+
end
|
897
1274
|
end
|
898
1275
|
end
|
899
1276
|
|
@@ -1178,6 +1555,18 @@ describe Grape::Entity do
|
|
1178
1555
|
expect(res).to have_key :nonexistent_attribute
|
1179
1556
|
end
|
1180
1557
|
|
1558
|
+
it 'exposes attributes defined through module inclusion' do
|
1559
|
+
module SharedAttributes
|
1560
|
+
def a_value
|
1561
|
+
3.14
|
1562
|
+
end
|
1563
|
+
end
|
1564
|
+
fresh_class.include(SharedAttributes)
|
1565
|
+
fresh_class.expose :a_value
|
1566
|
+
res = fresh_class.new(model).serializable_hash
|
1567
|
+
expect(res[:a_value]).to eq(3.14)
|
1568
|
+
end
|
1569
|
+
|
1181
1570
|
it 'does not expose attributes that are generated by a block but have not passed criteria' do
|
1182
1571
|
fresh_class.expose :nonexistent_attribute,
|
1183
1572
|
proc: ->(_, _) { 'I exist, but it is not yet my time to shine' },
|
@@ -1326,7 +1715,7 @@ describe Grape::Entity do
|
|
1326
1715
|
end
|
1327
1716
|
|
1328
1717
|
fresh_class.class_eval do
|
1329
|
-
expose :characteristics, using: EntitySpec::NoPathCharacterEntity, attr_path: proc {
|
1718
|
+
expose :characteristics, using: EntitySpec::NoPathCharacterEntity, attr_path: proc {}
|
1330
1719
|
end
|
1331
1720
|
|
1332
1721
|
expect(subject.serializable_hash).to eq(
|
@@ -1341,8 +1730,8 @@ describe Grape::Entity do
|
|
1341
1730
|
it 'allows to pass different :only and :except params using the same instance' do
|
1342
1731
|
fresh_class.expose :a, :b, :c
|
1343
1732
|
presenter = fresh_class.new(a: 1, b: 2, c: 3)
|
1344
|
-
expect(presenter.serializable_hash(only: [
|
1345
|
-
expect(presenter.serializable_hash(only: [
|
1733
|
+
expect(presenter.serializable_hash(only: %i[a b])).to eq(a: 1, b: 2)
|
1734
|
+
expect(presenter.serializable_hash(only: %i[b c])).to eq(b: 2, c: 3)
|
1346
1735
|
end
|
1347
1736
|
end
|
1348
1737
|
end
|
@@ -1463,10 +1852,12 @@ describe Grape::Entity do
|
|
1463
1852
|
end
|
1464
1853
|
end
|
1465
1854
|
|
1855
|
+
# rubocop:disable Lint/EmptyBlock
|
1466
1856
|
fresh_class.class_eval do
|
1467
1857
|
expose :first_friend, using: EntitySpec::FriendEntity do |_user, _opts|
|
1468
1858
|
end
|
1469
1859
|
end
|
1860
|
+
# rubocop:enable Lint/EmptyBlock
|
1470
1861
|
|
1471
1862
|
rep = subject.value_for(:first_friend)
|
1472
1863
|
expect(rep).to be_kind_of EntitySpec::FriendEntity
|
@@ -1765,39 +2156,5 @@ describe Grape::Entity do
|
|
1765
2156
|
end
|
1766
2157
|
end
|
1767
2158
|
end
|
1768
|
-
|
1769
|
-
describe Grape::Entity::Options do
|
1770
|
-
module EntitySpec
|
1771
|
-
class Crystalline
|
1772
|
-
attr_accessor :prop1, :prop2
|
1773
|
-
|
1774
|
-
def initialize
|
1775
|
-
@prop1 = 'value1'
|
1776
|
-
@prop2 = 'value2'
|
1777
|
-
end
|
1778
|
-
end
|
1779
|
-
|
1780
|
-
class CrystallineEntity < Grape::Entity
|
1781
|
-
expose :prop1, if: ->(_, options) { options.fetch(:signal) }
|
1782
|
-
expose :prop2, if: ->(_, options) { options.fetch(:beam, 'destructive') == 'destructive' }
|
1783
|
-
end
|
1784
|
-
end
|
1785
|
-
|
1786
|
-
context '#fetch' do
|
1787
|
-
it 'without passing in a required option raises KeyError' do
|
1788
|
-
expect { EntitySpec::CrystallineEntity.represent(EntitySpec::Crystalline.new).as_json }.to raise_error KeyError
|
1789
|
-
end
|
1790
|
-
|
1791
|
-
it 'passing in a required option will expose the values' do
|
1792
|
-
crystalline_entity = EntitySpec::CrystallineEntity.represent(EntitySpec::Crystalline.new, signal: true)
|
1793
|
-
expect(crystalline_entity.as_json).to eq(prop1: 'value1', prop2: 'value2')
|
1794
|
-
end
|
1795
|
-
|
1796
|
-
it 'with an option that is not default will not expose that value' do
|
1797
|
-
crystalline_entity = EntitySpec::CrystallineEntity.represent(EntitySpec::Crystalline.new, signal: true, beam: 'intermittent')
|
1798
|
-
expect(crystalline_entity.as_json).to eq(prop1: 'value1')
|
1799
|
-
end
|
1800
|
-
end
|
1801
|
-
end
|
1802
2159
|
end
|
1803
2160
|
end
|
@@ -1,13 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Grape::Entity::Exposure::NestingExposure::NestedExposures do
|
4
6
|
subject(:nested_exposures) { described_class.new([]) }
|
5
7
|
|
6
|
-
describe '#deep_complex_nesting?' do
|
8
|
+
describe '#deep_complex_nesting?(entity)' do
|
7
9
|
it 'is reset when additional exposure is added' do
|
8
10
|
subject << Grape::Entity::Exposure.new(:x, {})
|
9
11
|
expect(subject.instance_variable_get(:@deep_complex_nesting)).to be_nil
|
10
|
-
subject.deep_complex_nesting?
|
12
|
+
subject.deep_complex_nesting?(subject)
|
11
13
|
expect(subject.instance_variable_get(:@deep_complex_nesting)).to_not be_nil
|
12
14
|
subject << Grape::Entity::Exposure.new(:y, {})
|
13
15
|
expect(subject.instance_variable_get(:@deep_complex_nesting)).to be_nil
|
@@ -16,7 +18,7 @@ describe Grape::Entity::Exposure::NestingExposure::NestedExposures do
|
|
16
18
|
it 'is reset when exposure is deleted' do
|
17
19
|
subject << Grape::Entity::Exposure.new(:x, {})
|
18
20
|
expect(subject.instance_variable_get(:@deep_complex_nesting)).to be_nil
|
19
|
-
subject.deep_complex_nesting?
|
21
|
+
subject.deep_complex_nesting?(subject)
|
20
22
|
expect(subject.instance_variable_get(:@deep_complex_nesting)).to_not be_nil
|
21
23
|
subject.delete_by(:x)
|
22
24
|
expect(subject.instance_variable_get(:@deep_complex_nesting)).to be_nil
|
@@ -25,7 +27,7 @@ describe Grape::Entity::Exposure::NestingExposure::NestedExposures do
|
|
25
27
|
it 'is reset when exposures are cleared' do
|
26
28
|
subject << Grape::Entity::Exposure.new(:x, {})
|
27
29
|
expect(subject.instance_variable_get(:@deep_complex_nesting)).to be_nil
|
28
|
-
subject.deep_complex_nesting?
|
30
|
+
subject.deep_complex_nesting?(subject)
|
29
31
|
expect(subject.instance_variable_get(:@deep_complex_nesting)).to_not be_nil
|
30
32
|
subject.clear
|
31
33
|
expect(subject.instance_variable_get(:@deep_complex_nesting)).to be_nil
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Grape::Entity::Exposure::RepresentExposure do
|
@@ -10,11 +12,11 @@ describe Grape::Entity::Exposure::RepresentExposure do
|
|
10
12
|
let(:subexposure) { double(:subexposure) }
|
11
13
|
|
12
14
|
it 'sets using_class_name' do
|
13
|
-
expect { subject }.to change
|
15
|
+
expect { subject }.to change(exposure, :using_class_name).to(using_class_name)
|
14
16
|
end
|
15
17
|
|
16
18
|
it 'sets subexposure' do
|
17
|
-
expect { subject }.to change
|
19
|
+
expect { subject }.to change(exposure, :subexposure).to(subexposure)
|
18
20
|
end
|
19
21
|
|
20
22
|
context 'when using_class is set' do
|
@@ -23,7 +25,7 @@ describe Grape::Entity::Exposure::RepresentExposure do
|
|
23
25
|
end
|
24
26
|
|
25
27
|
it 'resets using_class' do
|
26
|
-
expect { subject }.to change
|
28
|
+
expect { subject }.to change(exposure, :using_class)
|
27
29
|
end
|
28
30
|
end
|
29
31
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Grape::Entity::Exposure do
|
@@ -24,12 +26,22 @@ describe Grape::Entity::Exposure do
|
|
24
26
|
describe '#key' do
|
25
27
|
it 'returns the attribute if no :as is set' do
|
26
28
|
fresh_class.expose :name
|
27
|
-
expect(subject.key).to eq :name
|
29
|
+
expect(subject.key(entity)).to eq :name
|
28
30
|
end
|
29
31
|
|
30
32
|
it 'returns the :as alias if one exists' do
|
31
33
|
fresh_class.expose :name, as: :nombre
|
32
|
-
expect(subject.key).to eq :nombre
|
34
|
+
expect(subject.key(entity)).to eq :nombre
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'returns the result if :as is a proc' do
|
38
|
+
fresh_class.expose :name, as: proc { object.name.reverse }
|
39
|
+
expect(subject.key(entity)).to eq(model.name.reverse)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'returns the result if :as is a lambda' do
|
43
|
+
fresh_class.expose :name, as: ->(obj, _opts) { obj.name.reverse }
|
44
|
+
expect(subject.key(entity)).to eq(model.name.reverse)
|
33
45
|
end
|
34
46
|
end
|
35
47
|
|