grape-entity 0.6.0 → 0.8.1

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 (49) hide show
  1. checksums.yaml +5 -5
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +5 -1
  4. data/.rspec +1 -1
  5. data/.rubocop.yml +124 -2
  6. data/.rubocop_todo.yml +21 -32
  7. data/.travis.yml +16 -17
  8. data/CHANGELOG.md +66 -0
  9. data/Dangerfile +2 -0
  10. data/Gemfile +8 -8
  11. data/Guardfile +4 -2
  12. data/README.md +101 -4
  13. data/Rakefile +2 -2
  14. data/bench/serializing.rb +7 -0
  15. data/grape-entity.gemspec +10 -8
  16. data/lib/grape-entity.rb +2 -0
  17. data/lib/grape_entity.rb +2 -0
  18. data/lib/grape_entity/condition.rb +20 -11
  19. data/lib/grape_entity/condition/base.rb +2 -0
  20. data/lib/grape_entity/condition/block_condition.rb +3 -1
  21. data/lib/grape_entity/condition/hash_condition.rb +2 -0
  22. data/lib/grape_entity/condition/symbol_condition.rb +2 -0
  23. data/lib/grape_entity/delegator.rb +10 -9
  24. data/lib/grape_entity/delegator/base.rb +2 -0
  25. data/lib/grape_entity/delegator/fetchable_object.rb +2 -0
  26. data/lib/grape_entity/delegator/hash_object.rb +4 -2
  27. data/lib/grape_entity/delegator/openstruct_object.rb +2 -0
  28. data/lib/grape_entity/delegator/plain_object.rb +2 -0
  29. data/lib/grape_entity/entity.rb +112 -38
  30. data/lib/grape_entity/exposure.rb +64 -41
  31. data/lib/grape_entity/exposure/base.rb +20 -6
  32. data/lib/grape_entity/exposure/block_exposure.rb +2 -0
  33. data/lib/grape_entity/exposure/delegator_exposure.rb +2 -0
  34. data/lib/grape_entity/exposure/formatter_block_exposure.rb +2 -0
  35. data/lib/grape_entity/exposure/formatter_exposure.rb +2 -0
  36. data/lib/grape_entity/exposure/nesting_exposure.rb +35 -30
  37. data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +25 -15
  38. data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +6 -2
  39. data/lib/grape_entity/exposure/represent_exposure.rb +3 -1
  40. data/lib/grape_entity/options.rb +44 -58
  41. data/lib/grape_entity/version.rb +3 -1
  42. data/spec/grape_entity/entity_spec.rb +243 -47
  43. data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +6 -4
  44. data/spec/grape_entity/exposure/represent_exposure_spec.rb +5 -3
  45. data/spec/grape_entity/exposure_spec.rb +14 -2
  46. data/spec/grape_entity/hash_spec.rb +38 -1
  47. data/spec/grape_entity/options_spec.rb +66 -0
  48. data/spec/spec_helper.rb +17 -0
  49. metadata +31 -44
@@ -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,36 +83,35 @@ 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)
124
- if attribute.is_a?(Hash)
109
+ case attribute
110
+ when Hash
125
111
  attribute.each do |attr, nested_attrs|
126
112
  hash[attr.to_sym] = build_symbolized_hash(nested_attrs, {})
127
113
  end
128
- elsif attribute.is_a?(Array)
114
+ when Array
129
115
  return attribute.each { |x| build_symbolized_hash(x, {}) }
130
116
  else
