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.
- checksums.yaml +5 -5
- data/.coveralls.yml +1 -0
- data/.gitignore +5 -1
- data/.rspec +1 -1
- data/.rubocop.yml +124 -2
- data/.rubocop_todo.yml +21 -32
- data/.travis.yml +16 -17
- data/CHANGELOG.md +66 -0
- data/Dangerfile +2 -0
- data/Gemfile +8 -8
- data/Guardfile +4 -2
- data/README.md +101 -4
- data/Rakefile +2 -2
- data/bench/serializing.rb +7 -0
- data/grape-entity.gemspec +10 -8
- 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 +4 -2
- 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 +112 -38
- data/lib/grape_entity/exposure.rb +64 -41
- data/lib/grape_entity/exposure/base.rb +20 -6
- 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 +35 -30
- data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +25 -15
- data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +6 -2
- data/lib/grape_entity/exposure/represent_exposure.rb +3 -1
- data/lib/grape_entity/options.rb +44 -58
- data/lib/grape_entity/version.rb +3 -1
- data/spec/grape_entity/entity_spec.rb +243 -47
- data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +6 -4
- data/spec/grape_entity/exposure/represent_exposure_spec.rb +5 -3
- data/spec/grape_entity/exposure_spec.rb +14 -2
- data/spec/grape_entity/hash_spec.rb +38 -1
- data/spec/grape_entity/options_spec.rb +66 -0
- data/spec/spec_helper.rb +17 -0
- metadata +31 -44
@@ -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,88 @@ 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, options) 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, options)
|
56
|
+
Condition.new_unless(
|
57
|
+
proc do |object, _options|
|
58
|
+
if options[:proc].nil?
|
59
|
+
Delegator.new(object).delegate(attribute).nil?
|
60
|
+
else
|
61
|
+
exec_with_object(options, &options[:proc]).nil?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
)
|
56
65
|
end
|
57
|
-
if_conditions << options[:if] unless options[:if].nil?
|
58
66
|
|
59
|
-
|
60
|
-
|
67
|
+
def build_class_exposure(base_args, using_class, passed_proc)
|
68
|
+
exposure =
|
69
|
+
if passed_proc
|
70
|
+
build_block_exposure(base_args, passed_proc)
|
71
|
+
else
|
72
|
+
build_delegator_exposure(base_args)
|
73
|
+
end
|
74
|
+
|
75
|
+
RepresentExposure.new(*base_args, using_class, exposure)
|
76
|
+
end
|
77
|
+
|
78
|
+
def build_formatter_exposure(base_args, format_with)
|
79
|
+
if format_with.is_a? Symbol
|
80
|
+
FormatterExposure.new(*base_args, format_with)
|
81
|
+
elsif format_with.respond_to?(:call)
|
82
|
+
FormatterBlockExposure.new(*base_args, &format_with)
|
83
|
+
end
|
61
84
|
end
|
62
85
|
|
63
|
-
|
64
|
-
|
65
|
-
unless_conditions.concat(options[:unless_extras])
|
86
|
+
def build_nesting_exposure(base_args)
|
87
|
+
NestingExposure.new(*base_args)
|
66
88
|
end
|
67
|
-
unless_conditions << options[:unless] unless options[:unless].nil?
|
68
89
|
|
69
|
-
|
70
|
-
|
90
|
+
def build_block_exposure(base_args, passed_proc)
|
91
|
+
BlockExposure.new(*base_args, &passed_proc)
|
71
92
|
end
|
72
93
|
|
73
|
-
|
94
|
+
def build_delegator_exposure(base_args)
|
95
|
+
DelegatorExposure.new(*base_args)
|
96
|
+
end
|
74
97
|
end
|
75
98
|
end
|
76
99
|
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, :override, :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,11 +13,13 @@ 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]
|
18
21
|
@documentation = options[:documentation]
|
22
|
+
@override = options[:override]
|
19
23
|
@conditions = conditions
|
20
24
|
end
|
21
25
|
|
@@ -34,15 +38,14 @@ module Grape
|
|
34
38
|
@conditions == other.conditions
|
35
39
|
end
|
36
40
|
|
37
|
-
def setup
|
38
|
-
end
|
41
|
+
def setup; end
|
39
42
|
|
40
43
|
def nesting?
|
41
44
|
false
|
42
45
|
end
|
43
46
|
|
44
47
|
# if we have any nesting exposures with the same name.
|
45
|
-
def deep_complex_nesting?
|
48
|
+
def deep_complex_nesting?(entity) # rubocop:disable Lint/UnusedMethodArgument
|
46
49
|
false
|
47
50
|
end
|
48
51
|
|
@@ -51,7 +54,10 @@ module Grape
|
|
51
54
|
if @is_safe
|
52
55
|
is_delegatable
|
53
56
|
else
|
54
|
-
is_delegatable || raise(
|
57
|
+
is_delegatable || raise(
|
58
|
+
NoMethodError,
|
59
|
+
"#{entity.class.name} missing attribute `#{@attribute}' on #{entity.object}"
|
60
|
+
)
|
55
61
|
end
|
56
62
|
end
|
57
63
|
|
@@ -103,6 +109,10 @@ module Grape
|
|
103
109
|
end
|
104
110
|
end
|
105
111
|
|
112
|
+
def key(entity = nil)
|
113
|
+
@key.respond_to?(:call) ? entity.exec_with_object(@options, &@key) : @key
|
114
|
+
end
|
115
|
+
|
106
116
|
def with_attr_path(entity, options)
|
107
117
|
path_part = attr_path(entity, options)
|
108
118
|
options.with_attr_path(path_part) do
|
@@ -110,6 +120,10 @@ module Grape
|
|
110
120
|
end
|
111
121
|
end
|
112
122
|
|
123
|
+
def override?
|
124
|
+
@override
|
125
|
+
end
|
126
|
+
|
113
127
|
protected
|
114
128
|
|
115
129
|
attr_reader :options
|
@@ -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
|
-
|
33
|
-
|
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
|
44
|
-
|
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
|
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).
|
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
|
-
|
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
|
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,16 @@ 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
|
-
|
101
|
-
output[exposure.key]
|
90
|
+
|
91
|
+
output[exposure.key(entity)] ||= []
|
92
|
+
output[exposure.key(entity)] << exposure
|
102
93
|
end
|
103
94
|
table.map do |key, exposures|
|
104
95
|
last_exposure = exposures.last
|
@@ -111,13 +102,27 @@ module Grape
|
|
111
102
|
end
|
112
103
|
new_nested_exposures = nesting_tail.flat_map(&:nested_exposures)
|
113
104
|
NestingExposure.new(key, {}, [], new_nested_exposures).tap do |new_exposure|
|
114
|
-
|
105
|
+
if nesting_tail.any? { |exposure| exposure.deep_complex_nesting?(entity) }
|
106
|
+
new_exposure.instance_variable_set(:@deep_complex_nesting, true)
|
107
|
+
end
|
115
108
|
end
|
116
109
|
else
|
117
110
|
last_exposure
|
118
111
|
end
|
119
112
|
end
|
120
113
|
end
|
114
|
+
|
115
|
+
def map_entity_exposures(entity, options)
|
116
|
+
new_options = nesting_options_for(options)
|
117
|
+
output = OutputBuilder.new(entity)
|
118
|
+
|
119
|
+
normalized_exposures(entity, new_options).each_with_object(output) do |exposure, out|
|
120
|
+
exposure.with_attr_path(entity, new_options) do
|
121
|
+
result = yield(exposure, new_options)
|
122
|
+
out.add(exposure, result)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
121
126
|
end
|
122
127
|
end
|
123
128
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
class Entity
|
3
5
|
module Exposure
|
@@ -7,12 +9,17 @@ module Grape
|
|
7
9
|
|
8
10
|
def initialize(exposures)
|
9
11
|
@exposures = exposures
|
12
|
+
@deep_complex_nesting = nil
|
10
13
|
end
|
11
14
|
|
12
15
|
def find_by(attribute)
|
13
16
|
@exposures.find { |e| e.attribute == attribute }
|
14
17
|
end
|
15
18
|
|
19
|
+
def select_by(attribute)
|
20
|
+
@exposures.select { |e| e.attribute == attribute }
|
21
|
+
end
|
22
|
+
|
16
23
|
def <<(exposure)
|
17
24
|
reset_memoization!
|
18
25
|
@exposures << exposure
|
@@ -29,20 +36,20 @@ module Grape
|
|
29
36
|
@exposures.clear
|
30
37
|
end
|
31
38
|
|
32
|
-
[
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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?
|
44
51
|
].each do |name|
|
45
|
-
class_eval <<-RUBY, __FILE__, __LINE__
|
52
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
46
53
|
def #{name}(*args, &block)
|
47
54
|
@exposures.#{name}(*args, &block)
|
48
55
|
end
|
@@ -50,10 +57,13 @@ module Grape
|
|
50
57
|
end
|
51
58
|
|
52
59
|
# Determine if we have any nesting exposures with the same name.
|
53
|
-
def deep_complex_nesting?
|
60
|
+
def deep_complex_nesting?(entity)
|
54
61
|
if @deep_complex_nesting.nil?
|
55
62
|
all_nesting = select(&:nesting?)
|
56
|
-
@deep_complex_nesting =
|
63
|
+
@deep_complex_nesting =
|
64
|
+
all_nesting
|
65
|
+
.group_by { |exposure| exposure.key(entity) }
|
66
|
+
.any? { |_key, exposures| exposures.length > 1 }
|
57
67
|
else
|
58
68
|
@deep_complex_nesting
|
59
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
|
@@ -16,9 +19,10 @@ module Grape
|
|
16
19
|
# If we have an array which should not be merged - save it with a key as a hash
|
17
20
|
# If we have hash which should be merged - save it without a key (merge)
|
18
21
|
return unless result
|
22
|
+
|
19
23
|
@output_hash.merge! result, &merge_strategy(exposure.for_merge)
|
20
24
|
else
|
21
|
-
@output_hash[exposure.key] = result
|
25
|
+
@output_hash[exposure.key(@entity)] = result
|
22
26
|
end
|
23
27
|
end
|
24
28
|
|