grape-entity 0.4.8 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,47 @@
1
+ module Grape
2
+ class Entity
3
+ module Exposure
4
+ class RepresentExposure < Base
5
+ attr_reader :using_class_name, :subexposure
6
+
7
+ def setup(using_class_name, subexposure)
8
+ @using_class_name = using_class_name
9
+ @subexposure = subexposure
10
+ end
11
+
12
+ def dup_args
13
+ [*super, using_class_name, subexposure]
14
+ end
15
+
16
+ def ==(other)
17
+ super &&
18
+ @using_class_name == other.using_class_name &&
19
+ @subexposure == other.subexposure
20
+ end
21
+
22
+ def value(entity, options)
23
+ new_options = options.for_nesting(key)
24
+ using_class.represent(@subexposure.value(entity, options), new_options)
25
+ end
26
+
27
+ def valid?(entity)
28
+ @subexposure.valid? entity
29
+ end
30
+
31
+ def using_class
32
+ @using_class ||= if @using_class_name.respond_to? :constantize
33
+ @using_class_name.constantize
34
+ else
35
+ @using_class_name
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def using_options_for(options)
42
+ options.for_nesting(key)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,142 @@
1
+ module Grape
2
+ class Entity
3
+ class Options
4
+ attr_reader :opts_hash
5
+
6
+ def initialize(opts_hash = {})
7
+ @opts_hash = opts_hash
8
+ @has_only = !opts_hash[:only].nil?
9
+ @has_except = !opts_hash[:except].nil?
10
+ @for_nesting_cache = {}
11
+ @should_return_key_cache = {}
12
+ end
13
+
14
+ def [](key)
15
+ @opts_hash[key]
16
+ end
17
+
18
+ def key?(key)
19
+ @opts_hash.key? key
20
+ end
21
+
22
+ def merge(new_opts)
23
+ if new_opts.empty?
24
+ self
25
+ else
26
+ merged = if new_opts.instance_of? Options
27
+ @opts_hash.merge(new_opts.opts_hash)
28
+ else
29
+ @opts_hash.merge(new_opts)
30
+ end
31
+ Options.new(merged)
32
+ end
33
+ end
34
+
35
+ def reverse_merge(new_opts)
36
+ if new_opts.empty?
37
+ self
38
+ else
39
+ merged = if new_opts.instance_of? Options
40
+ new_opts.opts_hash.merge(@opts_hash)
41
+ else
42
+ new_opts.merge(@opts_hash)
43
+ end
44
+ Options.new(merged)
45
+ end
46
+ end
47
+
48
+ def empty?
49
+ @opts_hash.empty?
50
+ end
51
+
52
+ def ==(other)
53
+ if other.is_a? Options
54
+ @opts_hash == other.opts_hash
55
+ else
56
+ @opts_hash == other
57
+ end
58
+ end
59
+
60
+ def should_return_key?(key)
61
+ return true unless @has_only || @has_except
62
+
63
+ only = only_fields.nil? ||
64
+ only_fields.key?(key)
65
+ except = except_fields && except_fields.key?(key) &&
66
+ except_fields[key] == true
67
+ only && !except
68
+ end
69
+
70
+ def for_nesting(key)
71
+ @for_nesting_cache[key] ||= build_for_nesting(key)
72
+ end
73
+
74
+ def only_fields(for_key = nil)
75
+ return nil unless @has_only
76
+
77
+ @only_fields ||= @opts_hash[:only].each_with_object({}) do |attribute, allowed_fields|
78
+ if attribute.is_a?(Hash)
79
+ attribute.each do |attr, nested_attrs|
80
+ allowed_fields[attr] ||= []
81
+ allowed_fields[attr] += nested_attrs
82
+ end
83
+ else
84
+ allowed_fields[attribute] = true
85
+ end
86
+ end.symbolize_keys
87
+
88
+ if for_key && @only_fields[for_key].is_a?(Array)
89
+ @only_fields[for_key]
90
+ elsif for_key.nil?
91
+ @only_fields
92
+ end
93
+ end
94
+
95
+ def except_fields(for_key = nil)
96
+ return nil unless @has_except
97
+
98
+ @except_fields ||= @opts_hash[:except].each_with_object({}) do |attribute, allowed_fields|
99
+ if attribute.is_a?(Hash)
100
+ attribute.each do |attr, nested_attrs|
101
+ allowed_fields[attr] ||= []
102
+ allowed_fields[attr] += nested_attrs
103
+ end
104
+ else
105
+ allowed_fields[attribute] = true
106
+ end
107
+ end.symbolize_keys
108
+
109
+ if for_key && @except_fields[for_key].is_a?(Array)
110
+ @except_fields[for_key]
111
+ elsif for_key.nil?
112
+ @except_fields
113
+ end
114
+ end
115
+
116
+ def with_attr_path(part)
117
+ stack = (opts_hash[:attr_path] ||= [])
118
+ if part
119
+ stack.push part
120
+ result = yield
121
+ stack.pop
122
+ result
123
+ else
124
+ yield
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ def build_for_nesting(key)
131
+ new_opts_hash = opts_hash.dup
132
+ new_opts_hash.delete(:collection)
133
+ new_opts_hash[:root] = nil
134
+ new_opts_hash[:only] = only_fields(key)
135
+ new_opts_hash[:except] = except_fields(key)
136
+ new_opts_hash[:attr_path] = opts_hash[:attr_path]
137
+
138
+ Options.new(new_opts_hash)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -1,3 +1,3 @@
1
1
  module GrapeEntity
2
- VERSION = '0.4.8'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -11,12 +11,12 @@ describe Grape::Entity do
11
11
  context 'multiple attributes' do
12
12
  it 'is able to add multiple exposed attributes with a single call' do
13
13
  subject.expose :name, :email, :location
14
- expect(subject.exposures.size).to eq 3
14
+ expect(subject.root_exposures.size).to eq 3
15
15
  end
16
16
 
17
- it 'sets the same options for all exposures passed' do
17
+ it 'sets the same options for all.root_exposures passed' do
18
18
  subject.expose :name, :email, :location, documentation: true
19
- subject.exposures.values.each { |v| expect(v).to eq(documentation: true) }
19
+ subject.root_exposures.each { |v| expect(v.documentation).to eq true }
20
20
  end
