grape-entity 0.4.8 → 0.5.0

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.
@@ -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