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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +12 -12
- data/CHANGELOG.md +18 -1
- data/README.md +30 -4
- data/Rakefile +1 -1
- data/lib/grape_entity.rb +6 -2
- data/lib/grape_entity/condition.rb +26 -0
- data/lib/grape_entity/condition/base.rb +35 -0
- data/lib/grape_entity/condition/block_condition.rb +21 -0
- data/lib/grape_entity/condition/hash_condition.rb +25 -0
- data/lib/grape_entity/condition/symbol_condition.rb +21 -0
- data/lib/grape_entity/entity.rb +85 -238
- data/lib/grape_entity/exposure.rb +77 -0
- data/lib/grape_entity/exposure/base.rb +118 -0
- data/lib/grape_entity/exposure/block_exposure.rb +29 -0
- data/lib/grape_entity/exposure/delegator_exposure.rb +11 -0
- data/lib/grape_entity/exposure/formatter_block_exposure.rb +25 -0
- data/lib/grape_entity/exposure/formatter_exposure.rb +30 -0
- data/lib/grape_entity/exposure/nesting_exposure.rb +128 -0
- data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +70 -0
- data/lib/grape_entity/exposure/represent_exposure.rb +47 -0
- data/lib/grape_entity/options.rb +142 -0
- data/lib/grape_entity/version.rb +1 -1
- data/spec/grape_entity/entity_spec.rb +378 -154
- data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +34 -0
- data/spec/grape_entity/exposure_spec.rb +90 -0
- metadata +47 -26
@@ -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
|
data/lib/grape_entity/version.rb
CHANGED
@@ -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.
|
14
|
+
expect(subject.root_exposures.size).to eq 3
|
15
15
|
end
|
16
16
|
|
17
|
-
it 'sets the same options for all
|
17
|
+
it 'sets the same options for all.root_exposures passed' do
|
18
18
|
subject.expose :name, :email, :location, documentation: true
|
19
|
-
subject.
|
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).
|
64
|
+
value = subject.represent(object).value_for(:bogus)
|
65
65
|
expect(value).to be_instance_of EntitySpec::BogusEntity
|
66
66
|
|
67
|
-
prop1 = value.
|
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
|
-
|
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({}).
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
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({}).
|
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
|
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({}).
|
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 '
|
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
|
206
|
-
it 'returns
|
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.
|
254
|
+
expect(child_class.root_exposures).to eq(subject.root_exposures)
|
211
255
|
end
|
212
256
|
|
213
|
-
it 'returns
|
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.
|
262
|
+
expect(child_class.root_exposures).to eq(subject.root_exposures)
|
219
263
|
end
|
220
264
|
|
221
|
-
it 'returns descendant
|
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.
|
229
|
-
expect(child_class.
|
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).
|
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).
|
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).
|
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).
|
340
|
+
expect(Grape::Entity.represent(object).value_for(:a)).to eq 11
|
297
341
|
subject.expose :b
|
298
|
-
expect(subject.represent(object).
|
299
|
-
expect(subject.represent(object).
|
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.
|
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
|
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.
|
319
|
-
expect(
|
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.
|
329
|
-
expect(
|
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 :
|
336
|
-
expect(Grape::Entity.
|
337
|
-
Grape::Entity.unexpose :
|
338
|
-
expect(Grape::Entity.
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
405
|
-
|
406
|
-
|
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
|
-
|
430
|
-
|
431
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
1227
|
-
expect(rep.
|
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.
|
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.
|
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.
|
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 '
|
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
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
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.
|
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.
|
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
|
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.
|
1657
|
+
expect(subject.entity_class.root_exposures.size).to eq 3
|
1434
1658
|
end
|
1435
1659
|
|
1436
1660
|
context 'instance' do
|