21
21
  end
22
22
 
@@ -61,10 +61,10 @@ describe Grape::Entity do
61
61
  end
62
62
 
63
63
  object = EntitySpec::SomeObject1.new
64
- value = subject.represent(object).send(:value_for, :bogus)
64
+ value = subject.represent(object).value_for(:bogus)
65
65
  expect(value).to be_instance_of EntitySpec::BogusEntity
66
66
 
67
- prop1 = value.send(:value_for, :prop1)
67
+ prop1 = value.value_for(:prop1)
68
68
  expect(prop1).to eq 'MODIFIED 2'
69
69
  end
70
70
 
@@ -72,12 +72,14 @@ describe Grape::Entity do
72
72
  it 'sets the :proc option in the exposure options' do
73
73
  block = ->(_) { true }
74
74
  subject.expose :name, using: 'Awesome', &block
75
- expect(subject.exposures[:name]).to eq(proc: block, using: 'Awesome')
75
+ exposure = subject.find_exposure(:name)
76
+ expect(exposure.subexposure.block).to eq(block)
77
+ expect(exposure.using_class_name).to eq('Awesome')
76
78
  end
77
79
 
78
80
  it 'references an instance of the entity without any options' do
79
81
  subject.expose(:size) { |_| self }
80
- expect(subject.represent({}).send(:value_for, :size)).to be_an_instance_of fresh_class
82
+ expect(subject.represent({}).value_for(:size)).to be_an_instance_of fresh_class
81
83
  end
82
84
  end
83
85
 
@@ -90,33 +92,38 @@ describe Grape::Entity do
90
92
  subject.expose :another_nested, using: 'Awesome'
91
93
  end
92
94
 
93
- expect(subject.exposures).to eq(
94
- awesome: {},
95
- awesome__nested: { nested: true },
96
- awesome__nested__moar_nested: { as: 'weee', nested: true },
97
- awesome__another_nested: { using: 'Awesome', nested: true }
98
- )
95
+ awesome = subject.find_exposure(:awesome)
96
+ nested = awesome.find_nested_exposure(:nested)
97
+ another_nested = awesome.find_nested_exposure(:another_nested)
98
+ moar_nested = nested.find_nested_exposure(:moar_nested)
99
+
100
+ expect(awesome).to be_nesting
101
+ expect(nested).to_not be_nil
102
+ expect(another_nested).to_not be_nil
103
+ expect(another_nested.using_class_name).to eq('Awesome')
104
+ expect(moar_nested).to_not be_nil
105
+ expect(moar_nested.key).to eq(:weee)
99
106
  end
100
107
 
101
- it 'represents the exposure as a hash of its nested exposures' do
108
+ it 'represents the exposure as a hash of its nested.root_exposures' do
102
109
  subject.expose :awesome do
103
110
  subject.expose(:nested) { |_| 'value' }
104
111
  subject.expose(:another_nested) { |_| 'value' }
105
112
  end
106
113
 
107
- expect(subject.represent({}).send(:value_for, :awesome)).to eq(
114
+ expect(subject.represent({}).value_for(:awesome)).to eq(
108
115
  nested: 'value',
109
116
  another_nested: 'value'
110
117
  )
111
118
  end
112
119
 
113
- it 'does not represent nested exposures whose conditions are not met' do
120
+ it 'does not represent nested.root_exposures whose conditions are not met' do
114
121
  subject.expose :awesome do
115
122
  subject.expose(:condition_met, if: ->(_, _) { true }) { |_| 'value' }
116
123
  subject.expose(:condition_not_met, if: ->(_, _) { false }) { |_| 'value' }
117
124
  end
118
125
 
119
- expect(subject.represent({}).send(:value_for, :awesome)).to eq(condition_met: 'value')
126
+ expect(subject.represent({}).value_for(:awesome)).to eq(condition_met: 'value')
120
127
  end
121
128
 
122
129
  it 'does not represent attributes, declared inside nested exposure, outside of it' do
@@ -139,7 +146,7 @@ describe Grape::Entity do
139
146
  )
140
147
  end
141
148
 
142
- it 'complex nested attributes' do
149
+ it 'merges complex nested attributes' do
143
150
  class ClassRoom < Grape::Entity
144
151
  expose(:parents, using: 'Parent') { |_| [{}, {}] }
145
152
  end
@@ -181,7 +188,44 @@ describe Grape::Entity do
181
188
  )
182
189
  end
183
190
 
184
- it 'is safe if its nested exposures are safe' do
191
+ it 'merges results of deeply nested double.root_exposures inside of nesting exposure' do
192
+ entity = Class.new(Grape::Entity) do
193
+ expose :data do
194
+ expose :something do
195
+ expose(:x) { |_| 'x' }
196
+ end
197
+ expose :something do
198
+ expose(:y) { |_| 'y' }
199
+ end
200
+ end
201
+ end
202
+ expect(entity.represent({}).serializable_hash).to eq(
203
+ data: {
204
+ something: {
205
+ x: 'x',
206
+ y: 'y'
207
+ }
208
+ }
209
+ )
210
+ end
211
+
212
+ it 'serializes deeply nested presenter exposures' do
213
+ e = Class.new(Grape::Entity) do
214
+ expose :f
215
+ end
216
+ subject.expose :a do
217
+ subject.expose :b do
218
+ subject.expose :c do
219
+ subject.expose :lol, using: e
220
+ end
221
+ end
222
+ end
223
+ expect(subject.represent(lol: { f: 123 }).serializable_hash).to eq(
224
+ a: { b: { c: { lol: { f: 123 } } } }
225
+ )
226
+ end
227
+
228
+ it 'is safe if its nested.root_exposures are safe' do
185
229
  subject.with_options safe: true do
186
230
  subject.expose :awesome do
187
231
  subject.expose(:nested) { |_| 'value' }
@@ -202,31 +246,31 @@ describe Grape::Entity do
202
246
  end
203
247
  end
204
248
 
205
- context 'inherited exposures' do
206
- it 'returns exposures from an ancestor' do
249
+ context 'inherited.root_exposures' do
250
+ it 'returns.root_exposures from an ancestor' do
207
251
  subject.expose :name, :email
