grape-entity 0.7.0 → 0.9.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.
@@ -87,9 +87,11 @@ module Grape
87
87
  exposure.should_expose?(entity, options)
88
88
  end
89
89
  next unless should_expose
90
+
90
91
  output[exposure.key(entity)] ||= []
91
92
  output[exposure.key(entity)] << exposure
92
93
  end
94
+
93
95
  table.map do |key, exposures|
94
96
  last_exposure = exposures.last
95
97
 
@@ -36,6 +36,7 @@ module Grape
36
36
  @exposures.clear
37
37
  end
38
38
 
39
+ # rubocop:disable Style/DocumentDynamicEvalDefinition
39
40
  %i[
40
41
  each
41
42
  to_ary to_a
@@ -49,12 +50,13 @@ module Grape
49
50
  length
50
51
  empty?
51
52
  ].each do |name|
52
- class_eval <<-RUBY, __FILE__, __LINE__
53
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
53
54
  def #{name}(*args, &block)
54
55
  @exposures.#{name}(*args, &block)
55
56
  end
56
57
  RUBY
57
58
  end
59
+ # rubocop:enable Style/DocumentDynamicEvalDefinition
58
60
 
59
61
  # Determine if we have any nesting exposures with the same name.
60
62
  def deep_complex_nesting?(entity)
@@ -9,6 +9,8 @@ module Grape
9
9
  @entity = entity
10
10
  @output_hash = {}
11
11
  @output_collection = []
12
+
13
+ super
12
14
  end
13
15
 
14
16
  def add(exposure, result)
@@ -19,6 +21,7 @@ module Grape
19
21
  # If we have an array which should not be merged - save it with a key as a hash
20
22
  # If we have hash which should be merged - save it without a key (merge)
21
23
  return unless result
24
+
22
25
  @output_hash.merge! result, &merge_strategy(exposure.for_merge)
23
26
  else
24
27
  @output_hash[exposure.key(@entity)] = result
@@ -106,11 +106,12 @@ module Grape
106
106
  end
107
107
 
108
108
  def build_symbolized_hash(attribute, hash)
109
- if attribute.is_a?(Hash)
109
+ case attribute
110
+ when Hash
110
111
  attribute.each do |attr, nested_attrs|
111
112
  hash[attr.to_sym] = build_symbolized_hash(nested_attrs, {})
112
113
  end
113
- elsif attribute.is_a?(Array)
114
+ when Array
114
115
  return attribute.each { |x| build_symbolized_hash(x, {}) }
115
116
  else
116
117
  hash[attribute.to_sym] = true
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GrapeEntity
4
- VERSION = '0.7.0'
4
+ VERSION = '0.9.0'
5
5
  end
@@ -30,7 +30,9 @@ describe Grape::Entity do
30
30
 
31
31
  it 'makes sure that :format_with as a proc cannot be used with a block' do
32
32
  # rubocop:disable Style/BlockDelimiters
33
+ # rubocop:disable Lint/EmptyBlock
33
34
  expect { subject.expose :name, format_with: proc {} do p 'hi' end }.to raise_error ArgumentError
35
+ # rubocop:enable Lint/EmptyBlock
34
36
  # rubocop:enable Style/BlockDelimiters
35
37
  end
36
38
 
@@ -126,6 +128,26 @@ describe Grape::Entity do
126
128
  expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
127
129
  end
128
130
  end
131
+
132
+ context 'when expose_nil option is false and block passed' do
133
+ it 'does not expose if block returns nil' do
134
+ subject.expose(:a, expose_nil: false) do |_obj, _options|
135
+ nil
136
+ end
137
+ subject.expose(:b)
138
+ subject.expose(:c)
139
+ expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
140
+ end
141
+
142
+ it 'exposes is block returns a value' do
143
+ subject.expose(:a, expose_nil: false) do |_obj, _options|
144
+ 100
145
+ end
146
+ subject.expose(:b)
147
+ subject.expose(:c)
148
+ expect(subject.represent(model).serializable_hash).to eq(a: 100, b: nil, c: 'value')
149
+ end
150
+ end
129
151
  end
