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.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +5 -1
  4. data/.rubocop.yml +31 -0
  5. data/.rubocop_todo.yml +29 -16
  6. data/.travis.yml +15 -10
  7. data/CHANGELOG.md +18 -0
  8. data/Dangerfile +2 -0
  9. data/Gemfile +6 -1
  10. data/README.md +82 -1
  11. data/Rakefile +2 -2
  12. data/bench/serializing.rb +2 -0
  13. data/grape-entity.gemspec +9 -7
  14. data/lib/grape-entity.rb +2 -0
  15. data/lib/grape_entity.rb +2 -0
  16. data/lib/grape_entity/condition.rb +20 -11
  17. data/lib/grape_entity/condition/base.rb +2 -0
  18. data/lib/grape_entity/condition/block_condition.rb +3 -1
  19. data/lib/grape_entity/condition/hash_condition.rb +2 -0
  20. data/lib/grape_entity/condition/symbol_condition.rb +2 -0
  21. data/lib/grape_entity/delegator.rb +10 -9
  22. data/lib/grape_entity/delegator/base.rb +2 -0
  23. data/lib/grape_entity/delegator/fetchable_object.rb +2 -0
  24. data/lib/grape_entity/delegator/hash_object.rb +2 -0
  25. data/lib/grape_entity/delegator/openstruct_object.rb +2 -0
  26. data/lib/grape_entity/delegator/plain_object.rb +2 -0
  27. data/lib/grape_entity/entity.rb +49 -29
  28. data/lib/grape_entity/exposure.rb +58 -41
  29. data/lib/grape_entity/exposure/base.rb +14 -3
  30. data/lib/grape_entity/exposure/block_exposure.rb +2 -0
  31. data/lib/grape_entity/exposure/delegator_exposure.rb +2 -0
  32. data/lib/grape_entity/exposure/formatter_block_exposure.rb +2 -0
  33. data/lib/grape_entity/exposure/formatter_exposure.rb +2 -0
  34. data/lib/grape_entity/exposure/nesting_exposure.rb +34 -30
  35. data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +23 -14
  36. data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +5 -2
  37. data/lib/grape_entity/exposure/represent_exposure.rb +3 -1
  38. data/lib/grape_entity/options.rb +41 -56
  39. data/lib/grape_entity/version.rb +3 -1
  40. data/spec/grape_entity/entity_spec.rb +202 -47
  41. data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +6 -4
  42. data/spec/grape_entity/exposure/represent_exposure_spec.rb +2 -0
  43. data/spec/grape_entity/exposure_spec.rb +14 -2
  44. data/spec/grape_entity/hash_spec.rb +2 -0
  45. data/spec/grape_entity/options_spec.rb +66 -0
  46. data/spec/spec_helper.rb +11 -0
  47. 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
- new_options = nesting_options_for(options)
33
- output = OutputBuilder.new
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 valid_value_for(key, entity, options)
44
- new_options = nesting_options_for(options)
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 serializable_value(entity, options)
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).each_with_object(output) do |exposure, out|
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
- result = exposure.serializable_value(entity, new_options)
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?, to: :nested_exposures
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
- new_exposure.instance_variable_set(:@deep_complex_nesting, true) if nesting_tail.any?(&:deep_complex_nesting?)
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
- :each,
35
- :to_ary, :to_a,
36
- :all?,
37
- :select,
38
- :each_with_object,
39
- :[],
40
- :==,
41
- :size,
42
- :count,
43
- :length,
44
- :empty?
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 = all_nesting.group_by(&:key).any? { |_key, exposures| exposures.length > 1 }
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
 
@@ -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 [](key)
15
- @opts_hash[key]
16
- end
22
+ def merge(new_opts)
23
+ return self if new_opts.empty?
17
24
 
18
- def fetch(*args)
19
- @opts_hash.fetch(*args)
20
- end
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
- def key?(key)
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
- def empty?
53
- @opts_hash.empty?
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
- @opts_hash == if other.is_a? Options
58
- other.opts_hash
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 && except_fields.key?(key) &&
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
- if part
101
- stack.push part
102
- result = yield
103
- stack.pop
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
- new_opts_hash = opts_hash.dup
114
- new_opts_hash.delete(:collection)
115
- new_opts_hash[:root] = nil
116
- new_opts_hash[:only] = only_fields(key)
117
- new_opts_hash[:except] = except_fields(key)
118
- new_opts_hash[:attr_path] = opts_hash[:attr_path]
119
-
120
- Options.new(new_opts_hash)
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)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GrapeEntity
2
- VERSION = '0.6.1'.freeze
4
+ VERSION = '0.7.0'
3
5
  end
@@ -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
- expect { subject.expose :name, format_with: proc {} {} }.to raise_error ArgumentError
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: [:id, :name], serializable: true)
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: [:name, :phone],
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: [:name, :email] }], serializable: true)
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: [:id, :name, :email]],
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: [:id, :name], serializable: true)
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: [:phone, :mobile_phone], serializable: true)
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: [:id, :title], serializable: true)
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: [:id, :name, :user], except: [:nephew], serializable: true)
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: [:a, :b])).to eq(a: 1, b: 2)
1345
- expect(presenter.serializable_hash(only: [:b, :c])).to eq(b: 2, c: 3)
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