131
117
  hash[attribute.to_sym] = true
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GrapeEntity
2
- VERSION = '0.6.0'.freeze
4
+ VERSION = '0.8.1'
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,150 @@ 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
+
130
+ context 'when expose_nil option is false and block passed' do
131
+ it 'does not expose if block returns nil' do
132
+ subject.expose(:a, expose_nil: false) do |_obj, _options|
133
+ nil
134
+ end
135
+ subject.expose(:b)
136
+ subject.expose(:c)
137
+ expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
138
+ end
139
+
140
+ it 'exposes is block returns a value' do
141
+ subject.expose(:a, expose_nil: false) do |_obj, _options|
142
+ 100
143
+ end
144
+ subject.expose(:b)
145
+ subject.expose(:c)
146
+ expect(subject.represent(model).serializable_hash).to eq(a: 100, b: nil, c: 'value')
147
+ end
148
+ end
149
+ end
150
+
151
+ context 'when model is a hash' do
152
+ let(:model) { { a: a, b: b, c: c } }
153
+
154
+ context 'when expose_nil option is not provided' do
155
+ it 'exposes nil attributes' do
156
+ subject.expose(:a)
157
+ subject.expose(:b)
158
+ subject.expose(:c)
159
+ expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
160
+ end
161
+ end
162
+
163
+ context 'when expose_nil option is true' do
164
+ it 'exposes nil attributes' do
165
+ subject.expose(:a, expose_nil: true)
166
+ subject.expose(:b, expose_nil: true)
167
+ subject.expose(:c)
168
+ expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
169
+ end
170
+ end
171
+
172
+ context 'when expose_nil option is false' do
173
+ it 'does not expose nil attributes' do
174
+ subject.expose(:a, expose_nil: false)
175
+ subject.expose(:b, expose_nil: false)
176
+ subject.expose(:c)
177
+ expect(subject.represent(model).serializable_hash).to eq(c: 'value')
178
+ end
179
+
180
+ it 'is only applied per attribute' do
181
+ subject.expose(:a, expose_nil: false)
182
+ subject.expose(:b)
183
+ subject.expose(:c)
184
+ expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
185
+ end
186
+
187
+ it 'raises an error when applied to multiple attribute exposures' do
188
+ expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
189
+ end
190
+ end
191
+ end
192
+
193
+ context 'with nested structures' do
194
+ let(:model) { { a: a, b: b, c: { d: nil, e: nil, f: { g: nil, h: nil } } } }
195
+
196
+ context 'when expose_nil option is false' do
197
+ it 'does not expose nil attributes' do
198
+ subject.expose(:a, expose_nil: false)
199
+ subject.expose(:b)
200
+ subject.expose(:c) do
201
+ subject.expose(:d, expose_nil: false)
202
+ subject.expose(:e)
203
+ subject.expose(:f) do
204
+ subject.expose(:g, expose_nil: false)
205
+ subject.expose(:h)
206
+ end
207
+ end
208
+
209
+ expect(subject.represent(model).serializable_hash).to eq(b: nil, c: { e: nil, f: { h: nil } })
210
+ end
211
+ end
212
+ end
213
+ end
214
+
67
215
  context 'with a block' do
68
216
  it 'errors out if called with multiple attributes' do
69
217
  expect { subject.expose(:name, :email) { true } }.to raise_error ArgumentError
@@ -112,6 +260,30 @@ describe Grape::Entity do
112
260
  end
113
261
  end
114
262
 
263
+ context 'with block passed via &' do
264
+ it 'with does not pass options when block is passed via &' do
265
+ class SomeObject
266
+ def method_without_args
267
+ 'result'
268
+ end
269
+ end
270
+
271
+ subject.expose :that_method_without_args do |object|
272
+ object.method_without_args
273
+ end
274
+
275
+ subject.expose :that_method_without_args_again, &:method_without_args
276
+
277
+ object = SomeObject.new
278
+
279
+ value = subject.represent(object).value_for(:that_method_without_args)
280
+ expect(value).to eq('result')
281
+
282
+ value2 = subject.represent(object).value_for(:that_method_without_args_again)
283
+ expect(value2).to eq('result')
284
+ end
285
+ end
286
+
115
287
  context 'with no parameters passed to the block' do
116
288
  it 'adds a nested exposure' do
117
289
  subject.expose :awesome do
@@ -131,7 +303,7 @@ describe Grape::Entity do
131
303
  expect(another_nested).to_not be_nil
132
304
  expect(another_nested.using_class_name).to eq('Awesome')
133
305
  expect(moar_nested).to_not be_nil
134
- expect(moar_nested.key).to eq(:weee)
306
+ expect(moar_nested.key(subject)).to eq(:weee)
135
307
  end