130
152
 
131
153
  context 'when model is a hash' do
@@ -240,27 +262,50 @@ describe Grape::Entity do
240
262
  end
241
263
  end
242
264
 
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
265
+ describe 'blocks' do
266
+ class SomeObject
267
+ def method_without_args
268
+ 'result'
249
269
  end
270
+ end
271
+
272
+ describe 'with block passed in' do
273
+ specify do
274
+ subject.expose :that_method_without_args do |object|
275
+ object.method_without_args
276
+ end
277
+
278
+ object = SomeObject.new
250
279
 
251
- subject.expose :that_method_without_args do |object|
252
- object.method_without_args
280
+ value = subject.represent(object).value_for(:that_method_without_args)
281
+ expect(value).to eq('result')
253
282
  end
283
+ end
284
+
285
+ context 'with block passed in via &' do
286
+ if RUBY_VERSION.start_with?('3')
287
+ specify do
288
+ subject.expose :that_method_without_args, &:method_without_args
289
+ subject.expose :method_without_args, as: :that_method_without_args_again
254
290
 
255
- subject.expose :that_method_without_args_again, &:method_without_args
291
+ object = SomeObject.new
292
+ expect do
293
+ subject.represent(object).value_for(:that_method_without_args)
294
+ end.to raise_error Grape::Entity::Deprecated
256
295
 
257
- object = SomeObject.new
296
+ value2 = subject.represent(object).value_for(:that_method_without_args_again)
297
+ expect(value2).to eq('result')
298
+ end
299
+ else
300
+ specify do
301
+ subject.expose :that_method_without_args_again, &:method_without_args
258
302
 
259
- value = subject.represent(object).value_for(:that_method_without_args)
260
- expect(value).to eq('result')
303
+ object = SomeObject.new
261
304
 
262
- value2 = subject.represent(object).value_for(:that_method_without_args_again)
263
- expect(value2).to eq('result')
305
+ value2 = subject.represent(object).value_for(:that_method_without_args_again)
306
+ expect(value2).to eq('result')
307
+ end
308
+ end
264
309
  end
265
310
  end
266
311
 
@@ -477,11 +522,20 @@ describe Grape::Entity do
477
522
  expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'foo')
478
523
  end
479
524
 
480
- it 'overrides parent class exposure' do
525
+ it 'not overrides exposure by default' do
481
526
  subject.expose :name
482
527
  child_class = Class.new(subject)
483
528
  child_class.expose :name, as: :child_name
484
529
 
530
+ expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
531
+ expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar', child_name: 'bar')
532
+ end
533
+
534
+ it 'overrides parent class exposure when option is specified' do
535
+ subject.expose :name
536
+ child_class = Class.new(subject)
537
+ child_class.expose :name, as: :child_name, override: true
538
+
485
539
  expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
486
540
  expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(child_name: 'bar')
487
541
  end
@@ -948,7 +1002,7 @@ describe Grape::Entity do
948
1002
  subject.expose(:user, using: user_entity)
949
1003
 
950
1004
  representation = subject.represent(OpenStruct.new(user: {}),
951
- only: [:id, :name, :phone, user: %i[id name email]],
1005
+ only: [:id, :name, :phone, { user: %i[id name email] }],
952
1006
  except: [:phone, { user: [:id] }], serializable: true)
953
1007
  expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
954
1008
  end
@@ -1367,6 +1421,18 @@ describe Grape::Entity do
1367
1421
  expect(res).to have_key :nonexistent_attribute
1368
1422
  end
1369
1423
 