208
252
  child_class = Class.new(subject)
209
253
 
210
- expect(child_class.exposures).to eq(subject.exposures)
254
+ expect(child_class.root_exposures).to eq(subject.root_exposures)
211
255
  end
212
256
 
213
- it 'returns exposures from multiple ancestor' do
257
+ it 'returns.root_exposures from multiple ancestor' do
214
258
  subject.expose :name, :email
215
259
  parent_class = Class.new(subject)
216
260
  child_class = Class.new(parent_class)
217
261
 
218
- expect(child_class.exposures).to eq(subject.exposures)
262
+ expect(child_class.root_exposures).to eq(subject.root_exposures)
219
263
  end
220
264
 
221
- it 'returns descendant exposures as a priority' do
265
+ it 'returns descendant.root_exposures as a priority' do
222
266
  subject.expose :name, :email
223
267
  child_class = Class.new(subject)
224
268
  child_class.expose :name do |_|
225
269
  'foo'
226
270
  end
227
271
 
228
- expect(subject.exposures[:name]).not_to have_key :proc
229
- expect(child_class.exposures[:name]).to have_key :proc
272
+ expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'bar')
273
+ expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'foo')
230
274
  end
231
275
  end
232
276
 
@@ -265,7 +309,7 @@ describe Grape::Entity do
265
309
  object = {}
266
310
 
267
311
  subject.expose(:size, format_with: ->(_value) { self.object.class.to_s })
268
- expect(subject.represent(object).send(:value_for, :size)).to eq object.class.to_s
312
+ expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
269
313
  end
270
314
 
271
315
  it 'formats an exposure with a :format_with symbol that returns a value from the entity instance' do
@@ -276,7 +320,7 @@ describe Grape::Entity do
276
320
  object = {}
277
321
 
278
322
  subject.expose(:size, format_with: :size_formatter)
279
- expect(subject.represent(object).send(:value_for, :size)).to eq object.class.to_s
323
+ expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
280
324
  end
281
325
 
282
326
  it 'works global on Grape::Entity' do
@@ -286,17 +330,17 @@ describe Grape::Entity do
286
330
  object = {}
287
331
 
288
332
  subject.expose(:size, format_with: :size_formatter)
289
- expect(subject.represent(object).send(:value_for, :size)).to eq object.class.to_s
333
+ expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
290
334
  end
291
335
  end
292
336
 
293
337
  it 'works global on Grape::Entity' do
294
338
  Grape::Entity.expose :a
295
339
  object = { a: 11, b: 22 }
296
- expect(Grape::Entity.represent(object).send(:value_for, :a)).to eq 11
340
+ expect(Grape::Entity.represent(object).value_for(:a)).to eq 11
297
341
  subject.expose :b
298
- expect(subject.represent(object).send(:value_for, :a)).to eq 11
299
- expect(subject.represent(object).send(:value_for, :b)).to eq 22
342
+ expect(subject.represent(object).value_for(:a)).to eq 11
343
+ expect(subject.represent(object).value_for(:b)).to eq 22
300
344
  Grape::Entity.unexpose :a
301
345
  end
302
346
  end
@@ -306,17 +350,20 @@ describe Grape::Entity do
306
350
  subject.expose :name, :email
307
351
  subject.unexpose :email
308
352
 
309
- expect(subject.exposures).to eq(name: {})
353
+ expect(subject.root_exposures.length).to eq 1
354
+ expect(subject.root_exposures[0].attribute).to eq :name
310
355
  end
311
356
 
312
- context 'inherited exposures' do
357
+ context 'inherited.root_exposures' do
313
358
  it 'when called from child class, only removes from the attribute from child' do
314
359
  subject.expose :name, :email
315
360
  child_class = Class.new(subject)
316
361
  child_class.unexpose :email
317
362
 
318
- expect(child_class.exposures).to eq(name: {})
319
- expect(subject.exposures).to eq(name: {}, email: {})
363
+ expect(child_class.root_exposures.length).to eq 1
364
+ expect(child_class.root_exposures[0].attribute).to eq :name
365
+ expect(subject.root_exposures[0].attribute).to eq :name
366
+ expect(subject.root_exposures[1].attribute).to eq :email
320
367
  end
321
368
 
322
369
  context 'when called from the parent class' do
@@ -325,17 +372,30 @@ describe Grape::Entity do
325
372
  child_class = Class.new(subject)
326
373
  subject.unexpose :email
327
374
 
328
- expect(subject.exposures).to eq(name: {})
329
- expect(child_class.exposures).to eq(name: {}, email: {})
375
+ expect(subject.root_exposures.length).to eq 1
376
+ expect(subject.root_exposures[0].attribute).to eq :name
377
+ expect(child_class.root_exposures[0].attribute).to eq :name
378
+ expect(child_class.root_exposures[1].attribute).to eq :email
330
379
  end
331
380
  end
332
381
  end
333
382
 
