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