1424
+ it 'exposes attributes defined through module inclusion' do
1425
+ module SharedAttributes
1426
+ def a_value
1427
+ 3.14
1428
+ end
1429
+ end
1430
+ fresh_class.include(SharedAttributes)
1431
+ fresh_class.expose :a_value
1432
+ res = fresh_class.new(model).serializable_hash
1433
+ expect(res[:a_value]).to eq(3.14)
1434
+ end
1435
+
1370
1436
  it 'does not expose attributes that are generated by a block but have not passed criteria' do
1371
1437
  fresh_class.expose :nonexistent_attribute,
1372
1438
  proc: ->(_, _) { 'I exist, but it is not yet my time to shine' },
@@ -1652,10 +1718,12 @@ describe Grape::Entity do
1652
1718
  end
1653
1719
  end
1654
1720
 
1721
+ # rubocop:disable Lint/EmptyBlock
1655
1722
  fresh_class.class_eval do
1656
1723
  expose :first_friend, using: EntitySpec::FriendEntity do |_user, _opts|
1657
1724
  end
1658
1725
  end
1726
+ # rubocop:enable Lint/EmptyBlock
1659
1727
 
1660
1728
  rep = subject.value_for(:first_friend)
1661
1729
  expect(rep).to be_kind_of EntitySpec::FriendEntity
@@ -12,11 +12,11 @@ describe Grape::Entity::Exposure::RepresentExposure do
12
12
  let(:subexposure) { double(:subexposure) }
13
13
 
14
14
  it 'sets using_class_name' do
15
- expect { subject }.to change { exposure.using_class_name }.to(using_class_name)
15
+ expect { subject }.to change(exposure, :using_class_name).to(using_class_name)
16
16
  end
17
17
 
18
18
  it 'sets subexposure' do
19
- expect { subject }.to change { exposure.subexposure }.to(subexposure)
19
+ expect { subject }.to change(exposure, :subexposure).to(subexposure)
20
20
  end
21
21
 
22
22
  context 'when using_class is set' do
@@ -25,7 +25,7 @@ describe Grape::Entity::Exposure::RepresentExposure do
25
25
  end
26
26
 
27
27
  it 'resets using_class' do
28
- expect { subject }.to change { exposure.using_class }
28
+ expect { subject }.to change(exposure, :using_class)
29
29
  end
30
30
  end
31
31
  end
@@ -3,7 +3,7 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Grape::Entity do
6
- it 'except option for nested entity' do
6
+ it 'except option for nested entity', :aggregate_failures do
7
7
  module EntitySpec
8
8
  class Address < Grape::Entity
9
9
  expose :post, if: :full
@@ -12,6 +12,14 @@ describe Grape::Entity do
12
12
  expose :house
13
13
  end
14
14
 
15
+ class AddressWithString < Grape::Entity
16
+ self.hash_access = :string
17
+ expose :post, if: :full
18
+ expose :city
19
+ expose :street
20
+ expose :house
21
+ end
22
+
15
23
  class Company < Grape::Entity
16
24
  expose :full_name, if: :full
17
25
  expose :name
@@ -19,6 +27,15 @@ describe Grape::Entity do
19
27
  Address.represent c[:address], Grape::Entity::Options.new(o.opts_hash.except(:full))
20
28
  end
21
29
  end
30
+
31
+ class CompanyWithString < Grape::Entity
32
+ self.hash_access = :string
33
+ expose :full_name, if: :full
34
+ expose :name
35
+ expose :address do |c, o|
36
+ AddressWithString.represent c['address'], Grape::Entity::Options.new(o.opts_hash.except(:full))
37
+ end
38
+ end
22
39
  end
23
40
 
24
41
  company = {
@@ -33,6 +50,24 @@ describe Grape::Entity do
33
50
  }
34
51
  }
35
52
 