383
+ it 'does not allow unexposing inside of nesting exposures' do
384
+ expect do
385
+ Class.new(Grape::Entity) do
386
+ expose :something do
387
+ expose :x
388
+ unexpose :x
389
+ end
390
+ end
391
+ end.to raise_error(/You cannot call 'unexpose`/)
392
+ end
393
+
334
394
  it 'works global on Grape::Entity' do
335
- Grape::Entity.expose :a
336
- expect(Grape::Entity.exposures).to eq(a: {})
337
- Grape::Entity.unexpose :a
338
- expect(Grape::Entity.exposures).to eq({})
395
+ Grape::Entity.expose :x
396
+ expect(Grape::Entity.root_exposures[0].attribute).to eq(:x)
397
+ Grape::Entity.unexpose :x
398
+ expect(Grape::Entity.root_exposures).to eq([])
339
399
  end
340
400
  end
341
401
 
@@ -350,14 +410,16 @@ describe Grape::Entity do
350
410
  expect { subject.class_eval(&block) }.to raise_error ArgumentError
351
411
  end
352
412
 
353
- it 'applies the options to all exposures inside' do
413
+ it 'applies the options to all.root_exposures inside' do
354
414
  subject.class_eval do
355
415
  with_options(if: { awesome: true }) do
356
416
  expose :awesome_thing, using: 'Awesome'
357
417
  end
358
418
  end
359
419
 
360
- expect(subject.exposures[:awesome_thing]).to eq(if: { awesome: true }, using: 'Awesome')
420
+ exposure = subject.find_exposure(:awesome_thing)
421
+ expect(exposure.using_class_name).to eq('Awesome')
422
+ expect(exposure.conditions[0].cond_hash).to eq(awesome: true)
361
423
  end
362
424
 
363
425
  it 'allows for nested .with_options' do
@@ -369,7 +431,9 @@ describe Grape::Entity do
369
431
  end
370
432
  end
371
433
 
372
- expect(subject.exposures[:awesome_thing]).to eq(if: { awesome: true }, using: 'Something')
434
+ exposure = subject.find_exposure(:awesome_thing)
435
+ expect(exposure.using_class_name).to eq('Something')
436
+ expect(exposure.conditions[0].cond_hash).to eq(awesome: true)
373
437
  end
374
438
 
375
439
  it 'overrides nested :as option' do
@@ -379,7 +443,8 @@ describe Grape::Entity do
379
443
  end
380
444
  end
381
445
 
382
- expect(subject.exposures[:awesome_thing]).to eq(as: :extra_smooth)
446
+ exposure = subject.find_exposure(:awesome_thing)
447
+ expect(exposure.key).to eq :extra_smooth
383
448
  end
384
449
 
385
450
  it 'merges nested :if option' do
@@ -401,10 +466,11 @@ describe Grape::Entity do
401
466
  end
402
467
  end
403
468
 
404
- expect(subject.exposures[:awesome_thing]).to eq(
405
- if: { awesome: false, less_awesome: true },
406
- if_extras: [:awesome, match_proc]
407
- )
469
+ exposure = subject.find_exposure(:awesome_thing)
470
+ expect(exposure.conditions.any?(&:inversed?)).to be_falsey
471
+ expect(exposure.conditions[0].symbol).to eq(:awesome)
472
+ expect(exposure.conditions[1].block).to eq(match_proc)
473
+ expect(exposure.conditions[2].cond_hash).to eq(awesome: false, less_awesome: true)
408
474
  end
409
475
 
410
476
  it 'merges nested :unless option' do
@@ -426,10 +492,11 @@ describe Grape::Entity do
426
492
  end
427
493
  end
428
494
 
429
- expect(subject.exposures[:awesome_thing]).to eq(
430
- unless: { awesome: false, less_awesome: true },
431
- unless_extras: [:awesome, match_proc]
432
- )
495
+ exposure = subject.find_exposure(:awesome_thing)
496
+ expect(exposure.conditions.all?(&:inversed?)).to be_truthy
497
+ expect(exposure.conditions[0].symbol).to eq(:awesome)
498
+ expect(exposure.conditions[1].block).to eq(match_proc)
499
+ expect(exposure.conditions[2].cond_hash).to eq(awesome: false, less_awesome: true)
433
500
  end
434
501
 
435
502
  it 'overrides nested :using option' do
@@ -439,7 +506,8 @@ describe Grape::Entity do
439
506
  end
440
507
  end
441
508
 
442
- expect(subject.exposures[:awesome_thing]).to eq(using: 'SomethingElse')
509
+ exposure = subject.find_exposure(:awesome_thing)
510
+ expect(exposure.using_class_name).to eq('SomethingElse')
443
511
  end
444
512
 
445
513
  it 'aliases :with option to :using option' do
@@ -448,7 +516,9 @@ describe Grape::Entity do
448
516
  expose :awesome_thing, with: 'SomethingElse'
449
517
  end
450
518
  end
451
- expect(subject.exposures[:awesome_thing]).to eq(using: 'SomethingElse')
519
+
520
+ exposure = subject.find_exposure(:awesome_thing)
521
+ expect(exposure.using_class_name).to eq('SomethingElse')
452
522
  end
453
523
 
454
524
  it 'overrides nested :proc option' do
@@ -460,7 +530,8 @@ describe Grape::Entity do
460
530
  end
461
531
  end
462
532
 
463
- expect(subject.exposures[:awesome_thing]).to eq(proc: match_proc)
533
+ exposure = subject.find_exposure(:awesome_thing)
534
+ expect(exposure.block).to eq(match_proc)
464
535
  end
465
536
 
466
537
  it 'overrides nested :documentation option' do
@@ -470,7 +541,8 @@ describe Grape::Entity do
470
541
  end
471
542
  end
472
543
 
473
- expect(subject.exposures[:awesome_thing]).to eq(documentation: { desc: 'Other description.' })
544
+ exposure = subject.find_exposure(:awesome_thing)
545
+ expect(exposure.documentation).to eq(desc: 'Other description.')
474
546
  end
475
547
  end
476
548
 
@@ -587,6 +659,22 @@ describe Grape::Entity do
587
659
  representation = subject.represent(object, except: [:id, 'address', { user: [:id, 'name'] }], serializable: true)
588
660
  expect(representation).to eq(name: nil, phone: nil, user: { email: nil })
589
661
  end
662
+
663
+ context 'with nested attributes' do
664
+ before do
665
+ subject.expose :additional do
666
+ subject.expose :something
667
+ end
668
+ end
669
+
670
+ it 'preserves nesting' do
671
+ expect(subject.represent({ something: 123 }, only: [{ additional: [:something] }], serializable: true)).to eq(
672
+ additional: {
673
+ something: 123
674
+ }
675
+ )
676
+ end
677
+ end
590
678
  end
591
679
 
592
680
  it 'can specify children attributes with only' do
@@ -644,6 +732,78 @@ describe Grape::Entity do
644
732
  representation = subject.represent(OpenStruct.new, condition: true, except: [:phone, :mobile_phone], serializable: true)
645
733
  expect(representation).to eq(id: nil, name: nil)
646
734
  end
735
+
736
+ it 'choses proper exposure according to condition' do
737
+ strategy1 = ->(_obj, _opts) { 'foo' }
738
+ strategy2 = ->(_obj, _opts) { 'bar' }
739
+
740
+ subject.expose :id, proc: strategy1
741
+ subject.expose :id, proc: strategy2
742
+ expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'bar')
743
+ expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
744
+
745
+ subject.unexpose_all
746
+
747
+ subject.expose :id, proc: strategy1, if: :condition
748
+ subject.expose :id, proc: strategy2
749
+ expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'bar')
750
+ expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
751
+
752
+ subject.unexpose_all
753
+
754
+ subject.expose :id, proc: strategy1
755
+ subject.expose :id, proc: strategy2, if: :condition
756
+ expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'foo')
757
+ expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
758
+
759
+ subject.unexpose_all
760
+
761
+ subject.expose :id, proc: strategy1, if: :condition1
762
+ subject.expose :id, proc: strategy2, if: :condition2
763
+ expect(subject.represent({}, condition1: false, condition2: false, serializable: true)).to eq({})
764
+ expect(subject.represent({}, condition1: false, condition2: true, serializable: true)).to eq(id: 'bar')
765
+ expect(subject.represent({}, condition1: true, condition2: false, serializable: true)).to eq(id: 'foo')
766
+ expect(subject.represent({}, condition1: true, condition2: true, serializable: true)).to eq(id: 'bar')
767
+ end
768
+
769
+ it 'does not merge nested exposures with plain hashes' do
770
+ subject.expose(:id)
771
+ subject.expose(:info, if: :condition1) do
772
+ subject.expose :a, :b
773
+ subject.expose(:additional, if: :condition2) do |_obj, _opts|
774
+ {
775
+ x: 11, y: 22, c: 123
776
+ }
777
+ end
778
+ end
779
+ subject.expose(:info, if: :condition2) do
780
+ subject.expose(:additional) do
781
+ subject.expose :c
782
+ end
783
+ end
784
+ subject.expose(:d, as: :info, if: :condition3)
785
+
786
+ obj = { id: 123, a: 1, b: 2, c: 3, d: 4 }
787
+
788
+ expect(subject.represent(obj, serializable: true)).to eq(id: 123)
789
+ expect(subject.represent(obj, condition1: true, serializable: true)).to eq(id: 123, info: { a: 1, b: 2 })
790
+ expect(subject.represent(obj, condition2: true, serializable: true)).to eq(
791
+ id: 123,
792
+ info: {
793
+ additional: {
794
+ c: 3
795
+ }
796
+ }
797
+ )
798
+ expect(subject.represent(obj, condition1: true, condition2: true, serializable: true)).to eq(
799
+ id: 123,
800
+ info: {
801
+ a: 1, b: 2, additional: { c: 3 }
802
+ }
803
+ )
804
+ expect(subject.represent(obj, condition3: true, serializable: true)).to eq(id: 123, info: 4)
805
+ expect(subject.represent(obj, condition1: true, condition2: true, condition3: true, serializable: true)).to eq(id: 123, info: 4)
806
+ end
647
807
  end
