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
|
require 'grape_entity/delegator/base'
|
2
4
|
require 'grape_entity/delegator/hash_object'
|
3
5
|
require 'grape_entity/delegator/openstruct_object'
|
@@ -8,15 +10,14 @@ module Grape
|
|
8
10
|
class Entity
|
9
11
|
module Delegator
|
10
12
|
def self.new(object)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
13
|
+
delegator_klass =
|
14
|
+
if object.is_a?(Hash) then HashObject
|
15
|
+
elsif defined?(OpenStruct) && object.is_a?(OpenStruct) then OpenStructObject
|
16
|
+
elsif object.respond_to?(:fetch, true) then FetchableObject
|
17
|
+
else PlainObject
|
18
|
+
end
|
19
|
+
|
20
|
+
delegator_klass.new(object)
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
data/lib/grape_entity/entity.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'multi_json'
|
2
4
|
require 'set'
|
3
5
|
|
@@ -124,7 +126,26 @@ module Grape
|
|
124
126
|
# This method is the primary means by which you will declare what attributes
|
125
127
|
# should be exposed by the entity.
|
126
128
|
#
|
129
|
+
# @option options :expose_nil When set to false the associated exposure will not
|
130
|
+
# be rendered if its value is nil.
|
131
|
+
#
|
127
132
|
# @option options :as Declare an alias for the representation of this attribute.
|
133
|
+
# If a proc is presented it is evaluated in the context of the entity so object
|
134
|
+
# and the entity methods are available to it.
|
135
|
+
#
|
136
|
+
# @example as: a proc or lambda
|
137
|
+
#
|
138
|
+
# object = OpenStruct(awesomness: 'awesome_key', awesome: 'not-my-key', other: 'other-key' )
|
139
|
+
#
|
140
|
+
# class MyEntity < Grape::Entity
|
141
|
+
# expose :awesome, as: proc { object.awesomeness }
|
142
|
+
# expose :awesomeness, as: ->(object, opts) { object.other }
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# => { 'awesome_key': 'not-my-key', 'other-key': 'awesome_key' }
|
146
|
+
#
|
147
|
+
# Note the parameters passed in via the lambda syntax.
|
148
|
+
#
|
128
149
|
# @option options :if When passed a Hash, the attribute will only be exposed if the
|
129
150
|
# runtime options match all the conditions passed in. When passed a lambda, the
|
130
151
|
# lambda will execute with two arguments: the object being represented and the
|
@@ -152,6 +173,7 @@ module Grape
|
|
152
173
|
|
153
174
|
if args.size > 1
|
154
175
|
raise ArgumentError, 'You may not use the :as option on multi-attribute exposures.' if options[:as]
|
176
|
+
raise ArgumentError, 'You may not use the :expose_nil on multi-attribute exposures.' if options.key?(:expose_nil)
|
155
177
|
raise ArgumentError, 'You may not use block-setting on multi-attribute exposures.' if block_given?
|
156
178
|
end
|
157
179
|
|
@@ -167,24 +189,24 @@ module Grape
|
|
167
189
|
|
168
190
|
@documentation = nil
|
169
191
|
@nesting_stack ||= []
|
192
|
+
args.each { |attribute| build_exposure_for_attribute(attribute, @nesting_stack, options, block) }
|
193
|
+
end
|
170
194
|
|
171
|
-
|
172
|
-
|
173
|
-
exposure = Exposure.new(attribute, options)
|
195
|
+
def self.build_exposure_for_attribute(attribute, nesting_stack, options, block)
|
196
|
+
exposure_list = nesting_stack.empty? ? root_exposures : nesting_stack.last.nested_exposures
|
174
197
|
|
175
|
-
|
176
|
-
root_exposures << exposure
|
177
|
-
else
|
178
|
-
@nesting_stack.last.nested_exposures << exposure
|
179
|
-
end
|
198
|
+
exposure = Exposure.new(attribute, options)
|
180
199
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
200
|
+
exposure_list.delete_by(attribute) if exposure_list.select_by(attribute).all? { |exp| exp.replaceable_by?(exposure) }
|
201
|
+
|
202
|
+
exposure_list << exposure
|
203
|
+
|
204
|
+
# Nested exposures are given in a block with no parameters.
|
205
|
+
return unless exposure.nesting?
|
206
|
+
|
207
|
+
nesting_stack << exposure
|
208
|
+
block.call
|
209
|
+
nesting_stack.pop
|
188
210
|
end
|
189
211
|
|
190
212
|
# Returns exposures that have been declared for this Entity on the top level.
|
@@ -237,9 +259,7 @@ module Grape
|
|
237
259
|
# #docmentation, any exposure without a documentation key will be ignored.
|
238
260
|
def self.documentation
|
239
261
|
@documentation ||= root_exposures.each_with_object({}) do |exposure, memo|
|
240
|
-
if exposure.documentation && !exposure.documentation.empty?
|
241
|
-
memo[exposure.key] = exposure.documentation
|
242
|
-
end
|
262
|
+
memo[exposure.key] = exposure.documentation if exposure.documentation && !exposure.documentation.empty?
|
243
263
|
end
|
244
264
|
end
|
245
265
|
|
@@ -434,12 +454,8 @@ module Grape
|
|
434
454
|
|
435
455
|
def initialize(object, options = {})
|
436
456
|
@object = object
|
437
|
-
@delegator = Delegator.new
|
438
|
-
@options =
|
439
|
-
options
|
440
|
-
else
|
441
|
-
Options.new options
|
442
|
-
end
|
457
|
+
@delegator = Delegator.new(object)
|
458
|
+
@options = options.is_a?(Options) ? options : Options.new(options)
|
443
459
|
end
|
444
460
|
|
445
461
|
def root_exposures
|
@@ -474,7 +490,11 @@ module Grape
|
|
474
490
|
end
|
475
491
|
|
476
492
|
def exec_with_object(options, &block)
|
477
|
-
|
493
|
+
if block.parameters.count == 1
|
494
|
+
instance_exec(object, &block)
|
495
|
+
else
|
496
|
+
instance_exec(object, options, &block)
|
497
|
+
end
|
478
498
|
end
|
479
499
|
|
480
500
|
def exec_with_attribute(attribute, &block)
|
@@ -506,8 +526,8 @@ module Grape
|
|
506
526
|
end
|
507
527
|
|
508
528
|
# All supported options.
|
509
|
-
OPTIONS = [
|
510
|
-
|
529
|
+
OPTIONS = %i[
|
530
|
+
rewrite as if unless using with proc documentation format_with safe attr_path if_extras unless_extras merge expose_nil
|
511
531
|
].to_set.freeze
|
512
532
|
|
513
533
|
# Merges the given options with current block options.
|
@@ -517,7 +537,7 @@ module Grape
|
|
517
537
|
opts = {}
|
518
538
|
|
519
539
|
merge_logic = proc do |key, existing_val, new_val|
|
520
|
-
if [
|
540
|
+
if %i[if unless].include?(key)
|
521
541
|
if existing_val.is_a?(Hash) && new_val.is_a?(Hash)
|
522
542
|
existing_val.merge(new_val)
|
523
543
|
elsif new_val.is_a?(Hash)
|
@@ -543,7 +563,7 @@ module Grape
|
|
543
563
|
#
|
544
564
|
# @param options [Hash] Exposure options.
|
545
565
|
def self.valid_options(options)
|
546
|
-
options.
|
566
|
+
options.each_key do |key|
|
547
567
|
raise ArgumentError, "#{key.inspect} is not a valid option." unless OPTIONS.include?(key)
|
548
568
|
end
|
549
569
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'grape_entity/exposure/base'
|
2
4
|
require 'grape_entity/exposure/represent_exposure'
|
3
5
|
require 'grape_entity/exposure/block_exposure'
|
@@ -10,67 +12,82 @@ require 'grape_entity/condition'
|
|
10
12
|
module Grape
|
11
13
|
class Entity
|
12
14
|
module Exposure
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
if options[:proc]
|
18
|
-
block_exposure = BlockExposure.new(*base_args, &options[:proc])
|
19
|
-
else
|
20
|
-
delegator_exposure = DelegatorExposure.new(*base_args)
|
21
|
-
end
|
15
|
+
class << self
|
16
|
+
def new(attribute, options)
|
17
|
+
conditions = compile_conditions(attribute, options)
|
18
|
+
base_args = [attribute, options, conditions]
|
22
19
|
|
23
|
-
|
20
|
+
passed_proc = options[:proc]
|
24
21
|
using_class = options[:using]
|
22
|
+
format_with = options[:format_with]
|
25
23
|
|
26
|
-
if
|
27
|
-
|
24
|
+
if using_class
|
25
|
+
build_class_exposure(base_args, using_class, passed_proc)
|
26
|
+
elsif passed_proc
|
27
|
+
build_block_exposure(base_args, passed_proc)
|
28
|
+
elsif format_with
|
29
|
+
build_formatter_exposure(base_args, format_with)
|
30
|
+
elsif options[:nesting]
|
31
|
+
build_nesting_exposure(base_args)
|
28
32
|
else
|
29
|
-
|
33
|
+
build_delegator_exposure(base_args)
|
30
34
|
end
|
35
|
+
end
|
31
36
|
|
32
|
-
|
33
|
-
block_exposure
|
37
|
+
private
|
34
38
|
|
35
|
-
|
36
|
-
|
39
|
+
def compile_conditions(attribute, options)
|
40
|
+
if_conditions = [
|
41
|
+
options[:if_extras],
|
42
|
+
options[:if]
|
43
|
+
].compact.flatten.map { |cond| Condition.new_if(cond) }
|
37
44
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
45
|
+
unless_conditions = [
|
46
|
+
options[:unless_extras],
|
47
|
+
options[:unless]
|
48
|
+
].compact.flatten.map { |cond| Condition.new_unless(cond) }
|
43
49
|
|
44
|
-
|
45
|
-
NestingExposure.new(*base_args)
|
50
|
+
unless_conditions << expose_nil_condition(attribute) if options[:expose_nil] == false
|
46
51
|
|
47
|
-
|
48
|
-
delegator_exposure
|
52
|
+
if_conditions + unless_conditions
|
49
53
|
end
|
50
|
-
end
|
51
54
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
def expose_nil_condition(attribute)
|
56
|
+
Condition.new_unless(
|
57
|
+
proc { |object, _options| Delegator.new(object).delegate(attribute).nil? }
|
58
|
+
)
|
56
59
|
end
|
57
|
-
if_conditions << options[:if] unless options[:if].nil?
|
58
60
|
|
59
|
-
|
60
|
-
|
61
|
+
def build_class_exposure(base_args, using_class, passed_proc)
|
62
|
+
exposure =
|
63
|
+
if passed_proc
|
64
|
+
build_block_exposure(base_args, passed_proc)
|
65
|
+
else
|
66
|
+
build_delegator_exposure(base_args)
|
67
|
+
end
|
68
|
+
|
69
|
+
RepresentExposure.new(*base_args, using_class, exposure)
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_formatter_exposure(base_args, format_with)
|
73
|
+
if format_with.is_a? Symbol
|
74
|
+
FormatterExposure.new(*base_args, format_with)
|
75
|
+
elsif format_with.respond_to?(:call)
|
76
|
+
FormatterBlockExposure.new(*base_args, &format_with)
|
77
|
+
end
|
61
78
|
end
|
62
79
|
|
63
|
-
|
64
|
-
|
65
|
-
unless_conditions.concat(options[:unless_extras])
|
80
|
+
def build_nesting_exposure(base_args)
|
81
|
+
NestingExposure.new(*base_args)
|
66
82
|
end
|
67
|
-
unless_conditions << options[:unless] unless options[:unless].nil?
|
68
83
|
|
69
|
-
|
70
|
-
|
84
|
+
def build_block_exposure(base_args, passed_proc)
|
85
|
+
BlockExposure.new(*base_args, &passed_proc)
|
71
86
|
end
|
72
87
|
|
73
|
-
|
88
|
+
def build_delegator_exposure(base_args)
|
89
|
+
DelegatorExposure.new(*base_args)
|
90
|
+
end
|
74
91
|
end
|
75
92
|
end
|
76
93
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
class Entity
|
3
5
|
module Exposure
|
4
6
|
class Base
|
5
|
-
attr_reader :attribute, :
|
7
|
+
attr_reader :attribute, :is_safe, :documentation, :conditions, :for_merge
|
6
8
|
|
7
9
|
def self.new(attribute, options, conditions, *args, &block)
|
8
10
|
super(attribute, options, conditions).tap { |e| e.setup(*args, &block) }
|
@@ -11,7 +13,8 @@ module Grape
|
|
11
13
|
def initialize(attribute, options, conditions)
|
12
14
|
@attribute = attribute.try(:to_sym)
|
13
15
|
@options = options
|
14
|
-
|
16
|
+
key = options[:as] || attribute
|
17
|
+
@key = key.respond_to?(:to_sym) ? key.to_sym : key
|
15
18
|
@is_safe = options[:safe]
|
16
19
|
@for_merge = options[:merge]
|
17
20
|
@attr_path_proc = options[:attr_path]
|
@@ -41,7 +44,7 @@ module Grape
|
|
41
44
|
end
|
42
45
|
|
43
46
|
# if we have any nesting exposures with the same name.
|
44
|
-
def deep_complex_nesting?
|
47
|
+
def deep_complex_nesting?(entity) # rubocop:disable Lint/UnusedMethodArgument
|
45
48
|
false
|
46
49
|
end
|
47
50
|
|
@@ -102,6 +105,10 @@ module Grape
|
|
102
105
|
end
|
103
106
|
end
|
104
107
|
|
108
|
+
def key(entity = nil)
|
109
|
+
@key.respond_to?(:call) ? entity.exec_with_object(@options, &@key) : @key
|
110
|
+
end
|
111
|
+
|
105
112
|
def with_attr_path(entity, options)
|
106
113
|
path_part = attr_path(entity, options)
|
107
114
|
options.with_attr_path(path_part) do
|
@@ -109,6 +116,10 @@ module Grape
|
|
109
116
|
end
|
110
117
|
end
|
111
118
|
|
119
|
+
def replaceable_by?(other)
|
120
|
+
!nesting? && !conditional? && !other.nesting? && !other.conditional?
|
121
|
+
end
|
122
|
+
|
112
123
|
protected
|
113
124
|
|
114
125
|
attr_reader :options
|