136
308
 
137
309
  it 'represents the exposure as a hash of its nested.root_exposures' do
@@ -324,6 +496,24 @@ describe Grape::Entity do
324
496
  expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'bar')
325
497
  expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'foo')
326
498
  end
499
+
500
+ it 'not overrides exposure by default' do
501
+ subject.expose :name
502
+ child_class = Class.new(subject)
503
+ child_class.expose :name, as: :child_name
504
+
505
+ expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
506
+ expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar', child_name: 'bar')
507
+ end
508
+
509
+ it 'overrides parent class exposure when option is specified' do
510
+ subject.expose :name
511
+ child_class = Class.new(subject)
512
+ child_class.expose :name, as: :child_name, override: true
513
+
514
+ expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
515
+ expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(child_name: 'bar')
516
+ end
327
517
  end
328
518
 
329
519
  context 'register formatters' do
@@ -496,7 +686,7 @@ describe Grape::Entity do
496
686
  end
497
687
 
498
688
  exposure = subject.find_exposure(:awesome_thing)
499
- expect(exposure.key).to eq :extra_smooth
689
+ expect(exposure.key(subject)).to eq :extra_smooth
500
690
  end
501
691
 
502
692
  it 'merges nested :if option' do
@@ -596,6 +786,34 @@ describe Grape::Entity do
596
786
  exposure = subject.find_exposure(:awesome_thing)
597
787
  expect(exposure.documentation).to eq(desc: 'Other description.')
598
788
  end
789
+
790
+ it 'propagates expose_nil option' do
791
+ subject.class_eval do
792
+ with_options(expose_nil: false) do
793
+ expose :awesome_thing
794
+ end
795
+ end
796
+
797
+ exposure = subject.find_exposure(:awesome_thing)
798
+ expect(exposure.conditions[0].inversed?).to be true
799
+ expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
800
+ end
801
+
802
+ it 'overrides nested :expose_nil option' do
803
+ subject.class_eval do
804
+ with_options(expose_nil: true) do
805
+ expose :awesome_thing, expose_nil: false
806
+ expose :other_awesome_thing
807
+ end
808
+ end
809
+
810
+ exposure = subject.find_exposure(:awesome_thing)
811
+ expect(exposure.conditions[0].inversed?).to be true
812
+ expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
813
+ # Conditions are only added for exposures that do not expose nil
814
+ exposure = subject.find_exposure(:other_awesome_thing)
815
+ expect(exposure.conditions[0]).to be_nil
816
+ end
599
817
  end
600
818
 
601
819
  describe '.represent' do
@@ -653,7 +871,7 @@ describe Grape::Entity do
653
871
  context 'with specified fields' do
654
872
  it 'returns only specified fields with only option' do
655
873
  subject.expose(:id, :name, :phone)
656
- representation = subject.represent(OpenStruct.new, only: [:id, :name], serializable: true)
874
+ representation = subject.represent(OpenStruct.new, only: %i[id name], serializable: true)
657
875
  expect(representation).to eq(id: nil, name: nil)
658
876
  end
659
877
 
@@ -666,7 +884,7 @@ describe Grape::Entity do
666
884
  it 'returns only fields specified in the only option and not specified in the except option' do
667
885
  subject.expose(:id, :name, :phone)
668
886
  representation = subject.represent(OpenStruct.new,
669
- only: [:name, :phone],
887
+ only: %i[name phone],
670
888
  except: [:phone], serializable: true)
671
889
  expect(representation).to eq(name: nil)
672
890
  end
@@ -736,7 +954,7 @@ describe Grape::Entity do
736
954
  subject.expose(:id, :name, :phone)
737
955
  subject.expose(:user, using: user_entity)
738
956
 
739
- representation = subject.represent(OpenStruct.new(user: {}), only: [:id, :name, { user: [:name, :email] }], serializable: true)
957
+ representation = subject.represent(OpenStruct.new(user: {}), only: [:id, :name, { user: %i[name email] }], serializable: true)
740
958
  expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