648
808
 
649
809
  context 'attribute with alias' do
@@ -856,6 +1016,11 @@ describe Grape::Entity do
856
1016
  friends: [
857
1017
  double(name: 'Friend 1', email: 'friend1@example.com', characteristics: [], fantasies: [], birthday: Time.gm(2012, 2, 27), friends: []),
858
1018
  double(name: 'Friend 2', email: 'friend2@example.com', characteristics: [], fantasies: [], birthday: Time.gm(2012, 2, 27), friends: [])
1019
+ ],
1020
+ extra: { key: 'foo', value: 'bar' },
1021
+ nested: [
1022
+ { name: 'n1', data: { key: 'ex1', value: 'v1' } },
1023
+ { name: 'n2', data: { key: 'ex2', value: 'v2' } }
859
1024
  ]
860
1025
  }
861
1026
  end
@@ -889,7 +1054,7 @@ describe Grape::Entity do
889
1054
  expect(fresh_class.new(some_class.new).serializable_hash).to eq(name: true)
890
1055
  end
891
1056
 
892
- it "does expose attributes that don't exist on the object as nil" do
1057
+ it "does expose attributes that don't exist on the object" do
893
1058
  fresh_class.expose :email, :nonexistent_attribute, :name, safe: true
894
1059
 
895
1060
  res = fresh_class.new(model).serializable_hash
@@ -898,6 +1063,13 @@ describe Grape::Entity do
898
1063
  expect(res).to have_key :name
899
1064
  end
900
1065
 
1066
+ it "does expose attributes that don't exist on the object as nil" do
1067
+ fresh_class.expose :email, :nonexistent_attribute, :name, safe: true
1068
+
1069
+ res = fresh_class.new(model).serializable_hash
1070
+ expect(res[:nonexistent_attribute]).to eq(nil)
1071
+ end
1072
+
901
1073
  it 'does expose attributes marked as safe if model is a hash object' do
902
1074
  fresh_class.expose :name, safe: true
903
1075
 
@@ -1018,6 +1190,108 @@ describe Grape::Entity do
1018
1190
  expect(presenter.serializable_hash).to eq(name: 'abc', embedded: { a: nil, b: { abc: 'def' } })
1019
1191
  end
1020
1192
  end
