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.
Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/.coveralls.yml +1 -0
  3. data/.github/dependabot.yml +14 -0
  4. data/.github/workflows/rubocop.yml +26 -0
  5. data/.github/workflows/ruby.yml +26 -0
  6. data/.gitignore +5 -1
  7. data/.rspec +2 -1
  8. data/.rubocop.yml +82 -2
  9. data/.rubocop_todo.yml +16 -33
  10. data/CHANGELOG.md +120 -0
  11. data/Dangerfile +2 -0
  12. data/Gemfile +8 -8
  13. data/Guardfile +4 -2
  14. data/README.md +168 -7
  15. data/Rakefile +2 -2
  16. data/UPGRADING.md +19 -2
  17. data/bench/serializing.rb +7 -0
  18. data/grape-entity.gemspec +10 -8
  19. data/lib/grape-entity.rb +2 -0
  20. data/lib/grape_entity/condition/base.rb +3 -1
  21. data/lib/grape_entity/condition/block_condition.rb +3 -1
  22. data/lib/grape_entity/condition/hash_condition.rb +2 -0
  23. data/lib/grape_entity/condition/symbol_condition.rb +2 -0
  24. data/lib/grape_entity/condition.rb +20 -11
  25. data/lib/grape_entity/delegator/base.rb +7 -0
  26. data/lib/grape_entity/delegator/fetchable_object.rb +2 -0
  27. data/lib/grape_entity/delegator/hash_object.rb +4 -2
  28. data/lib/grape_entity/delegator/openstruct_object.rb +2 -0
  29. data/lib/grape_entity/delegator/plain_object.rb +2 -0
  30. data/lib/grape_entity/delegator.rb +14 -9
  31. data/lib/grape_entity/deprecated.rb +13 -0
  32. data/lib/grape_entity/entity.rb +115 -38
  33. data/lib/grape_entity/exposure/base.rb +27 -11
  34. data/lib/grape_entity/exposure/block_exposure.rb +2 -0
  35. data/lib/grape_entity/exposure/delegator_exposure.rb +2 -0
  36. data/lib/grape_entity/exposure/formatter_block_exposure.rb +2 -0
  37. data/lib/grape_entity/exposure/formatter_exposure.rb +2 -0
  38. data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +27 -15
  39. data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +8 -2
  40. data/lib/grape_entity/exposure/nesting_exposure.rb +36 -30
  41. data/lib/grape_entity/exposure/represent_exposure.rb +3 -1
  42. data/lib/grape_entity/exposure.rb +69 -41
  43. data/lib/grape_entity/options.rb +44 -58
  44. data/lib/grape_entity/version.rb +3 -1
  45. data/lib/grape_entity.rb +3 -0
  46. data/spec/grape_entity/entity_spec.rb +405 -48
  47. data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +6 -4
  48. data/spec/grape_entity/exposure/represent_exposure_spec.rb +5 -3
  49. data/spec/grape_entity/exposure_spec.rb +14 -2
  50. data/spec/grape_entity/hash_spec.rb +52 -1
  51. data/spec/grape_entity/options_spec.rb +66 -0
  52. data/spec/spec_helper.rb +17 -0
  53. metadata +35 -45
  54. 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
- expect { subject.expose :name, format_with: proc {} {} }.to raise_error ArgumentError
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: [:id, :name], serializable: true)
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: [:name, :phone],
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: [:name, :email] }], serializable: true)
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: [:id, :name, :email]],
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: [:id, :name], serializable: true)
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: [:phone, :mobile_phone], serializable: true)
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: [:id, :title], serializable: true)
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: [:id, :name, :user], except: [:nephew], serializable: true)
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 { nil }
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: [:a, :b])).to eq(a: 1, b: 2)
1345
- expect(presenter.serializable_hash(only: [:b, :c])).to eq(b: 2, c: 3)
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 { exposure.using_class_name }.to(using_class_name)
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 { exposure.subexposure }.to(subexposure)
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 { exposure.using_class }
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