grape-entity 0.9.0 → 0.10.2
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 +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +41 -0
- data/.rspec +2 -1
- data/.rubocop.yml +4 -1
- data/.rubocop_todo.yml +27 -5
- data/CHANGELOG.md +27 -0
- data/CONTRIBUTING.md +1 -1
- data/README.md +28 -0
- data/lib/grape_entity/delegator/base.rb +5 -0
- data/lib/grape_entity/delegator.rb +8 -4
- data/lib/grape_entity/entity.rb +8 -7
- data/lib/grape_entity/exposure/base.rb +8 -1
- data/lib/grape_entity/exposure.rb +6 -1
- data/lib/grape_entity/options.rb +1 -1
- data/lib/grape_entity/version.rb +1 -1
- data/spec/grape_entity/entity_spec.rb +152 -3
- data/spec/grape_entity/hash_spec.rb +15 -1
- data/spec/spec_helper.rb +8 -4
- metadata +4 -5
- data/.github/workflows/rubocop.yml +0 -26
- data/.github/workflows/ruby.yml +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a89222a95fe66dc86d907762352275aa328a0423a461e2f5a7c8fc806a92f09
|
|
4
|
+
data.tar.gz: a06ad75430e568a7e4cd2e9b6b329011e8382003c5882f690199403be58928c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1bc3419fea51eb0c7c7c78f8f4e510c9d73bfe210cee2aa1f2aaaee92725d02334ab235818a37300c733c0970c9ee40cf56a022b421937ec6662d3e67da3e85f
|
|
7
|
+
data.tar.gz: 4f31cb8e01fbba21883bbf432fa557d0ca7f85d3c1d42a0aac0e4143c4add3af37e7f24b4fc315ff55502a640bffa0d185e8c3081d8a8b5492d45dc1b0102233
|
data/.github/dependabot.yml
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Ruby
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- '*'
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- '*'
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
rubocop:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v3
|
|
19
|
+
- uses: ruby/setup-ruby@v1
|
|
20
|
+
with:
|
|
21
|
+
ruby-version: '3.1'
|
|
22
|
+
bundler-cache: true
|
|
23
|
+
- name: Run rubocop
|
|
24
|
+
run: bundle exec rubocop --parallel --format progress
|
|
25
|
+
|
|
26
|
+
rspec:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
needs: ['rubocop']
|
|
29
|
+
strategy:
|
|
30
|
+
matrix:
|
|
31
|
+
ruby-version: ['2.7', '3.0', '3.1', 'head', jruby, truffleruby]
|
|
32
|
+
steps:
|
|
33
|
+
- name: Check out branch
|
|
34
|
+
uses: actions/checkout@v3
|
|
35
|
+
- name: Set up Ruby
|
|
36
|
+
uses: ruby/setup-ruby@v1
|
|
37
|
+
with:
|
|
38
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
39
|
+
bundler-cache: true
|
|
40
|
+
- name: Run rspec rest of the suite
|
|
41
|
+
run: bundle exec rspec
|
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -5,7 +5,7 @@ AllCops:
|
|
|
5
5
|
- vendor/**/*
|
|
6
6
|
- example/**/*
|
|
7
7
|
NewCops: enable
|
|
8
|
-
TargetRubyVersion: 3.
|
|
8
|
+
TargetRubyVersion: 3.1
|
|
9
9
|
SuggestExtensions: false
|
|
10
10
|
|
|
11
11
|
# Layout stuff
|
|
@@ -77,6 +77,9 @@ Naming:
|
|
|
77
77
|
Style/Documentation:
|
|
78
78
|
Enabled: false
|
|
79
79
|
|
|
80
|
+
Style/HashSyntax:
|
|
81
|
+
Enabled: false
|
|
82
|
+
|
|
80
83
|
Style/OptionalBooleanParameter:
|
|
81
84
|
AllowedMethods:
|
|
82
85
|
# from lib/grape_entity/condition/base.rb
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on
|
|
3
|
+
# on 2022-07-26 21:29:59 UTC using RuboCop version 1.32.0.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
|
8
8
|
|
|
9
|
+
# Offense count: 1
|
|
10
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
11
|
+
# Configuration parameters: Include.
|
|
12
|
+
# Include: **/*.gemspec
|
|
13
|
+
Gemspec/DeprecatedAttributeAssignment:
|
|
14
|
+
Exclude:
|
|
15
|
+
- 'grape-entity.gemspec'
|
|
16
|
+
|
|
17
|
+
# Offense count: 1
|
|
18
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
19
|
+
# Configuration parameters: Include.
|
|
20
|
+
# Include: **/*.gemspec
|
|
21
|
+
Gemspec/RequireMFA:
|
|
22
|
+
Exclude:
|
|
23
|
+
- 'grape-entity.gemspec'
|
|
24
|
+
|
|
9
25
|
# Offense count: 1
|
|
10
26
|
# Configuration parameters: Include.
|
|
11
27
|
# Include: **/*.gemspec
|
|
@@ -14,14 +30,20 @@ Gemspec/RequiredRubyVersion:
|
|
|
14
30
|
- 'grape-entity.gemspec'
|
|
15
31
|
|
|
16
32
|
# Offense count: 6
|
|
17
|
-
#
|
|
33
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
18
34
|
Lint/BooleanSymbol:
|
|
19
35
|
Exclude:
|
|
20
36
|
- 'spec/grape_entity/exposure_spec.rb'
|
|
21
37
|
|
|
22
|
-
# Offense count:
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
# Offense count: 15
|
|
39
|
+
Style/OpenStructUse:
|
|
40
|
+
Exclude:
|
|
41
|
+
- 'lib/grape_entity/delegator.rb'
|
|
42
|
+
- 'spec/grape_entity/entity_spec.rb'
|
|
43
|
+
|
|
44
|
+
# Offense count: 2
|
|
45
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
46
|
+
# Configuration parameters: AllowMethodsWithArguments, IgnoredMethods, AllowComments.
|
|
25
47
|
# IgnoredMethods: respond_to, define_method
|
|
26
48
|
Style/SymbolProc:
|
|
27
49
|
Exclude:
|
data/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,33 @@
|
|
|
9
9
|
* Your contribution here.
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
### 0.10.2 (2022-07-29)
|
|
13
|
+
|
|
14
|
+
#### Fixes
|
|
15
|
+
|
|
16
|
+
* [#366](https://github.com/ruby-grape/grape-entity/pull/366): Don't suppress regular ArgumentError exceptions - [splattael](https://github.com/splattael).
|
|
17
|
+
* [#363](https://github.com/ruby-grape/grape-entity/pull/338): Fix typo - [@OuYangJinTing](https://github.com/OuYangJinTing).
|
|
18
|
+
* [#361](https://github.com/ruby-grape/grape-entity/pull/361): Require 'active_support/core_ext' - [@pravi](https://github.com/pravi).
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### 0.10.1 (2021-10-22)
|
|
22
|
+
|
|
23
|
+
#### Fixes
|
|
24
|
+
|
|
25
|
+
* [#359](https://github.com/ruby-grape/grape-entity/pull/359): Respect `hash_access` setting when using `expose_nil: false` option - [@magni-](https://github.com/magni-).
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### 0.10.0 (2021-09-15)
|
|
29
|
+
|
|
30
|
+
#### Features
|
|
31
|
+
|
|
32
|
+
* [#352](https://github.com/ruby-grape/grape-entity/pull/352): Add Default value option - [@ahmednaguib](https://github.com/ahmednaguib).
|
|
33
|
+
|
|
34
|
+
#### Fixes
|
|
35
|
+
|
|
36
|
+
* [#355](https://github.com/ruby-grape/grape-entity/pull/355): Fix infinite loop problem with the `NameErrors` in block exposures - [@meinac](https://github.com/meinac).
|
|
37
|
+
|
|
38
|
+
|
|
12
39
|
### 0.9.0 (2021-03-20)
|
|
13
40
|
|
|
14
41
|
#### Features
|
data/CONTRIBUTING.md
CHANGED
|
@@ -78,7 +78,7 @@ git push origin my-feature-branch
|
|
|
78
78
|
|
|
79
79
|
#### Make a Pull Request
|
|
80
80
|
|
|
81
|
-
Go to https://github.com/
|
|
81
|
+
Go to https://github.com/ruby-grape/grape-entity and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days.
|
|
82
82
|
|
|
83
83
|
#### Rebase
|
|
84
84
|
|
data/README.md
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
- [Aliases](#aliases)
|
|
25
25
|
- [Format Before Exposing](#format-before-exposing)
|
|
26
26
|
- [Expose Nil](#expose-nil)
|
|
27
|
+
- [Default Value](#default-value)
|
|
27
28
|
- [Documentation](#documentation)
|
|
28
29
|
- [Options Hash](#options-hash)
|
|
29
30
|
- [Passing Additional Option To Nested Exposure](#passing-additional-option-to-nested-exposure)
|
|
@@ -110,6 +111,20 @@ The field lookup takes several steps
|
|
|
110
111
|
* next try `object.fetch(exposure)`
|
|
111
112
|
* last raise an Exception
|
|
112
113
|
|
|
114
|
+
`exposure` is a Symbol by default. If `object` is a Hash with stringified keys, you can set the hash accessor at the entity-class level to properly expose its members:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
class Status < GrapeEntity
|
|
118
|
+
self.hash_access = :to_s
|
|
119
|
+
|
|
120
|
+
expose :code
|
|
121
|
+
expose :message
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
Status.represent({ 'code' => 418, 'message' => "I'm a teapot" }).as_json
|
|
125
|
+
#=> { code: 418, message: "I'm a teapot" }
|
|
126
|
+
```
|
|
127
|
+
|
|
113
128
|
#### Exposing with a Presenter
|
|
114
129
|
|
|
115
130
|
Don't derive your model classes from `Grape::Entity`, expose them using a presenter.
|
|
@@ -482,6 +497,19 @@ module Entities
|
|
|
482
497
|
end
|
|
483
498
|
```
|
|
484
499
|
|
|
500
|
+
#### Default Value
|
|
501
|
+
|
|
502
|
+
This option can be used to provide a default value in case the return value is nil or empty.
|
|
503
|
+
|
|
504
|
+
```ruby
|
|
505
|
+
module Entities
|
|
506
|
+
class MyModel < Grape::Entity
|
|
507
|
+
expose :name, default: ''
|
|
508
|
+
expose :age, default: 60
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
```
|
|
512
|
+
|
|
485
513
|
#### Documentation
|
|
486
514
|
|
|
487
515
|
Expose documentation with the field. Gets bubbled up when used with Grape and various API documentation systems.
|
|
@@ -11,10 +11,14 @@ module Grape
|
|
|
11
11
|
module Delegator
|
|
12
12
|
def self.new(object)
|
|
13
13
|
delegator_klass =
|
|
14
|
-
if object.is_a?(Hash)
|
|
15
|
-
|
|
16
|
-
elsif object.
|
|
17
|
-
|
|
14
|
+
if object.is_a?(Hash)
|
|
15
|
+
HashObject
|
|
16
|
+
elsif defined?(OpenStruct) && object.is_a?(OpenStruct)
|
|
17
|
+
OpenStructObject
|
|
18
|
+
elsif object.respond_to?(:fetch, true)
|
|
19
|
+
FetchableObject
|
|
20
|
+
else
|
|
21
|
+
PlainObject
|
|
18
22
|
end
|
|
19
23
|
|
|
20
24
|
delegator_klass.new(object)
|
data/lib/grape_entity/entity.rb
CHANGED
|
@@ -153,7 +153,7 @@ module Grape
|
|
|
153
153
|
#
|
|
154
154
|
# @example as: a proc or lambda
|
|
155
155
|
#
|
|
156
|
-
# object = OpenStruct(
|
|
156
|
+
# object = OpenStruct(awesomeness: 'awesome_key', awesome: 'not-my-key', other: 'other-key' )
|
|
157
157
|
#
|
|
158
158
|
# class MyEntity < Grape::Entity
|
|
159
159
|
# expose :awesome, as: proc { object.awesomeness }
|
|
@@ -481,9 +481,6 @@ module Grape
|
|
|
481
481
|
@object = object
|
|
482
482
|
@options = options.is_a?(Options) ? options : Options.new(options)
|
|
483
483
|
@delegator = Delegator.new(object)
|
|
484
|
-
|
|
485
|
-
# Why not `arity > 1`? It might be negative https://ruby-doc.org/core-2.6.6/Method.html#method-i-arity
|
|
486
|
-
@delegator_accepts_opts = @delegator.method(:delegate).arity != 1
|
|
487
484
|
end
|
|
488
485
|
|
|
489
486
|
def root_exposures
|
|
@@ -525,9 +522,12 @@ module Grape
|
|
|
525
522
|
end
|
|
526
523
|
rescue StandardError => e
|
|
527
524
|
# it handles: https://github.com/ruby/ruby/blob/v3_0_0_preview1/NEWS.md#language-changes point 3, Proc
|
|
528
|
-
|
|
525
|
+
# accounting for expose :foo, &:bar
|
|
526
|
+
if e.is_a?(ArgumentError) && block.parameters == [[:req], [:rest]]
|
|
527
|
+
raise Grape::Entity::Deprecated.new e.message, 'in ruby 3.0'
|
|
528
|
+
end
|
|
529
529
|
|
|
530
|
-
raise e
|
|
530
|
+
raise e
|
|
531
531
|
end
|
|
532
532
|
|
|
533
533
|
def exec_with_attribute(attribute, &block)
|
|
@@ -541,7 +541,7 @@ module Grape
|
|
|
541
541
|
def delegate_attribute(attribute)
|
|
542
542
|
if is_defined_in_entity?(attribute)
|
|
543
543
|
send(attribute)
|
|
544
|
-
elsif
|
|
544
|
+
elsif delegator.accepts_options?
|
|
545
545
|
delegator.delegate(attribute, **self.class.delegation_opts)
|
|
546
546
|
else
|
|
547
547
|
delegator.delegate(attribute)
|
|
@@ -585,6 +585,7 @@ module Grape
|
|
|
585
585
|
merge
|
|
586
586
|
expose_nil
|
|
587
587
|
override
|
|
588
|
+
default
|
|
588
589
|
].to_set.freeze
|
|
589
590
|
|
|
590
591
|
# Merges the given options with current block options.
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support'
|
|
4
|
+
require 'active_support/core_ext'
|
|
5
|
+
|
|
3
6
|
module Grape
|
|
4
7
|
class Entity
|
|
5
8
|
module Exposure
|
|
@@ -16,6 +19,7 @@ module Grape
|
|
|
16
19
|
key = options[:as] || attribute
|
|
17
20
|
@key = key.respond_to?(:to_sym) ? key.to_sym : key
|
|
18
21
|
@is_safe = options[:safe]
|
|
22
|
+
@default_value = options[:default]
|
|
19
23
|
@for_merge = options[:merge]
|
|
20
24
|
@attr_path_proc = options[:attr_path]
|
|
21
25
|
@documentation = options[:documentation]
|
|
@@ -82,7 +86,10 @@ module Grape
|
|
|
82
86
|
end
|
|
83
87
|
|
|
84
88
|
def valid_value(entity, options)
|
|
85
|
-
|
|
89
|
+
return unless valid?(entity)
|
|
90
|
+
|
|
91
|
+
output = value(entity, options)
|
|
92
|
+
output.blank? && @default_value.present? ? @default_value : output
|
|
86
93
|
end
|
|
87
94
|
|
|
88
95
|
def should_return_key?(options)
|
|
@@ -56,7 +56,12 @@ module Grape
|
|
|
56
56
|
Condition.new_unless(
|
|
57
57
|
proc do |object, _options|
|
|
58
58
|
if options[:proc].nil?
|
|
59
|
-
Delegator.new(object)
|
|
59
|
+
delegator = Delegator.new(object)
|
|
60
|
+
if is_a?(Grape::Entity) && delegator.accepts_options?
|
|
61
|
+
delegator.delegate(attribute, **self.class.delegation_opts).nil?
|
|
62
|
+
else
|
|
63
|
+
delegator.delegate(attribute).nil?
|
|
64
|
+
end
|
|
60
65
|
else
|
|
61
66
|
exec_with_object(options, &options[:proc]).nil?
|
|
62
67
|
end
|
data/lib/grape_entity/options.rb
CHANGED
data/lib/grape_entity/version.rb
CHANGED
|
@@ -30,9 +30,7 @@ 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
|
|
34
33
|
expect { subject.expose :name, format_with: proc {} do p 'hi' end }.to raise_error ArgumentError
|
|
35
|
-
# rubocop:enable Lint/EmptyBlock
|
|
36
34
|
# rubocop:enable Style/BlockDelimiters
|
|
37
35
|
end
|
|
38
36
|
|
|
@@ -214,6 +212,130 @@ describe Grape::Entity do
|
|
|
214
212
|
end
|
|
215
213
|
end
|
|
216
214
|
|
|
215
|
+
context 'with :default option' do
|
|
216
|
+
let(:a) { nil }
|
|
217
|
+
let(:b) { nil }
|
|
218
|
+
let(:c) { 'value' }
|
|
219
|
+
|
|
220
|
+
context 'when model is a PORO' do
|
|
221
|
+
let(:model) { Model.new(a, b, c) }
|
|
222
|
+
|
|
223
|
+
before do
|
|
224
|
+
stub_const 'Model', Class.new
|
|
225
|
+
Model.class_eval do
|
|
226
|
+
attr_accessor :a, :b, :c
|
|
227
|
+
|
|
228
|
+
def initialize(a, b, c)
|
|
229
|
+
@a = a
|
|
230
|
+
@b = b
|
|
231
|
+
@c = c
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
context 'when default option is not provided' do
|
|
237
|
+
it 'exposes attributes values' do
|
|
238
|
+
subject.expose(:a)
|
|
239
|
+
subject.expose(:b)
|
|
240
|
+
subject.expose(:c)
|
|
241
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
context 'when default option is set' do
|
|
246
|
+
it 'exposes default values for attributes' do
|
|
247
|
+
subject.expose(:a, default: 'a')
|
|
248
|
+
subject.expose(:b, default: 'b')
|
|
249
|
+
subject.expose(:c, default: 'c')
|
|
250
|
+
expect(subject.represent(model).serializable_hash).to eq(a: 'a', b: 'b', c: 'value')
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
context 'when default option is set and block passed' do
|
|
255
|
+
it 'return default value if block returns nil' do
|
|
256
|
+
subject.expose(:a, default: 'a') do |_obj, _options|
|
|
257
|
+
nil
|
|
258
|
+
end
|
|
259
|
+
subject.expose(:b)
|
|
260
|
+
subject.expose(:c)
|
|
261
|
+
expect(subject.represent(model).serializable_hash).to eq(a: 'a', b: nil, c: 'value')
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it 'return value from block if block returns a value' do
|
|
265
|
+
subject.expose(:a, default: 'a') do |_obj, _options|
|
|
266
|
+
100
|
|
267
|
+
end
|
|
268
|
+
subject.expose(:b)
|
|
269
|
+
subject.expose(:c)
|
|
270
|
+
expect(subject.represent(model).serializable_hash).to eq(a: 100, b: nil, c: 'value')
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
context 'when model is a hash' do
|
|
276
|
+
let(:model) { { a: a, b: b, c: c } }
|
|
277
|
+
|
|
278
|
+
context 'when expose_nil option is not provided' do
|
|
279
|
+
it 'exposes nil attributes' do
|
|
280
|
+
subject.expose(:a)
|
|
281
|
+
subject.expose(:b)
|
|
282
|
+
subject.expose(:c)
|
|
283
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
context 'when expose_nil option is true' do
|
|
288
|
+
it 'exposes nil attributes' do
|
|
289
|
+
subject.expose(:a, expose_nil: true)
|
|
290
|
+
subject.expose(:b, expose_nil: true)
|
|
291
|
+
subject.expose(:c)
|
|
292
|
+
expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
context 'when expose_nil option is false' do
|
|
297
|
+
it 'does not expose nil attributes' do
|
|
298
|
+
subject.expose(:a, expose_nil: false)
|
|
299
|
+
subject.expose(:b, expose_nil: false)
|
|
300
|
+
subject.expose(:c)
|
|
301
|
+
expect(subject.represent(model).serializable_hash).to eq(c: 'value')
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it 'is only applied per attribute' do
|
|
305
|
+
subject.expose(:a, expose_nil: false)
|
|
306
|
+
subject.expose(:b)
|
|
307
|
+
subject.expose(:c)
|
|
308
|
+
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
it 'raises an error when applied to multiple attribute exposures' do
|
|
312
|
+
expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
context 'with nested structures' do
|
|
318
|
+
let(:model) { { a: a, b: b, c: { d: nil, e: nil, f: { g: nil, h: nil } } } }
|
|
319
|
+
|
|
320
|
+
context 'when expose_nil option is false' do
|
|
321
|
+
it 'does not expose nil attributes' do
|
|
322
|
+
subject.expose(:a, expose_nil: false)
|
|
323
|
+
subject.expose(:b)
|
|
324
|
+
subject.expose(:c) do
|
|
325
|
+
subject.expose(:d, expose_nil: false)
|
|
326
|
+
subject.expose(:e)
|
|
327
|
+
subject.expose(:f) do
|
|
328
|
+
subject.expose(:g, expose_nil: false)
|
|
329
|
+
subject.expose(:h)
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
expect(subject.represent(model).serializable_hash).to eq(b: nil, c: { e: nil, f: { h: nil } })
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
217
339
|
context 'with a block' do
|
|
218
340
|
it 'errors out if called with multiple attributes' do
|
|
219
341
|
expect { subject.expose(:name, :email) { true } }.to raise_error ArgumentError
|
|
@@ -267,6 +389,10 @@ describe Grape::Entity do
|
|
|
267
389
|
def method_without_args
|
|
268
390
|
'result'
|
|
269
391
|
end
|
|
392
|
+
|
|
393
|
+
def raises_argument_error
|
|
394
|
+
raise ArgumentError, 'something different'
|
|
395
|
+
end
|
|
270
396
|
end
|
|
271
397
|
|
|
272
398
|
describe 'with block passed in' do
|
|
@@ -280,6 +406,17 @@ describe Grape::Entity do
|
|
|
280
406
|
value = subject.represent(object).value_for(:that_method_without_args)
|
|
281
407
|
expect(value).to eq('result')
|
|
282
408
|
end
|
|
409
|
+
|
|
410
|
+
it 'does not suppress ArgumentError' do
|
|
411
|
+
subject.expose :raises_argument_error do |object|
|
|
412
|
+
object.raises_argument_error
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
object = SomeObject.new
|
|
416
|
+
expect do
|
|
417
|
+
subject.represent(object).value_for(:raises_argument_error)
|
|
418
|
+
end.to raise_error(ArgumentError, 'something different')
|
|
419
|
+
end
|
|
283
420
|
end
|
|
284
421
|
|
|
285
422
|
context 'with block passed in via &' do
|
|
@@ -1137,6 +1274,18 @@ describe Grape::Entity do
|
|
|
1137
1274
|
expect(representation).to eq(id: nil, name: nil, user: { id: nil, name: nil, email: nil })
|
|
1138
1275
|
end
|
|
1139
1276
|
end
|
|
1277
|
+
|
|
1278
|
+
context 'when NameError happens in a parameterized block_exposure' do
|
|
1279
|
+
before do
|
|
1280
|
+
subject.expose :raise_no_method_error do |_|
|
|
1281
|
+
foo
|
|
1282
|
+
end
|
|
1283
|
+
end
|
|
1284
|
+
|
|
1285
|
+
it 'does not cause infinite loop' do
|
|
1286
|
+
expect { subject.represent({}, serializable: true) }.to raise_error(NameError)
|
|
1287
|
+
end
|
|
1288
|
+
end
|
|
1140
1289
|
end
|
|
1141
1290
|
end
|
|
1142
1291
|
|
|
@@ -1581,7 +1730,7 @@ describe Grape::Entity do
|
|
|
1581
1730
|
end
|
|
1582
1731
|
|
|
1583
1732
|
fresh_class.class_eval do
|
|
1584
|
-
expose :characteristics, using: EntitySpec::NoPathCharacterEntity, attr_path: proc {
|
|
1733
|
+
expose :characteristics, using: EntitySpec::NoPathCharacterEntity, attr_path: proc {}
|
|
1585
1734
|
end
|
|
1586
1735
|
|
|
1587
1736
|
expect(subject.serializable_hash).to eq(
|
|
@@ -17,7 +17,7 @@ describe Grape::Entity do
|
|
|
17
17
|
expose :post, if: :full
|
|
18
18
|
expose :city
|
|
19
19
|
expose :street
|
|
20
|
-
expose :house
|
|
20
|
+
expose :house, expose_nil: false
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
class Company < Grape::Entity
|
|
@@ -62,9 +62,23 @@ describe Grape::Entity do
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
company_without_house_with_string = {
|
|
66
|
+
'full_name' => 'full_name',
|
|
67
|
+
'name' => 'name',
|
|
68
|
+
'address' => {
|
|
69
|
+
'post' => '123456',
|
|
70
|
+
'city' => 'city',
|
|
71
|
+
'street' => 'street',
|
|
72
|
+
'something_else' => 'something_else'
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
expect(EntitySpec::CompanyWithString.represent(company_with_string).serializable_hash).to eq \
|
|
66
77
|
company.slice(:name).merge(address: company[:address].slice(:city, :street, :house))
|
|
67
78
|
|
|
79
|
+
expect(EntitySpec::CompanyWithString.represent(company_without_house_with_string).serializable_hash).to eq \
|
|
80
|
+
company.slice(:name).merge(address: company[:address].slice(:city, :street))
|
|
81
|
+
|
|
68
82
|
expect(EntitySpec::CompanyWithString.represent(company_with_string, full: true).serializable_hash).to eq \
|
|
69
83
|
company.slice(:full_name, :name).merge(address: company[:address].slice(:city, :street, :house))
|
|
70
84
|
|
data/spec/spec_helper.rb
CHANGED
|
@@ -9,12 +9,16 @@ require 'active_support/core_ext/hash' if ActiveSupport::VERSION &&
|
|
|
9
9
|
ActiveSupport::VERSION::MAJOR &&
|
|
10
10
|
ActiveSupport::VERSION::MAJOR < 4
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
# Skip code covarge on Ruby >= 3.1
|
|
13
|
+
# See https://github.com/simplecov-ruby/simplecov/issues/1003
|
|
14
|
+
unless RUBY_VERSION >= '3.1'
|
|
15
|
+
SimpleCov.start do
|
|
16
|
+
add_filter 'spec/'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Coveralls.wear! unless RUBY_PLATFORM.eql? 'java'
|
|
14
20
|
end
|
|
15
21
|
|
|
16
|
-
Coveralls.wear! unless RUBY_PLATFORM.eql? 'java'
|
|
17
|
-
|
|
18
22
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
19
23
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
20
24
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'support'))
|
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.
|
|
4
|
+
version: 0.10.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Bleigh
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-07-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -160,8 +160,7 @@ extra_rdoc_files: []
|
|
|
160
160
|
files:
|
|
161
161
|
- ".coveralls.yml"
|
|
162
162
|
- ".github/dependabot.yml"
|
|
163
|
-
- ".github/workflows/
|
|
164
|
-
- ".github/workflows/ruby.yml"
|
|
163
|
+
- ".github/workflows/ci.yml"
|
|
165
164
|
- ".gitignore"
|
|
166
165
|
- ".rspec"
|
|
167
166
|
- ".rubocop.yml"
|
|
@@ -232,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
232
231
|
- !ruby/object:Gem::Version
|
|
233
232
|
version: '0'
|
|
234
233
|
requirements: []
|
|
235
|
-
rubygems_version: 3.
|
|
234
|
+
rubygems_version: 3.3.7
|
|
236
235
|
signing_key:
|
|
237
236
|
specification_version: 4
|
|
238
237
|
summary: A simple facade for managing the relationship between your model and API.
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
name: Rubocop
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- '*'
|
|
7
|
-
pull_request:
|
|
8
|
-
branches:
|
|
9
|
-
- '*'
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
rubocop:
|
|
13
|
-
name: Rubocop
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v2
|
|
17
|
-
- uses: actions/setup-ruby@v1
|
|
18
|
-
with:
|
|
19
|
-
ruby-version: '3.0'
|
|
20
|
-
- run: gem install rubocop --no-doc
|
|
21
|
-
- run: rubocop --format progress --format json --out rubocop.json
|
|
22
|
-
id: rubocop
|
|
23
|
-
- uses: duderman/rubocop-annotate-action@v0.1.0
|
|
24
|
-
with:
|
|
25
|
-
path: rubocop.json
|
|
26
|
-
if: ${{ failure() }}
|
data/.github/workflows/ruby.yml
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
name: Ruby
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- '*'
|
|
7
|
-
pull_request:
|
|
8
|
-
branches:
|
|
9
|
-
- '*'
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
spec:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
strategy:
|
|
15
|
-
matrix:
|
|
16
|
-
ruby-version: ['2.6', '2.7', '3.0', head, jruby, truffleruby]
|
|
17
|
-
|
|
18
|
-
steps:
|
|
19
|
-
- uses: actions/checkout@v2
|
|
20
|
-
- name: Set up Ruby
|
|
21
|
-
uses: ruby/setup-ruby@v1
|
|
22
|
-
with:
|
|
23
|
-
ruby-version: ${{ matrix.ruby-version }}
|
|
24
|
-
bundler-cache: true
|
|
25
|
-
- name: Run rspec
|
|
26
|
-
run: bundle exec rspec
|