1193
+
1194
+ context '#attr_path' do
1195
+ it 'for all kinds of attributes' do
1196
+ module EntitySpec
1197
+ class EmailEntity < Grape::Entity
1198
+ expose(:email, as: :addr) { |_, o| o[:attr_path].join('/') }
1199
+ end
1200
+
1201
+ class UserEntity < Grape::Entity
1202
+ expose(:name, as: :full_name) { |_, o| o[:attr_path].join('/') }
1203
+ expose :email, using: 'EntitySpec::EmailEntity'
1204
+ end
1205
+
1206
+ class ExtraEntity < Grape::Entity
1207
+ expose(:key) { |_, o| o[:attr_path].join('/') }
1208
+ expose(:value) { |_, o| o[:attr_path].join('/') }
1209
+ end
1210
+
1211
+ class NestedEntity < Grape::Entity
1212
+ expose(:name) { |_, o| o[:attr_path].join('/') }
1213
+ expose :data, using: 'EntitySpec::ExtraEntity'
1214
+ end
1215
+ end
1216
+
1217
+ fresh_class.class_eval do
1218
+ expose(:id) { |_, o| o[:attr_path].join('/') }
1219
+ expose(:foo, as: :bar) { |_, o| o[:attr_path].join('/') }
1220
+ expose :title do
1221
+ expose :full do
1222
+ expose(:prefix, as: :pref) { |_, o| o[:attr_path].join('/') }
1223
+ expose(:main) { |_, o| o[:attr_path].join('/') }
1224
+ end
1225
+ end
1226
+ expose :friends, as: :social, using: 'EntitySpec::UserEntity'
1227
+ expose :extra, using: 'EntitySpec::ExtraEntity'
1228
+ expose :nested, using: 'EntitySpec::NestedEntity'
1229
+ end
1230
+
1231
+ expect(subject.serializable_hash).to eq(
1232
+ id: 'id',
1233
+ bar: 'bar',
1234
+ title: { full: { pref: 'title/full/pref', main: 'title/full/main' } },
1235
+ social: [
1236
+ { full_name: 'social/full_name', email: { addr: 'social/email/addr' } },
1237
+ { full_name: 'social/full_name', email: { addr: 'social/email/addr' } }
1238
+ ],
1239
+ extra: { key: 'extra/key', value: 'extra/value' },
1240
+ nested: [
1241
+ { name: 'nested/name', data: { key: 'nested/data/key', value: 'nested/data/value' } },
1242
+ { name: 'nested/name', data: { key: 'nested/data/key', value: 'nested/data/value' } }
1243
+ ]
1244
+ )
1245
+ end
1246
+
1247
+ it 'allows customize path of an attribute' do
1248
+ module EntitySpec
1249
+ class CharacterEntity < Grape::Entity
1250
+ expose(:key) { |_, o| o[:attr_path].join('/') }
1251
+ expose(:value) { |_, o| o[:attr_path].join('/') }
1252
+ end
1253
+ end
1254
+
1255
+ fresh_class.class_eval do
1256
+ expose :characteristics, using: EntitySpec::CharacterEntity,
1257
+ attr_path: ->(_obj, _opts) { :character }
1258
+ end
1259
+
1260
+ expect(subject.serializable_hash).to eq(
1261
+ characteristics: [
1262
+ { key: 'character/key', value: 'character/value' }
1263
+ ]
1264
+ )
1265
+ end
1266
+
1267
+ it 'can drop one nest level by set path_for to nil' do
1268
+ module EntitySpec
1269
+ class NoPathCharacterEntity < Grape::Entity
1270
+ expose(:key) { |_, o| o[:attr_path].join('/') }
1271
+ expose(:value) { |_, o| o[:attr_path].join('/') }
1272
+ end
1273
+ end
1274
+
1275
+ fresh_class.class_eval do
1276
+ expose :characteristics, using: EntitySpec::NoPathCharacterEntity, attr_path: proc { nil }
1277
+ end
1278
+
1279
+ expect(subject.serializable_hash).to eq(
1280
+ characteristics: [
1281
+ { key: 'key', value: 'value' }
1282
+ ]
1283
+ )
1284
+ end
1285
+ end
1286
+
1287
+ context 'with projections passed in options' do
1288
+ it 'allows to pass different :only and :except params using the same instance' do
1289
+ fresh_class.expose :a, :b, :c
1290
+ presenter = fresh_class.new(a: 1, b: 2, c: 3)
1291
+ expect(presenter.serializable_hash(only: [:a, :b])).to eq(a: 1, b: 2)
1292
+ expect(presenter.serializable_hash(only: [:b, :c])).to eq(b: 2, c: 3)
1293
+ end
1294
+ end
1021
1295
  end
1022
1296
 
1023
1297
  describe '#value_for' do
@@ -1040,17 +1314,19 @@ describe Grape::Entity do
1040
1314
  end
1041
1315
 
1042
1316
  it 'passes through bare expose attributes' do
1043
- expect(subject.send(:value_for, :name)).to eq attributes[:name]
1317
+ expect(subject.value_for(:name)).to eq attributes[:name]
1044
1318
  end
1045
1319
 
1046
1320
  it 'instantiates a representation if that is called for' do
1047
- rep = subject.send(:value_for, :friends)
1321
+ rep = subject.value_for(:friends)
1048
1322
  expect(rep.reject { |r| r.is_a?(fresh_class) }).to be_empty
1049
1323
  expect(rep.first.serializable_hash[:name]).to eq 'Friend 1'
1050
1324
  expect(rep.last.serializable_hash[:name]).to eq 'Friend 2'
1051
1325
  end
1052
1326
 
1053
1327
  context 'child representations' do
1328
+ after { EntitySpec::FriendEntity.unexpose_all }
1329
+
1054
1330
  it 'disables root key name for child representations' do
1055
1331
  module EntitySpec
1056
1332
  class FriendEntity < Grape::Entity
@@ -1063,7 +1339,7 @@ describe Grape::Entity do
1063
1339
  expose :friends, using: EntitySpec::FriendEntity
1064
1340
  end
1065
1341
 
1066
- rep = subject.send(:value_for, :friends)
1342
+ rep = subject.value_for(:friends)
1067
1343
  expect(rep).to be_kind_of Array
1068
1344
  expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
1069
1345
  expect(rep.first.serializable_hash[:name]).to eq 'Friend 1'
@@ -1084,7 +1360,7 @@ describe Grape::Entity do
1084
1360
  end
1085
1361
  end
1086
1362
 
1087
- rep = subject.send(:value_for, :custom_friends)
1363
+ rep = subject.value_for(:custom_friends)
1088
1364
  expect(rep).to be_kind_of Array
1089
1365
  expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
1090
1366
  expect(rep.first.serializable_hash).to eq(name: 'Friend 1', email: 'friend1@example.com')
@@ -1105,7 +1381,7 @@ describe Grape::Entity do
1105
1381
  end
1106
1382
  end
1107
1383
 
1108
- rep = subject.send(:value_for, :first_friend)
1384
+ rep = subject.value_for(:first_friend)
1109
1385
  expect(rep).to be_kind_of EntitySpec::FriendEntity
1110
1386
  expect(rep.serializable_hash).to eq(name: 'Friend 1', email: 'friend1@example.com')
1111
1387
  end
@@ -1123,7 +1399,7 @@ describe Grape::Entity do
1123
1399
  end
1124
1400
  end
1125
1401
 
1126
- rep = subject.send(:value_for, :first_friend)
1402
+ rep = subject.value_for(:first_friend)
1127
1403
  expect(rep).to be_kind_of EntitySpec::FriendEntity
1128
1404
  expect(rep.serializable_hash).to be_nil
1129
1405
  end
@@ -1140,7 +1416,7 @@ describe Grape::Entity do
1140
1416
  expose :characteristics, using: EntitySpec::CharacteristicsEntity
1141
1417
  end
1142
1418
 
1143
- rep = subject.send(:value_for, :characteristics)
1419
+ rep = subject.value_for(:characteristics)
1144
1420
  expect(rep).to be_kind_of Array
1145
1421
  expect(rep.reject { |r| r.is_a?(EntitySpec::CharacteristicsEntity) }).to be_empty
1146
1422
  expect(rep.first.serializable_hash[:key]).to eq 'hair_color'
@@ -1160,13 +1436,13 @@ describe Grape::Entity do
1160
1436
  expose :friends, using: EntitySpec::FriendEntity
1161
1437
  end
1162
1438
 
1163
- rep = subject.send(:value_for, :friends)
1439
+ rep = subject.value_for(:friends)
1164
1440
  expect(rep).to be_kind_of Array
1165
1441
  expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
1166
1442
  expect(rep.first.serializable_hash[:email]).to be_nil
1167
1443
  expect(rep.last.serializable_hash[:email]).to be_nil
1168
1444
 
1169
- rep = subject.send(:value_for, :friends, user_type: :admin)
1445
+ rep = subject.value_for(:friends, Grape::Entity::Options.new(user_type: :admin))
1170
1446
  expect(rep).to be_kind_of Array
1171
1447
  expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
1172
1448
  expect(rep.first.serializable_hash[:email]).to eq 'friend1@example.com'
@@ -1186,7 +1462,7 @@ describe Grape::Entity do
1186
1462
  expose :friends, using: EntitySpec::FriendEntity
1187
1463
  end
1188
1464
 
1189
- rep = subject.send(:value_for, :friends, collection: false)
1465
+ rep = subject.value_for(:friends, Grape::Entity::Options.new(collection: false))
1190
1466
  expect(rep).to be_kind_of Array
1191
1467
  expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
1192
1468
  expect(rep.first.serializable_hash[:email]).to eq 'friend1@example.com'
@@ -1195,15 +1471,15 @@ describe Grape::Entity do
1195
1471
  end
1196
1472
 
1197
1473
  it 'calls through to the proc if there is one' do
1198
- expect(subject.send(:value_for, :computed, awesome: 123)).to eq 123
1474
+ expect(subject.value_for(:computed, Grape::Entity::Options.new(awesome: 123))).to eq 123
1199
1475
  end
1200
1476
 
1201
1477
  it 'returns a formatted value if format_with is passed' do
1202
- expect(subject.send(:value_for, :birthday)).to eq '02/27/2012'
1478
+ expect(subject.value_for(:birthday)).to eq '02/27/2012'
1203
1479
  end
1204
1480
 
1205
1481
  it 'returns a formatted value if format_with is passed a lambda' do
1206
- expect(subject.send(:value_for, :fantasies)).to eq ['Nessy', 'Double Rainbows', 'Unicorns']
1482
+ expect(subject.value_for(:fantasies)).to eq ['Nessy', 'Double Rainbows', 'Unicorns']
1207
1483
  end
1208
1484
 
1209
1485
  it 'tries instance methods on the entity first' do
@@ -1223,12 +1499,12 @@ describe Grape::Entity do
1223
1499
 
1224
1500
  friend = double('Friend', name: 'joe', email: 'joe@example.com')
1225
1501
  rep = EntitySpec::DelegatingEntity.new(friend)
1226
- expect(rep.send(:value_for, :name)).to eq 'cooler name'
1227
- expect(rep.send(:value_for, :email)).to eq 'joe@example.com'
1502
+ expect(rep.value_for(:name)).to eq 'cooler name'
1503
+ expect(rep.value_for(:email)).to eq 'joe@example.com'
1228
1504
 
1229
1505
  another_friend = double('Friend', email: 'joe@example.com')
1230
1506
  rep = EntitySpec::DelegatingEntity.new(another_friend)
1231
- expect(rep.send(:value_for, :name)).to eq 'cooler name'
1507
+ expect(rep.value_for(:name)).to eq 'cooler name'
1232
1508
  end
1233
1509
 
1234
1510
  context 'using' do
@@ -1244,7 +1520,7 @@ describe Grape::Entity do
1244
1520
  expose :friends, using: 'EntitySpec::UserEntity'
1245
1521
  end
1246
1522
 
1247
- rep = subject.send(:value_for, :friends)
1523
+ rep = subject.value_for(:friends)
1248
1524
  expect(rep).to be_kind_of Array
1249
1525
  expect(rep.size).to eq 2
1250
1526
  expect(rep.all? { |r| r.is_a?(EntitySpec::UserEntity) }).to be true
@@ -1255,7 +1531,7 @@ describe Grape::Entity do
1255
1531
  expose :friends, using: EntitySpec::UserEntity
1256
1532
  end
1257
1533
 
1258
- rep = subject.send(:value_for, :friends)
1534
+ rep = subject.value_for(:friends)
1259
1535
  expect(rep).to be_kind_of Array
1260
1536
  expect(rep.size).to eq 2
1261
1537
  expect(rep.all? { |r| r.is_a?(EntitySpec::UserEntity) }).to be true
@@ -1263,7 +1539,7 @@ describe Grape::Entity do
1263
1539
  end
1264
1540
  end
1265
1541
 
1266
- describe '#documentation' do
1542
+ describe '.documentation' do
1267
1543
  it 'returns an empty hash is no documentation is provided' do
1268
1544
  fresh_class.expose :name
1269
1545
 
@@ -1288,6 +1564,18 @@ describe Grape::Entity do
1288
1564
  expect(subject.documentation).to eq(label: doc, email: doc)
1289
1565
  end