53
+ company_with_string = {
54
+ 'full_name' => 'full_name',
55
+ 'name' => 'name',
56
+ 'address' => {
57
+ 'post' => '123456',
58
+ 'city' => 'city',
59
+ 'street' => 'street',
60
+ 'house' => 'house',
61
+ 'something_else' => 'something_else'
62
+ }
63
+ }
64
+
65
+ expect(EntitySpec::CompanyWithString.represent(company_with_string).serializable_hash).to eq \
66
+ company.slice(:name).merge(address: company[:address].slice(:city, :street, :house))
67
+
68
+ expect(EntitySpec::CompanyWithString.represent(company_with_string, full: true).serializable_hash).to eq \
69
+ company.slice(:full_name, :name).merge(address: company[:address].slice(:city, :street, :house))
70
+
36
71
  expect(EntitySpec::Company.represent(company).serializable_hash).to eq \
37
72
  company.slice(:name).merge(address: company[:address].slice(:city, :street, :house))
38
73
 
data/spec/spec_helper.rb CHANGED
@@ -3,11 +3,17 @@
3
3
  require 'simplecov'
4
4
  require 'coveralls'
5
5
 
6
+ # This works around the hash extensions not being automatically included in ActiveSupport < 4
7
+ require 'active_support/version'
8
+ require 'active_support/core_ext/hash' if ActiveSupport::VERSION &&
9
+ ActiveSupport::VERSION::MAJOR &&
10
+ ActiveSupport::VERSION::MAJOR < 4
11
+
6
12
  SimpleCov.start do
7
13
  add_filter 'spec/'
8
14
  end
9
15
 
10
- Coveralls.wear!
16
+ Coveralls.wear! unless RUBY_PLATFORM.eql? 'java'
11
17
 
12
18
  $LOAD_PATH.unshift(File.dirname(__FILE__))
13
19
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grape-entity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-25 00:00:00.000000000 Z
11
+ date: 2021-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.0'
19
+ version: 3.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '4.0'
26
+ version: 3.0.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: multi_json
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '3.0'
131
+ version: '3.9'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '3.0'
138
+ version: '3.9'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: yard
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -159,11 +159,13 @@ extensions: []
159
159
  extra_rdoc_files: []
160
160
  files:
161
161
  - ".coveralls.yml"
162
+ - ".github/dependabot.yml"
163
+ - ".github/workflows/rubocop.yml"
164
+ - ".github/workflows/ruby.yml"
162
165
  - ".gitignore"
163
166
  - ".rspec"
164
167
  - ".rubocop.yml"
165
168
  - ".rubocop_todo.yml"
166
- - ".travis.yml"
167
169
  - ".yardopts"
168
170
  - CHANGELOG.md
169
171
  - CONTRIBUTING.md
@@ -190,6 +192,7 @@ files:
190
192
  - lib/grape_entity/delegator/hash_object.rb
191
193
  - lib/grape_entity/delegator/openstruct_object.rb
192
194
  - lib/grape_entity/delegator/plain_object.rb
195
+ - lib/grape_entity/deprecated.rb
193
196
  - lib/grape_entity/entity.rb
194
197
  - lib/grape_entity/exposure.rb
195
198
  - lib/grape_entity/exposure/base.rb
@@ -214,7 +217,7 @@ homepage: https://github.com/ruby-grape/grape-entity
214
217
  licenses:
215
218
  - MIT
216
219
  metadata: {}
217
- post_install_message:
220
+ post_install_message:
218
221
  rdoc_options: []
219
222
  require_paths:
220
223
  - lib
@@ -222,16 +225,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
222
225
  requirements:
223
226
  - - ">="
224
227
  - !ruby/object:Gem::Version
225
- version: '2.3'
228
+ version: '2.5'
226
229
  required_rubygems_version: !ruby/object:Gem::Requirement
227
230
  requirements:
228
231
  - - ">="
229
232
  - !ruby/object:Gem::Version
230
233
  version: '0'
231
234
  requirements: []
232
- rubyforge_project: grape-entity
233
- rubygems_version: 2.7.3
234
- signing_key:
235
+ rubygems_version: 3.2.3
236
+ signing_key:
235
237
  specification_version: 4
236
238
  summary: A simple facade for managing the relationship between your model and API.
237
239
  test_files: