grape-entity 0.6.1 → 0.7.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 +5 -5
- data/.coveralls.yml +1 -0
- data/.gitignore +5 -1
- data/.rubocop.yml +31 -0
- data/.rubocop_todo.yml +29 -16
- data/.travis.yml +15 -10
- data/CHANGELOG.md +18 -0
- data/Dangerfile +2 -0
- data/Gemfile +6 -1
- data/README.md +82 -1
- data/Rakefile +2 -2
- data/bench/serializing.rb +2 -0
- data/grape-entity.gemspec +9 -7
- data/lib/grape-entity.rb +2 -0
- data/lib/grape_entity.rb +2 -0
- data/lib/grape_entity/condition.rb +20 -11
- data/lib/grape_entity/condition/base.rb +2 -0
- data/lib/grape_entity/condition/block_condition.rb +3 -1
- data/lib/grape_entity/condition/hash_condition.rb +2 -0
- data/lib/grape_entity/condition/symbol_condition.rb +2 -0
- data/lib/grape_entity/delegator.rb +10 -9
- data/lib/grape_entity/delegator/base.rb +2 -0
- data/lib/grape_entity/delegator/fetchable_object.rb +2 -0
- data/lib/grape_entity/delegator/hash_object.rb +2 -0
- data/lib/grape_entity/delegator/openstruct_object.rb +2 -0
- data/lib/grape_entity/delegator/plain_object.rb +2 -0
- data/lib/grape_entity/entity.rb +49 -29
- data/lib/grape_entity/exposure.rb +58 -41
- data/lib/grape_entity/exposure/base.rb +14 -3
- data/lib/grape_entity/exposure/block_exposure.rb +2 -0
- data/lib/grape_entity/exposure/delegator_exposure.rb +2 -0
- data/lib/grape_entity/exposure/formatter_block_exposure.rb +2 -0
- data/lib/grape_entity/exposure/formatter_exposure.rb +2 -0
- data/lib/grape_entity/exposure/nesting_exposure.rb +34 -30
- data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +23 -14
- data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +5 -2
- data/lib/grape_entity/exposure/represent_exposure.rb +3 -1
- data/lib/grape_entity/options.rb +41 -56
- data/lib/grape_entity/version.rb +3 -1
- data/spec/grape_entity/entity_spec.rb +202 -47
- data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +6 -4
- data/spec/grape_entity/exposure/represent_exposure_spec.rb +2 -0
- data/spec/grape_entity/exposure_spec.rb +14 -2
- data/spec/grape_entity/hash_spec.rb +2 -0
- data/spec/grape_entity/options_spec.rb +66 -0
- data/spec/spec_helper.rb +11 -0
- metadata +28 -39
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
class Entity
|
3
5
|
module Exposure
|
@@ -29,45 +31,33 @@ module Grape
|
|
29
31
|
end
|
30
32
|
|
31
33
|
def value(entity, options)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
normalized_exposures(entity, new_options).each_with_object(output) do |exposure, out|
|
36
|
-
exposure.with_attr_path(entity, new_options) do
|
37
|
-
result = exposure.value(entity, new_options)
|
38
|
-
out.add(exposure, result)
|
39
|
-
end
|
34
|
+
map_entity_exposures(entity, options) do |exposure, nested_options|
|
35
|
+
exposure.value(entity, nested_options)
|
40
36
|
end
|
41
37
|
end
|
42
38
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
result = nil
|
47
|
-
normalized_exposures(entity, new_options).select { |e| e.key == key }.each do |exposure|
|
48
|
-
exposure.with_attr_path(entity, new_options) do
|
49
|
-
result = exposure.valid_value(entity, new_options)
|
50
|
-
end
|
39
|
+
def serializable_value(entity, options)
|
40
|
+
map_entity_exposures(entity, options) do |exposure, nested_options|
|
41
|
+
exposure.serializable_value(entity, nested_options)
|
51
42
|
end
|
52
|
-
result
|
53
43
|
end
|
54
44
|
|
55
|
-
def
|
45
|
+
def valid_value_for(key, entity, options)
|
56
46
|
new_options = nesting_options_for(options)
|
57
|
-
output = OutputBuilder.new
|
58
47
|
|
59
|
-
normalized_exposures(entity, new_options).
|
48
|
+
key_exposures = normalized_exposures(entity, new_options).select { |e| e.key(entity) == key }
|
49
|
+
|
50
|
+
key_exposures.map do |exposure|
|
60
51
|
exposure.with_attr_path(entity, new_options) do
|
61
|
-
|
62
|
-
out.add(exposure, result)
|
52
|
+
exposure.valid_value(entity, new_options)
|
63
53
|
end
|
64
|
-
end
|
54
|
+
end.last
|
65
55
|
end
|
66
56
|
|
67
57
|
# if we have any nesting exposures with the same name.
|
68
|
-
# delegate :deep_complex_nesting
|
69
|
-
def deep_complex_nesting?
|
70
|
-
nested_exposures.deep_complex_nesting?
|
58
|
+
# delegate :deep_complex_nesting?(entity), to: :nested_exposures
|
59
|
+
def deep_complex_nesting?(entity)
|
60
|
+
nested_exposures.deep_complex_nesting?(entity)
|
71
61
|
end
|
72
62
|
|
73
63
|
private
|
@@ -90,15 +80,15 @@ module Grape
|
|
90
80
|
|
91
81
|
# This method 'merges' subsequent nesting exposures with the same name if it's needed
|
92
82
|
def normalized_exposures(entity, options)
|
93
|
-
return easy_normalized_exposures(entity, options) unless deep_complex_nesting? # optimization
|
83
|
+
return easy_normalized_exposures(entity, options) unless deep_complex_nesting?(entity) # optimization
|
94
84
|
|
95
85
|
table = nested_exposures.each_with_object({}) do |exposure, output|
|
96
86
|
should_expose = exposure.with_attr_path(entity, options) do
|
97
87
|
exposure.should_expose?(entity, options)
|
98
88
|
end
|
99
89
|
next unless should_expose
|
100
|
-
output[exposure.key] ||= []
|
101
|
-
output[exposure.key] << exposure
|
90
|
+
output[exposure.key(entity)] ||= []
|
91
|
+
output[exposure.key(entity)] << exposure
|
102
92
|
end
|
103
93
|
table.map do |key, exposures|
|
104
94
|
last_exposure = exposures.last
|
@@ -111,13 +101,27 @@ module Grape
|
|
111
101
|
end
|
112
102
|
new_nested_exposures = nesting_tail.flat_map(&:nested_exposures)
|
113
103
|
NestingExposure.new(key, {}, [], new_nested_exposures).tap do |new_exposure|
|
114
|
-
|
104
|
+
if nesting_tail.any? { |exposure| exposure.deep_complex_nesting?(entity) }
|
105
|
+
new_exposure.instance_variable_set(:@deep_complex_nesting, true)
|
106
|
+
end
|
115
107
|
end
|
116
108
|
else
|
117
109
|
last_exposure
|
118
110
|
end
|
119
111
|
end
|
120
112
|
end
|
113
|
+
|
114
|
+
def map_entity_exposures(entity, options)
|
115
|
+
new_options = nesting_options_for(options)
|
116
|
+
output = OutputBuilder.new(entity)
|
117
|
+
|
118
|
+
normalized_exposures(entity, new_options).each_with_object(output) do |exposure, out|
|
119
|
+
exposure.with_attr_path(entity, new_options) do
|
120
|
+
result = yield(exposure, new_options)
|
121
|
+
out.add(exposure, result)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
121
125
|
end
|
122
126
|
end
|
123
127
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
class Entity
|
3
5
|
module Exposure
|
@@ -14,6 +16,10 @@ module Grape
|
|
14
16
|
@exposures.find { |e| e.attribute == attribute }
|
15
17
|
end
|
16
18
|
|
19
|
+
def select_by(attribute)
|
20
|
+
@exposures.select { |e| e.attribute == attribute }
|
21
|
+
end
|
22
|
+
|
17
23
|
def <<(exposure)
|
18
24
|
reset_memoization!
|
19
25
|
@exposures << exposure
|
@@ -30,18 +36,18 @@ module Grape
|
|
30
36
|
@exposures.clear
|
31
37
|
end
|
32
38
|
|
33
|
-
[
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
%i[
|
40
|
+
each
|
41
|
+
to_ary to_a
|
42
|
+
all?
|
43
|
+
select
|
44
|
+
each_with_object
|
45
|
+
\[\]
|
46
|
+
==
|
47
|
+
size
|
48
|
+
count
|
49
|
+
length
|
50
|
+
empty?
|
45
51
|
].each do |name|
|
46
52
|
class_eval <<-RUBY, __FILE__, __LINE__
|
47
53
|
def #{name}(*args, &block)
|
@@ -51,10 +57,13 @@ module Grape
|
|
51
57
|
end
|
52
58
|
|
53
59
|
# Determine if we have any nesting exposures with the same name.
|
54
|
-
def deep_complex_nesting?
|
60
|
+
def deep_complex_nesting?(entity)
|
55
61
|
if @deep_complex_nesting.nil?
|
56
62
|
all_nesting = select(&:nesting?)
|
57
|
-
@deep_complex_nesting =
|
63
|
+
@deep_complex_nesting =
|
64
|
+
all_nesting
|
65
|
+
.group_by { |exposure| exposure.key(entity) }
|
66
|
+
.any? { |_key, exposures| exposures.length > 1 }
|
58
67
|
else
|
59
68
|
@deep_complex_nesting
|
60
69
|
end
|
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
class Entity
|
3
5
|
module Exposure
|
4
6
|
class NestingExposure
|
5
7
|
class OutputBuilder < SimpleDelegator
|
6
|
-
def initialize
|
8
|
+
def initialize(entity)
|
9
|
+
@entity = entity
|
7
10
|
@output_hash = {}
|
8
11
|
@output_collection = []
|
9
12
|
end
|
@@ -18,7 +21,7 @@ module Grape
|
|
18
21
|
return unless result
|
19
22
|
@output_hash.merge! result, &merge_strategy(exposure.for_merge)
|
20
23
|
else
|
21
|
-
@output_hash[exposure.key] = result
|
24
|
+
@output_hash[exposure.key(@entity)] = result
|
22
25
|
end
|
23
26
|
end
|
24
27
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
class Entity
|
3
5
|
module Exposure
|
@@ -21,7 +23,7 @@ module Grape
|
|
21
23
|
end
|
22
24
|
|
23
25
|
def value(entity, options)
|
24
|
-
new_options = options.for_nesting(key)
|
26
|
+
new_options = options.for_nesting(key(entity))
|
25
27
|
using_class.represent(@subexposure.value(entity, options), new_options)
|
26
28
|
end
|
27
29
|
|
data/lib/grape_entity/options.rb
CHANGED
@@ -1,8 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
1
5
|
module Grape
|
2
6
|
class Entity
|
3
7
|
class Options
|
8
|
+
extend Forwardable
|
9
|
+
|
4
10
|
attr_reader :opts_hash
|
5
11
|
|
12
|
+
def_delegators :opts_hash, :dig, :key?, :fetch, :[], :empty
|
13
|
+
|
6
14
|
def initialize(opts_hash = {})
|
7
15
|
@opts_hash = opts_hash
|
8
16
|
@has_only = !opts_hash[:only].nil?
|
@@ -11,54 +19,33 @@ module Grape
|
|
11
19
|
@should_return_key_cache = {}
|
12
20
|
end
|
13
21
|
|
14
|
-
def
|
15
|
-
|
16
|
-
end
|
22
|
+
def merge(new_opts)
|
23
|
+
return self if new_opts.empty?
|
17
24
|
|
18
|
-
|
19
|
-
|
20
|
-
|
25
|
+
merged = if new_opts.instance_of? Options
|
26
|
+
@opts_hash.merge(new_opts.opts_hash)
|
27
|
+
else
|
28
|
+
@opts_hash.merge(new_opts)
|
29
|
+
end
|
21
30
|
|
22
|
-
|
23
|
-
@opts_hash.key? key
|
24
|
-
end
|
25
|
-
|
26
|
-
def merge(new_opts)
|
27
|
-
if new_opts.empty?
|
28
|
-
self
|
29
|
-
else
|
30
|
-
merged = if new_opts.instance_of? Options
|
31
|
-
@opts_hash.merge(new_opts.opts_hash)
|
32
|
-
else
|
33
|
-
@opts_hash.merge(new_opts)
|
34
|
-
end
|
35
|
-
Options.new(merged)
|
36
|
-
end
|
31
|
+
Options.new(merged)
|
37
32
|
end
|
38
33
|
|
39
34
|
def reverse_merge(new_opts)
|
40
|
-
if new_opts.empty?
|
41
|
-
self
|
42
|
-
else
|
43
|
-
merged = if new_opts.instance_of? Options
|
44
|
-
new_opts.opts_hash.merge(@opts_hash)
|
45
|
-
else
|
46
|
-
new_opts.merge(@opts_hash)
|
47
|
-
end
|
48
|
-
Options.new(merged)
|
49
|
-
end
|
50
|
-
end
|
35
|
+
return self if new_opts.empty?
|
51
36
|
|
52
|
-
|
53
|
-
|
37
|
+
merged = if new_opts.instance_of? Options
|
38
|
+
new_opts.opts_hash.merge(@opts_hash)
|
39
|
+
else
|
40
|
+
new_opts.merge(@opts_hash)
|
41
|
+
end
|
42
|
+
|
43
|
+
Options.new(merged)
|
54
44
|
end
|
55
45
|
|
56
46
|
def ==(other)
|
57
|
-
|
58
|
-
|
59
|
-
else
|
60
|
-
other
|
61
|
-
end
|
47
|
+
other_hash = other.is_a?(Options) ? other.opts_hash : other
|
48
|
+
@opts_hash == other_hash
|
62
49
|
end
|
63
50
|
|
64
51
|
def should_return_key?(key)
|
@@ -66,7 +53,7 @@ module Grape
|
|
66
53
|
|
67
54
|
only = only_fields.nil? ||
|
68
55
|
only_fields.key?(key)
|
69
|
-
except = except_fields
|
56
|
+
except = except_fields&.key?(key) &&
|
70
57
|
except_fields[key] == true
|
71
58
|
only && !except
|
72
59
|
end
|
@@ -96,28 +83,26 @@ module Grape
|
|
96
83
|
end
|
97
84
|
|
98
85
|
def with_attr_path(part)
|
86
|
+
return yield unless part
|
87
|
+
|
99
88
|
stack = (opts_hash[:attr_path] ||= [])
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
result
|
105
|
-
else
|
106
|
-
yield
|
107
|
-
end
|
89
|
+
stack.push part
|
90
|
+
result = yield
|
91
|
+
stack.pop
|
92
|
+
result
|
108
93
|
end
|
109
94
|
|
110
95
|
private
|
111
96
|
|
112
97
|
def build_for_nesting(key)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
98
|
+
Options.new(
|
99
|
+
opts_hash.dup.reject { |current_key| current_key == :collection }.merge(
|
100
|
+
root: nil,
|
101
|
+
only: only_fields(key),
|
102
|
+
except: except_fields(key),
|
103
|
+
attr_path: opts_hash[:attr_path]
|
104
|
+
)
|
105
|
+
)
|
121
106
|
end
|
122
107
|
|
123
108
|
def build_symbolized_hash(attribute, hash)
|
data/lib/grape_entity/version.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'ostruct'
|
3
5
|
|
@@ -27,7 +29,9 @@ describe Grape::Entity do
|
|
27
29
|
end
|
28
30
|
|
29
31
|
it 'makes sure that :format_with as a proc cannot be used with a block' do
|
30
|
-
|
32
|
+
# rubocop:disable Style/BlockDelimiters
|
33
|
+
expect { subject.expose :name, format_with: proc {} do p 'hi' end }.to raise_error ArgumentError
|
34
|
+
# rubocop:enable Style/BlockDelimiters
|
31
35
|
end
|
32
36
|
|
33
37
|
it 'makes sure unknown options are not silently ignored' do
|
@@ -64,6 +68,130 @@ describe Grape::Entity do
|
|
64
68
|
end
|
65
69
|
end
|
66
70
|
|
71
|
+
context 'with :expose_nil option' do
|
72
|
+
let(:a) { nil }
|
73
|
+
let(:b) { nil }
|
74
|
+
let(:c) { 'value' }
|
75
|
+
|
76
|
+
context 'when model is a PORO' do
|
77
|
+
let(:model) { Model.new(a, b, c) }
|
78
|
+
|
79
|
+
before do
|
80
|
+
stub_const 'Model', Class.new
|
81
|
+
Model.class_eval do
|
82
|
+
attr_accessor :a, :b, :c
|
83
|
+
|
84
|
+
def initialize(a, b, c)
|
85
|
+
@a = a
|
86
|
+
@b = b
|
87
|
+
@c = c
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when expose_nil option is not provided' do
|
93
|
+
it 'exposes nil attributes' do
|
94
|
+
subject.expose(:a)
|
95
|
+
subject.expose(:b)
|
96
|
+
subject.expose(:c)
|
97
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when expose_nil option is true' do
|
102
|
+
it 'exposes nil attributes' do
|
103
|
+
subject.expose(:a, expose_nil: true)
|
104
|
+
subject.expose(:b, expose_nil: true)
|
105
|
+
subject.expose(:c)
|
106
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'when expose_nil option is false' do
|
111
|
+
it 'does not expose nil attributes' do
|
112
|
+
subject.expose(:a, expose_nil: false)
|
113
|
+
subject.expose(:b, expose_nil: false)
|
114
|
+
subject.expose(:c)
|
115
|
+
expect(subject.represent(model).serializable_hash).to eq(c: 'value')
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'is only applied per attribute' do
|
119
|
+
subject.expose(:a, expose_nil: false)
|
120
|
+
subject.expose(:b)
|
121
|
+
subject.expose(:c)
|
122
|
+
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'raises an error when applied to multiple attribute exposures' do
|
126
|
+
expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when model is a hash' do
|
132
|
+
let(:model) { { a: a, b: b, c: c } }
|
133
|
+
|
134
|
+
context 'when expose_nil option is not provided' do
|
135
|
+
it 'exposes nil attributes' do
|
136
|
+
subject.expose(:a)
|
137
|
+
subject.expose(:b)
|
138
|
+
subject.expose(:c)
|
139
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'when expose_nil option is true' do
|
144
|
+
it 'exposes nil attributes' do
|
145
|
+
subject.expose(:a, expose_nil: true)
|
146
|
+
subject.expose(:b, expose_nil: true)
|
147
|
+
subject.expose(:c)
|
148
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'when expose_nil option is false' do
|
153
|
+
it 'does not expose nil attributes' do
|
154
|
+
subject.expose(:a, expose_nil: false)
|
155
|
+
subject.expose(:b, expose_nil: false)
|
156
|
+
subject.expose(:c)
|
157
|
+
expect(subject.represent(model).serializable_hash).to eq(c: 'value')
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'is only applied per attribute' do
|
161
|
+
subject.expose(:a, expose_nil: false)
|
162
|
+
subject.expose(:b)
|
163
|
+
subject.expose(:c)
|
164
|
+
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'raises an error when applied to multiple attribute exposures' do
|
168
|
+
expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'with nested structures' do
|
174
|
+
let(:model) { { a: a, b: b, c: { d: nil, e: nil, f: { g: nil, h: nil } } } }
|
175
|
+
|
176
|
+
context 'when expose_nil option is false' do
|
177
|
+
it 'does not expose nil attributes' do
|
178
|
+
subject.expose(:a, expose_nil: false)
|
179
|
+
subject.expose(:b)
|
180
|
+
subject.expose(:c) do
|
181
|
+
subject.expose(:d, expose_nil: false)
|
182
|
+
subject.expose(:e)
|
183
|
+
subject.expose(:f) do
|
184
|
+
subject.expose(:g, expose_nil: false)
|
185
|
+
subject.expose(:h)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: { e: nil, f: { h: nil } })
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
67
195
|
context 'with a block' do
|
68
196
|
it 'errors out if called with multiple attributes' do
|
69
197
|
expect { subject.expose(:name, :email) { true } }.to raise_error ArgumentError
|
@@ -112,6 +240,30 @@ describe Grape::Entity do
|
|
112
240
|
end
|
113
241
|
end
|
114
242
|
|
243
|
+
context 'with block passed via &' do
|
244
|
+
it 'with does not pass options when block is passed via &' do
|
245
|
+
class SomeObject
|
246
|
+
def method_without_args
|
247
|
+
'result'
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
subject.expose :that_method_without_args do |object|
|
252
|
+
object.method_without_args
|
253
|
+
end
|
254
|
+
|
255
|
+
subject.expose :that_method_without_args_again, &:method_without_args
|
256
|
+
|
257
|
+
object = SomeObject.new
|
258
|
+
|
259
|
+
value = subject.represent(object).value_for(:that_method_without_args)
|
260
|
+
expect(value).to eq('result')
|
261
|
+
|
262
|
+
value2 = subject.represent(object).value_for(:that_method_without_args_again)
|
263
|
+
expect(value2).to eq('result')
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
115
267
|
context 'with no parameters passed to the block' do
|
116
268
|
it 'adds a nested exposure' do
|
117
269
|
subject.expose :awesome do
|
@@ -131,7 +283,7 @@ describe Grape::Entity do
|
|
131
283
|
expect(another_nested).to_not be_nil
|
132
284
|
expect(another_nested.using_class_name).to eq('Awesome')
|
133
285
|
expect(moar_nested).to_not be_nil
|
134
|
-
expect(moar_nested.key).to eq(:weee)
|
286
|
+
expect(moar_nested.key(subject)).to eq(:weee)
|
135
287
|
end
|
136
288
|
|
137
289
|
it 'represents the exposure as a hash of its nested.root_exposures' do
|
@@ -324,6 +476,15 @@ describe Grape::Entity do
|
|
324
476
|
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'bar')
|
325
477
|
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'foo')
|
326
478
|
end
|
479
|
+
|
480
|
+
it 'overrides parent class exposure' do
|
481
|
+
subject.expose :name
|
482
|
+
child_class = Class.new(subject)
|
483
|
+
child_class.expose :name, as: :child_name
|
484
|
+
|
485
|
+
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
|
486
|
+
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(child_name: 'bar')
|
487
|
+
end
|
327
488
|
end
|
328
489
|
|
329
490
|
context 'register formatters' do
|
@@ -496,7 +657,7 @@ describe Grape::Entity do
|
|
496
657
|
end
|
497
658
|
|
498
659
|
exposure = subject.find_exposure(:awesome_thing)
|
499
|
-
expect(exposure.key).to eq :extra_smooth
|
660
|
+
expect(exposure.key(subject)).to eq :extra_smooth
|
500
661
|
end
|
501
662
|
|
502
663
|
it 'merges nested :if option' do
|
@@ -596,6 +757,34 @@ describe Grape::Entity do
|
|
596
757
|
exposure = subject.find_exposure(:awesome_thing)
|
597
758
|
expect(exposure.documentation).to eq(desc: 'Other description.')
|
598
759
|
end
|
760
|
+
|
761
|
+
it 'propagates expose_nil option' do
|
762
|
+
subject.class_eval do
|
763
|
+
with_options(expose_nil: false) do
|
764
|
+
expose :awesome_thing
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
exposure = subject.find_exposure(:awesome_thing)
|
769
|
+
expect(exposure.conditions[0].inversed?).to be true
|
770
|
+
expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
|
771
|
+
end
|
772
|
+
|
773
|
+
it 'overrides nested :expose_nil option' do
|
774
|
+
subject.class_eval do
|
775
|
+
with_options(expose_nil: true) do
|
776
|
+
expose :awesome_thing, expose_nil: false
|
777
|
+
expose :other_awesome_thing
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
exposure = subject.find_exposure(:awesome_thing)
|
782
|
+
expect(exposure.conditions[0].inversed?).to be true
|
783
|
+
expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
|
784
|
+
# Conditions are only added for exposures that do not expose nil
|
785
|
+
exposure = subject.find_exposure(:other_awesome_thing)
|
786
|
+
expect(exposure.conditions[0]).to be_nil
|
787
|
+
end
|
599
788
|
end
|
600
789
|
|
601
790
|
describe '.represent' do
|
@@ -653,7 +842,7 @@ describe Grape::Entity do
|
|
653
842
|
context 'with specified fields' do
|
654
843
|
it 'returns only specified fields with only option' do
|
655
844
|
subject.expose(:id, :name, :phone)
|
656
|
-
representation = subject.represent(OpenStruct.new, only: [
|
845
|
+
representation = subject.represent(OpenStruct.new, only: %i[id name], serializable: true)
|
657
846
|
expect(representation).to eq(id: nil, name: nil)
|
658
847
|
end
|
659
848
|
|
@@ -666,7 +855,7 @@ describe Grape::Entity do
|
|
666
855
|
it 'returns only fields specified in the only option and not specified in the except option' do
|
667
856
|
subject.expose(:id, :name, :phone)
|
668
857
|
representation = subject.represent(OpenStruct.new,
|
669
|
-
only: [
|
858
|
+
only: %i[name phone],
|
670
859
|
except: [:phone], serializable: true)
|
671
860
|
expect(representation).to eq(name: nil)
|
672
861
|
end
|
@@ -736,7 +925,7 @@ describe Grape::Entity do
|
|
736
925
|
subject.expose(:id, :name, :phone)
|
737
926
|
subject.expose(:user, using: user_entity)
|
738
927
|
|
739
|
-
representation = subject.represent(OpenStruct.new(user: {}), only: [:id, :name, { user: [
|
928
|
+
representation = subject.represent(OpenStruct.new(user: {}), only: [:id, :name, { user: %i[name email] }], serializable: true)
|
740
929
|
expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
|
741
930
|
end
|
742
931
|
|
@@ -759,7 +948,7 @@ describe Grape::Entity do
|
|
759
948
|
subject.expose(:user, using: user_entity)
|
760
949
|
|
761
950
|
representation = subject.represent(OpenStruct.new(user: {}),
|
762
|
-
only: [:id, :name, :phone, user: [
|
951
|
+
only: [:id, :name, :phone, user: %i[id name email]],
|
763
952
|
except: [:phone, { user: [:id] }], serializable: true)
|
764
953
|
expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
|
765
954
|
end
|
@@ -771,7 +960,7 @@ describe Grape::Entity do
|
|
771
960
|
subject.expose(:name)
|
772
961
|
end
|
773
962
|
|
774
|
-
representation = subject.represent(OpenStruct.new, condition: true, only: [
|
963
|
+
representation = subject.represent(OpenStruct.new, condition: true, only: %i[id name], serializable: true)
|
775
964
|
expect(representation).to eq(id: nil, name: nil)
|
776
965
|
end
|
777
966
|
|
@@ -781,7 +970,7 @@ describe Grape::Entity do
|
|
781
970
|
subject.expose(:name, :mobile_phone)
|
782
971
|
end
|
783
972
|
|
784
|
-
representation = subject.represent(OpenStruct.new, condition: true, except: [
|
973
|
+
representation = subject.represent(OpenStruct.new, condition: true, except: %i[phone mobile_phone], serializable: true)
|
785
974
|
expect(representation).to eq(id: nil, name: nil)
|
786
975
|
end
|
787
976
|
|
@@ -863,7 +1052,7 @@ describe Grape::Entity do
|
|
863
1052
|
subject.expose(:id)
|
864
1053
|
subject.expose(:name, as: :title)
|
865
1054
|
|
866
|
-
representation = subject.represent(OpenStruct.new, condition: true, only: [
|
1055
|
+
representation = subject.represent(OpenStruct.new, condition: true, only: %i[id title], serializable: true)
|
867
1056
|
expect(representation).to eq(id: nil, title: nil)
|
868
1057
|
end
|
869
1058
|
|
@@ -890,7 +1079,7 @@ describe Grape::Entity do
|
|
890
1079
|
subject.expose(:nephew, using: nephew_entity)
|
891
1080
|
|
892
1081
|
representation = subject.represent(OpenStruct.new(user: {}),
|
893
|
-
only: [
|
1082
|
+
only: %i[id name user], except: [:nephew], serializable: true)
|
894
1083
|
expect(representation).to eq(id: nil, name: nil, user: { id: nil, name: nil, email: nil })
|
895
1084
|
end
|
896
1085
|
end
|
@@ -1341,8 +1530,8 @@ describe Grape::Entity do
|
|
1341
1530
|
it 'allows to pass different :only and :except params using the same instance' do
|
1342
1531
|
fresh_class.expose :a, :b, :c
|
1343
1532
|
presenter = fresh_class.new(a: 1, b: 2, c: 3)
|
1344
|
-
expect(presenter.serializable_hash(only: [
|
1345
|
-
expect(presenter.serializable_hash(only: [
|
1533
|
+
expect(presenter.serializable_hash(only: %i[a b])).to eq(a: 1, b: 2)
|
1534
|
+
expect(presenter.serializable_hash(only: %i[b c])).to eq(b: 2, c: 3)
|
1346
1535
|
end
|
1347
1536
|
end
|
1348
1537
|
end
|
@@ -1765,39 +1954,5 @@ describe Grape::Entity do
|
|
1765
1954
|
end
|
1766
1955
|
end
|
1767
1956
|
end
|
1768
|
-
|
1769
|
-
describe Grape::Entity::Options do
|
1770
|
-
module EntitySpec
|
1771
|
-
class Crystalline
|
1772
|
-
attr_accessor :prop1, :prop2
|
1773
|
-
|
1774
|
-
def initialize
|
1775
|
-
@prop1 = 'value1'
|
1776
|
-
@prop2 = 'value2'
|
1777
|
-
end
|
1778
|
-
end
|
1779
|
-
|
1780
|
-
class CrystallineEntity < Grape::Entity
|
1781
|
-
expose :prop1, if: ->(_, options) { options.fetch(:signal) }
|
1782
|
-
expose :prop2, if: ->(_, options) { options.fetch(:beam, 'destructive') == 'destructive' }
|
1783
|
-
end
|
1784
|
-
end
|
1785
|
-
|
1786
|
-
context '#fetch' do
|
1787
|
-
it 'without passing in a required option raises KeyError' do
|
1788
|
-
expect { EntitySpec::CrystallineEntity.represent(EntitySpec::Crystalline.new).as_json }.to raise_error KeyError
|
1789
|
-
end
|
1790
|
-
|
1791
|
-
it 'passing in a required option will expose the values' do
|
1792
|
-
crystalline_entity = EntitySpec::CrystallineEntity.represent(EntitySpec::Crystalline.new, signal: true)
|
1793
|
-
expect(crystalline_entity.as_json).to eq(prop1: 'value1', prop2: 'value2')
|
1794
|
-
end
|
1795
|
-
|
1796
|
-
it 'with an option that is not default will not expose that value' do
|
1797
|
-
crystalline_entity = EntitySpec::CrystallineEntity.represent(EntitySpec::Crystalline.new, signal: true, beam: 'intermittent')
|
1798
|
-
expect(crystalline_entity.as_json).to eq(prop1: 'value1')
|
1799
|
-
end
|
1800
|
-
end
|
1801
|
-
end
|
1802
1957
|
end
|
1803
1958
|
end
|