1290
1566
 
1567
+ it 'resets memoization when exposing additional attributes' do
1568
+ fresh_class.expose :x, documentation: { desc: 'just x' }
1569
+ expect(fresh_class.instance_variable_get(:@documentation)).to be_nil
1570
+ doc1 = fresh_class.documentation
1571
+ expect(fresh_class.instance_variable_get(:@documentation)).not_to be_nil
1572
+ fresh_class.expose :y, documentation: { desc: 'just y' }
1573
+ expect(fresh_class.instance_variable_get(:@documentation)).to be_nil
1574
+ doc2 = fresh_class.documentation
1575
+ expect(doc1).to eq(x: { desc: 'just x' })
1576
+ expect(doc2).to eq(x: { desc: 'just x' }, y: { desc: 'just y' })
1577
+ end
1578
+
1291
1579
  context 'inherited documentation' do
1292
1580
  it 'returns documentation from ancestor' do
1293
1581
  doc = { type: 'foo', desc: 'bar' }
@@ -1326,78 +1614,14 @@ describe Grape::Entity do
1326
1614
  expect(child_class.documentation).to eq(name: doc)
1327
1615
  expect(nephew_class.documentation).to eq(name: doc, email: new_doc)
1328
1616
  end
1329
- end
1330
- end
1331
-
1332
- describe '#key_for' do
1333
- it 'returns the attribute if no :as is set' do
1334
- fresh_class.expose :name
1335
- expect(subject.class.send(:key_for, :name)).to eq :name
1336
- end
1337
1617
 
1338
- it 'returns a symbolized version of the attribute' do
1339
- fresh_class.expose :name
1340
- expect(subject.class.send(:key_for, 'name')).to eq :name
1341
- end
1342
-
1343
- it 'returns the :as alias if one exists' do
1344
- fresh_class.expose :name, as: :nombre
1345
- expect(subject.class.send(:key_for, 'name')).to eq :nombre
1346
- end
1347
- end
1348
-
1349
- describe '#conditions_met?' do
1350
- it 'only passes through hash :if exposure if all attributes match' do
1351
- exposure_options = { if: { condition1: true, condition2: true } }
1352
-
1353
- expect(subject.send(:conditions_met?, exposure_options, {})).to be false
1354
- expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be false
1355
- expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true)).to be true
1356
- expect(subject.send(:conditions_met?, exposure_options, condition1: false, condition2: true)).to be false
1357
- expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true, other: true)).to be true
1358
- end
1359
-
1360
- it 'looks for presence/truthiness if a symbol is passed' do
1361
- exposure_options = { if: :condition1 }
1362
-
1363
- expect(subject.send(:conditions_met?, exposure_options, {})).to be false
1364
- expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be true
1365
- expect(subject.send(:conditions_met?, exposure_options, condition1: false)).to be false
1366
- expect(subject.send(:conditions_met?, exposure_options, condition1: nil)).to be false
1367
- end
1368
-
1369
- it 'looks for absence/falsiness if a symbol is passed' do
1370
- exposure_options = { unless: :condition1 }
1371
-
1372
- expect(subject.send(:conditions_met?, exposure_options, {})).to be true
1373
- expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be false
1374
- expect(subject.send(:conditions_met?, exposure_options, condition1: false)).to be true
1375
- expect(subject.send(:conditions_met?, exposure_options, condition1: nil)).to be true
1376
- end
1377
-
1378
- it 'only passes through proc :if exposure if it returns truthy value' do
1379
- exposure_options = { if: ->(_, opts) { opts[:true] } }
1380
-
1381
- expect(subject.send(:conditions_met?, exposure_options, true: false)).to be false
1382
- expect(subject.send(:conditions_met?, exposure_options, true: true)).to be true
1383
- end
1384
-
1385
- it 'only passes through hash :unless exposure if any attributes do not match' do
1386
- exposure_options = { unless: { condition1: true, condition2: true } }
1387
-
1388
- expect(subject.send(:conditions_met?, exposure_options, {})).to be true
1389
- expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be false
1390
- expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true)).to be false
1391
- expect(subject.send(:conditions_met?, exposure_options, condition1: false, condition2: true)).to be false
1392
- expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true, other: true)).to be false
1393
- expect(subject.send(:conditions_met?, exposure_options, condition1: false, condition2: false)).to be true
1394
- end
1395
-
1396
- it 'only passes through proc :unless exposure if it returns falsy value' do
1397
- exposure_options = { unless: ->(_, opts) { opts[:true] == true } }
1398
-
1399
- expect(subject.send(:conditions_met?, exposure_options, true: false)).to be true
1400
- expect(subject.send(:conditions_met?, exposure_options, true: true)).to be false
1618
+ it 'includes only root exposures' do
1619
+ fresh_class.expose :name, documentation: { desc: 'foo' }
1620
+ fresh_class.expose :nesting do
1621
+ fresh_class.expose :smth, documentation: { desc: 'should not be seen' }
1622
+ end
1623
+ expect(fresh_class.documentation).to eq(name: { desc: 'foo' })
1624
+ end
1401
1625
  end
1402
1626
  end
1403
1627
 
@@ -1418,19 +1642,19 @@ describe Grape::Entity do
1418
1642
  expose :name
1419
1643
  end
1420
1644
 
1421
- expect(subject.entity_class.exposures).not_to be_empty
1645
+ expect(subject.entity_class.root_exposures).not_to be_empty
1422
1646
  end
1423
1647
 
1424
1648
  it 'is able to expose straight from the class' do
1425
1649
  subject.entity :name, :email
1426
- expect(subject.entity_class.exposures.size).to eq 2
1650
+ expect(subject.entity_class.root_exposures.size).to eq 2
1427
1651
  end
1428
1652
 
1429
- it 'is able to mix field and advanced exposures' do
1653
+ it 'is able to mix field and advanced.root_exposures' do
1430
1654
  subject.entity :name, :email do
1431
1655
  expose :third
1432
1656
  end
1433
- expect(subject.entity_class.exposures.size).to eq 3
1657
+ expect(subject.entity_class.root_exposures.size).to eq 3
1434
1658
  end
1435
1659
 
1436
1660
  context 'instance' do