741
959
  end
742
960
 
@@ -759,7 +977,7 @@ describe Grape::Entity do
759
977
  subject.expose(:user, using: user_entity)
760
978
 
761
979
  representation = subject.represent(OpenStruct.new(user: {}),
762
- only: [:id, :name, :phone, user: [:id, :name, :email]],
980
+ only: [:id, :name, :phone, { user: %i[id name email] }],
763
981
  except: [:phone, { user: [:id] }], serializable: true)
764
982
  expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
765
983
  end
@@ -771,7 +989,7 @@ describe Grape::Entity do
771
989
  subject.expose(:name)
772
990
  end
773
991
 
774
- representation = subject.represent(OpenStruct.new, condition: true, only: [:id, :name], serializable: true)
992
+ representation = subject.represent(OpenStruct.new, condition: true, only: %i[id name], serializable: true)
775
993
  expect(representation).to eq(id: nil, name: nil)
776
994
  end
777
995
 
@@ -781,7 +999,7 @@ describe Grape::Entity do
781
999
  subject.expose(:name, :mobile_phone)
782
1000
  end
783
1001
 
784
- representation = subject.represent(OpenStruct.new, condition: true, except: [:phone, :mobile_phone], serializable: true)
1002
+ representation = subject.represent(OpenStruct.new, condition: true, except: %i[phone mobile_phone], serializable: true)
785
1003
  expect(representation).to eq(id: nil, name: nil)
786
1004
  end
787
1005
 
@@ -863,7 +1081,7 @@ describe Grape::Entity do
863
1081
  subject.expose(:id)
864
1082
  subject.expose(:name, as: :title)
865
1083
 
866
- representation = subject.represent(OpenStruct.new, condition: true, only: [:id, :title], serializable: true)
1084
+ representation = subject.represent(OpenStruct.new, condition: true, only: %i[id title], serializable: true)
867
1085
  expect(representation).to eq(id: nil, title: nil)
868
1086
  end
869
1087
 
@@ -890,7 +1108,7 @@ describe Grape::Entity do
890
1108
  subject.expose(:nephew, using: nephew_entity)
891
1109
 
892
1110
  representation = subject.represent(OpenStruct.new(user: {}),
893
- only: [:id, :name, :user], except: [:nephew], serializable: true)
1111
+ only: %i[id name user], except: [:nephew], serializable: true)
894
1112
  expect(representation).to eq(id: nil, name: nil, user: { id: nil, name: nil, email: nil })
895
1113
  end
896
1114
  end
@@ -1178,6 +1396,18 @@ describe Grape::Entity do
1178
1396
  expect(res).to have_key :nonexistent_attribute
1179
1397
  end
1180
1398
 
1399
+ it 'exposes attributes defined through module inclusion' do
1400
+ module SharedAttributes
1401
+ def a_value
1402
+ 3.14
1403
+ end
1404
+ end
1405
+ fresh_class.include(SharedAttributes)
1406
+ fresh_class.expose :a_value
1407
+ res = fresh_class.new(model).serializable_hash
1408
+ expect(res[:a_value]).to eq(3.14)
1409
+ end
1410
+
1181
1411
  it 'does not expose attributes that are generated by a block but have not passed criteria' do
1182
1412
  fresh_class.expose :nonexistent_attribute,
1183
1413
  proc: ->(_, _) { 'I exist, but it is not yet my time to shine' },
@@ -1341,8 +1571,8 @@ describe Grape::Entity do
1341
1571
  it 'allows to pass different :only and :except params using the same instance' do
1342
1572
  fresh_class.expose :a, :b, :c
1343
1573
  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)
1574
+ expect(presenter.serializable_hash(only: %i[a b])).to eq(a: 1, b: 2)
1575
+ expect(presenter.serializable_hash(only: %i[b c])).to eq(b: 2, c: 3)
1346
1576
  end
1347
1577
  end
1348
1578
  end
@@ -1765,39 +1995,5 @@ describe Grape::Entity do
1765
1995
  end
1766
1996
  end
1767
1997
  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
1998
  end
1803
1999
  end