grape-entity 0.4.8 → 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 +5 -5
- data/.coveralls.yml +1 -0
- data/.github/dependabot.yml +20 -0
- data/.github/workflows/ci.yml +41 -0
- data/.gitignore +5 -1
- data/.rspec +2 -1
- data/.rubocop.yml +85 -2
- data/.rubocop_todo.yml +41 -33
- data/CHANGELOG.md +243 -37
- data/CONTRIBUTING.md +1 -1
- data/Dangerfile +3 -0
- data/Gemfile +11 -7
- data/Guardfile +4 -2
- data/LICENSE +1 -1
- data/README.md +272 -19
- data/Rakefile +9 -11
- data/UPGRADING.md +35 -0
- data/bench/serializing.rb +7 -0
- data/grape-entity.gemspec +13 -8
- data/lib/grape-entity.rb +2 -0
- data/lib/grape_entity/condition/base.rb +37 -0
- data/lib/grape_entity/condition/block_condition.rb +23 -0
- data/lib/grape_entity/condition/hash_condition.rb +27 -0
- data/lib/grape_entity/condition/symbol_condition.rb +23 -0
- data/lib/grape_entity/condition.rb +35 -0
- data/lib/grape_entity/delegator/base.rb +7 -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/delegator.rb +14 -9
- data/lib/grape_entity/deprecated.rb +13 -0
- data/lib/grape_entity/entity.rb +192 -258
- data/lib/grape_entity/exposure/base.rb +138 -0
- data/lib/grape_entity/exposure/block_exposure.rb +31 -0
- data/lib/grape_entity/exposure/delegator_exposure.rb +13 -0
- data/lib/grape_entity/exposure/formatter_block_exposure.rb +27 -0
- data/lib/grape_entity/exposure/formatter_exposure.rb +32 -0
- data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +83 -0
- data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +66 -0
- data/lib/grape_entity/exposure/nesting_exposure.rb +133 -0
- data/lib/grape_entity/exposure/represent_exposure.rb +50 -0
- data/lib/grape_entity/exposure.rb +105 -0
- data/lib/grape_entity/options.rb +132 -0
- data/lib/grape_entity/version.rb +3 -1
- data/lib/grape_entity.rb +9 -2
- data/spec/grape_entity/entity_spec.rb +903 -184
- data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +58 -0
- data/spec/grape_entity/exposure/represent_exposure_spec.rb +32 -0
- data/spec/grape_entity/exposure_spec.rb +102 -0
- data/spec/grape_entity/hash_spec.rb +91 -0
- data/spec/grape_entity/options_spec.rb +66 -0
- data/spec/spec_helper.rb +21 -2
- metadata +91 -18
- data/.travis.yml +0 -19
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext'
|
5
|
+
|
6
|
+
module Grape
|
7
|
+
class Entity
|
8
|
+
module Exposure
|
9
|
+
class Base
|
10
|
+
attr_reader :attribute, :is_safe, :documentation, :override, :conditions, :for_merge
|
11
|
+
|
12
|
+
def self.new(attribute, options, conditions, *args, &block)
|
13
|
+
super(attribute, options, conditions).tap { |e| e.setup(*args, &block) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(attribute, options, conditions)
|
17
|
+
@attribute = attribute.try(:to_sym)
|
18
|
+
@options = options
|
19
|
+
key = options[:as] || attribute
|
20
|
+
@key = key.respond_to?(:to_sym) ? key.to_sym : key
|
21
|
+
@is_safe = options[:safe]
|
22
|
+
@default_value = options[:default]
|
23
|
+
@for_merge = options[:merge]
|
24
|
+
@attr_path_proc = options[:attr_path]
|
25
|
+
@documentation = options[:documentation]
|
26
|
+
@override = options[:override]
|
27
|
+
@conditions = conditions
|
28
|
+
end
|
29
|
+
|
30
|
+
def dup(&block)
|
31
|
+
self.class.new(*dup_args, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def dup_args
|
35
|
+
[@attribute, @options, @conditions.map(&:dup)]
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(other)
|
39
|
+
self.class == other.class &&
|
40
|
+
@attribute == other.attribute &&
|
41
|
+
@options == other.options &&
|
42
|
+
@conditions == other.conditions
|
43
|
+
end
|
44
|
+
|
45
|
+
def setup; end
|
46
|
+
|
47
|
+
def nesting?
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
# if we have any nesting exposures with the same name.
|
52
|
+
def deep_complex_nesting?(entity) # rubocop:disable Lint/UnusedMethodArgument
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
def valid?(entity)
|
57
|
+
is_delegatable = entity.delegator.delegatable?(@attribute) || entity.respond_to?(@attribute, true)
|
58
|
+
if @is_safe
|
59
|
+
is_delegatable
|
60
|
+
else
|
61
|
+
is_delegatable || raise(
|
62
|
+
NoMethodError,
|
63
|
+
"#{entity.class.name} missing attribute `#{@attribute}' on #{entity.object}"
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def value(_entity, _options)
|
69
|
+
raise NotImplementedError
|
70
|
+
end
|
71
|
+
|
72
|
+
def serializable_value(entity, options)
|
73
|
+
partial_output = valid_value(entity, options)
|
74
|
+
|
75
|
+
if partial_output.respond_to?(:serializable_hash)
|
76
|
+
partial_output.serializable_hash
|
77
|
+
elsif partial_output.is_a?(Array) && partial_output.all? { |o| o.respond_to?(:serializable_hash) }
|
78
|
+
partial_output.map(&:serializable_hash)
|
79
|
+
elsif partial_output.is_a?(Hash)
|
80
|
+
partial_output.each do |key, value|
|
81
|
+
partial_output[key] = value.serializable_hash if value.respond_to?(:serializable_hash)
|
82
|
+
end
|
83
|
+
else
|
84
|
+
partial_output
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def valid_value(entity, options)
|
89
|
+
return unless valid?(entity)
|
90
|
+
|
91
|
+
output = value(entity, options)
|
92
|
+
output.blank? && @default_value.present? ? @default_value : output
|
93
|
+
end
|
94
|
+
|
95
|
+
def should_return_key?(options)
|
96
|
+
options.should_return_key?(@key)
|
97
|
+
end
|
98
|
+
|
99
|
+
def conditional?
|
100
|
+
!@conditions.empty?
|
101
|
+
end
|
102
|
+
|
103
|
+
def conditions_met?(entity, options)
|
104
|
+
@conditions.all? { |condition| condition.met? entity, options }
|
105
|
+
end
|
106
|
+
|
107
|
+
def should_expose?(entity, options)
|
108
|
+
should_return_key?(options) && conditions_met?(entity, options)
|
109
|
+
end
|
110
|
+
|
111
|
+
def attr_path(entity, options)
|
112
|
+
if @attr_path_proc
|
113
|
+
entity.exec_with_object(options, &@attr_path_proc)
|
114
|
+
else
|
115
|
+
@key
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def key(entity = nil)
|
120
|
+
@key.respond_to?(:call) ? entity.exec_with_object(@options, &@key) : @key
|
121
|
+
end
|
122
|
+
|
123
|
+
def with_attr_path(entity, options, &block)
|
124
|
+
path_part = attr_path(entity, options)
|
125
|
+
options.with_attr_path(path_part, &block)
|
126
|
+
end
|
127
|
+
|
128
|
+
def override?
|
129
|
+
@override
|
130
|
+
end
|
131
|
+
|
132
|
+
protected
|
133
|
+
|
134
|
+
attr_reader :options
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Entity
|
5
|
+
module Exposure
|
6
|
+
class BlockExposure < Base
|
7
|
+
attr_reader :block
|
8
|
+
|
9
|
+
def value(entity, options)
|
10
|
+
entity.exec_with_object(options, &@block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def dup
|
14
|
+
super(&@block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
super && @block == other.block
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?(_entity)
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def setup(&block)
|
26
|
+
@block = block
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Entity
|
5
|
+
module Exposure
|
6
|
+
class FormatterBlockExposure < Base
|
7
|
+
attr_reader :format_with
|
8
|
+
|
9
|
+
def setup(&format_with)
|
10
|
+
@format_with = format_with
|
11
|
+
end
|
12
|
+
|
13
|
+
def dup
|
14
|
+
super(&@format_with)
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
super && @format_with == other.format_with
|
19
|
+
end
|
20
|
+
|
21
|
+
def value(entity, _options)
|
22
|
+
entity.exec_with_attribute(attribute, &@format_with)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Entity
|
5
|
+
module Exposure
|
6
|
+
class FormatterExposure < Base
|
7
|
+
attr_reader :format_with
|
8
|
+
|
9
|
+
def setup(format_with)
|
10
|
+
@format_with = format_with
|
11
|
+
end
|
12
|
+
|
13
|
+
def dup_args
|
14
|
+
[*super, format_with]
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
super && @format_with == other.format_with
|
19
|
+
end
|
20
|
+
|
21
|
+
def value(entity, _options)
|
22
|
+
formatters = entity.class.formatters
|
23
|
+
if formatters[@format_with]
|
24
|
+
entity.exec_with_attribute(attribute, &formatters[@format_with])
|
25
|
+
else
|
26
|
+
entity.send(@format_with, entity.delegate_attribute(attribute))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Entity
|
5
|
+
module Exposure
|
6
|
+
class NestingExposure
|
7
|
+
class NestedExposures
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def initialize(exposures)
|
11
|
+
@exposures = exposures
|
12
|
+
@deep_complex_nesting = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_by(attribute)
|
16
|
+
@exposures.find { |e| e.attribute == attribute }
|
17
|
+
end
|
18
|
+
|
19
|
+
def select_by(attribute)
|
20
|
+
@exposures.select { |e| e.attribute == attribute }
|
21
|
+
end
|
22
|
+
|
23
|
+
def <<(exposure)
|
24
|
+
reset_memoization!
|
25
|
+
@exposures << exposure
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete_by(*attributes)
|
29
|
+
reset_memoization!
|
30
|
+
@exposures.reject! { |e| attributes.include? e.attribute }
|
31
|
+
@exposures
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear
|
35
|
+
reset_memoization!
|
36
|
+
@exposures.clear
|
37
|
+
end
|
38
|
+
|
39
|
+
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
40
|
+
%i[
|
41
|
+
each
|
42
|
+
to_ary to_a
|
43
|
+
all?
|
44
|
+
select
|
45
|
+
each_with_object
|
46
|
+
\[\]
|
47
|
+
==
|
48
|
+
size
|
49
|
+
count
|
50
|
+
length
|
51
|
+
empty?
|
52
|
+
].each do |name|
|
53
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
54
|
+
def #{name}(*args, &block)
|
55
|
+
@exposures.#{name}(*args, &block)
|
56
|
+
end
|
57
|
+
RUBY
|
58
|
+
end
|
59
|
+
# rubocop:enable Style/DocumentDynamicEvalDefinition
|
60
|
+
|
61
|
+
# Determine if we have any nesting exposures with the same name.
|
62
|
+
def deep_complex_nesting?(entity)
|
63
|
+
if @deep_complex_nesting.nil?
|
64
|
+
all_nesting = select(&:nesting?)
|
65
|
+
@deep_complex_nesting =
|
66
|
+
all_nesting
|
67
|
+
.group_by { |exposure| exposure.key(entity) }
|
68
|
+
.any? { |_key, exposures| exposures.length > 1 }
|
69
|
+
else
|
70
|
+
@deep_complex_nesting
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def reset_memoization!
|
77
|
+
@deep_complex_nesting = nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Entity
|
5
|
+
module Exposure
|
6
|
+
class NestingExposure
|
7
|
+
class OutputBuilder < SimpleDelegator
|
8
|
+
def initialize(entity)
|
9
|
+
@entity = entity
|
10
|
+
@output_hash = {}
|
11
|
+
@output_collection = []
|
12
|
+
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(exposure, result)
|
17
|
+
# Save a result array in collections' array if it should be merged
|
18
|
+
if result.is_a?(Array) && exposure.for_merge
|
19
|
+
@output_collection << result
|
20
|
+
elsif exposure.for_merge
|
21
|
+
# If we have an array which should not be merged - save it with a key as a hash
|
22
|
+
# If we have hash which should be merged - save it without a key (merge)
|
23
|
+
return unless result
|
24
|
+
|
25
|
+
@output_hash.merge! result, &merge_strategy(exposure.for_merge)
|
26
|
+
else
|
27
|
+
@output_hash[exposure.key(@entity)] = result
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def kind_of?(klass)
|
32
|
+
klass == output.class || super
|
33
|
+
end
|
34
|
+
alias is_a? kind_of?
|
35
|
+
|
36
|
+
def __getobj__
|
37
|
+
output
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# If output_collection contains at least one element we have to represent the output as a collection
|
43
|
+
def output
|
44
|
+
if @output_collection.empty?
|
45
|
+
output = @output_hash
|
46
|
+
else
|
47
|
+
output = @output_collection
|
48
|
+
output << @output_hash unless @output_hash.empty?
|
49
|
+
output.flatten!
|
50
|
+
end
|
51
|
+
output
|
52
|
+
end
|
53
|
+
|
54
|
+
# In case if we want to solve collisions providing lambda to :merge option
|
55
|
+
def merge_strategy(for_merge)
|
56
|
+
if for_merge.respond_to? :call
|
57
|
+
for_merge
|
58
|
+
else
|
59
|
+
-> {}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Entity
|
5
|
+
module Exposure
|
6
|
+
class NestingExposure < Base
|
7
|
+
attr_reader :nested_exposures
|
8
|
+
|
9
|
+
def setup(nested_exposures = [])
|
10
|
+
@nested_exposures = NestedExposures.new(nested_exposures)
|
11
|
+
end
|
12
|
+
|
13
|
+
def dup_args
|
14
|
+
[*super, @nested_exposures.map(&:dup)]
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
super && @nested_exposures == other.nested_exposures
|
19
|
+
end
|
20
|
+
|
21
|
+
def nesting?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_nested_exposure(attribute)
|
26
|
+
nested_exposures.find_by(attribute)
|
27
|
+
end
|
28
|
+
|
29
|
+
def valid?(entity)
|
30
|
+
nested_exposures.all? { |e| e.valid?(entity) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def value(entity, options)
|
34
|
+
map_entity_exposures(entity, options) do |exposure, nested_options|
|
35
|
+
exposure.value(entity, nested_options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def serializable_value(entity, options)
|
40
|
+
map_entity_exposures(entity, options) do |exposure, nested_options|
|
41
|
+
exposure.serializable_value(entity, nested_options)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def valid_value_for(key, entity, options)
|
46
|
+
new_options = nesting_options_for(options)
|
47
|
+
|
48
|
+
key_exposures = normalized_exposures(entity, new_options).select { |e| e.key(entity) == key }
|
49
|
+
|
50
|
+
key_exposures.map do |exposure|
|
51
|
+
exposure.with_attr_path(entity, new_options) do
|
52
|
+
exposure.valid_value(entity, new_options)
|
53
|
+
end
|
54
|
+
end.last
|
55
|
+
end
|
56
|
+
|
57
|
+
# if we have any nesting exposures with the same name.
|
58
|
+
# delegate :deep_complex_nesting?(entity), to: :nested_exposures
|
59
|
+
def deep_complex_nesting?(entity)
|
60
|
+
nested_exposures.deep_complex_nesting?(entity)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def nesting_options_for(options)
|
66
|
+
if @key
|
67
|
+
options.for_nesting(@key)
|
68
|
+
else
|
69
|
+
options
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def easy_normalized_exposures(entity, options)
|
74
|
+
nested_exposures.select do |exposure|
|
75
|
+
exposure.with_attr_path(entity, options) do
|
76
|
+
exposure.should_expose?(entity, options)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# This method 'merges' subsequent nesting exposures with the same name if it's needed
|
82
|
+
def normalized_exposures(entity, options)
|
83
|
+
return easy_normalized_exposures(entity, options) unless deep_complex_nesting?(entity) # optimization
|
84
|
+
|
85
|
+
table = nested_exposures.each_with_object({}) do |exposure, output|
|
86
|
+
should_expose = exposure.with_attr_path(entity, options) do
|
87
|
+
exposure.should_expose?(entity, options)
|
88
|
+
end
|
89
|
+
next unless should_expose
|
90
|
+
|
91
|
+
output[exposure.key(entity)] ||= []
|
92
|
+
output[exposure.key(entity)] << exposure
|
93
|
+
end
|
94
|
+
|
95
|
+
table.map do |key, exposures|
|
96
|
+
last_exposure = exposures.last
|
97
|
+
|
98
|
+
if last_exposure.nesting?
|
99
|
+
# For the given key if the last candidates for exposing are nesting then combine them.
|
100
|
+
nesting_tail = []
|
101
|
+
exposures.reverse_each do |exposure|
|
102
|
+
nesting_tail.unshift exposure if exposure.nesting?
|
103
|
+
end
|
104
|
+
new_nested_exposures = nesting_tail.flat_map(&:nested_exposures)
|
105
|
+
NestingExposure.new(key, {}, [], new_nested_exposures).tap do |new_exposure|
|
106
|
+
if nesting_tail.any? { |exposure| exposure.deep_complex_nesting?(entity) }
|
107
|
+
new_exposure.instance_variable_set(:@deep_complex_nesting, true)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
else
|
111
|
+
last_exposure
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def map_entity_exposures(entity, options)
|
117
|
+
new_options = nesting_options_for(options)
|
118
|
+
output = OutputBuilder.new(entity)
|
119
|
+
|
120
|
+
normalized_exposures(entity, new_options).each_with_object(output) do |exposure, out|
|
121
|
+
exposure.with_attr_path(entity, new_options) do
|
122
|
+
result = yield(exposure, new_options)
|
123
|
+
out.add(exposure, result)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
require 'grape_entity/exposure/nesting_exposure/nested_exposures'
|
133
|
+
require 'grape_entity/exposure/nesting_exposure/output_builder'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Entity
|
5
|
+
module Exposure
|
6
|
+
class RepresentExposure < Base
|
7
|
+
attr_reader :using_class_name, :subexposure
|
8
|
+
|
9
|
+
def setup(using_class_name, subexposure)
|
10
|
+
@using_class = nil
|
11
|
+
@using_class_name = using_class_name
|
12
|
+
@subexposure = subexposure
|
13
|
+
end
|
14
|
+
|
15
|
+
def dup_args
|
16
|
+
[*super, using_class_name, subexposure]
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
super &&
|
21
|
+
@using_class_name == other.using_class_name &&
|
22
|
+
@subexposure == other.subexposure
|
23
|
+
end
|
24
|
+
|
25
|
+
def value(entity, options)
|
26
|
+
new_options = options.for_nesting(key(entity))
|
27
|
+
using_class.represent(@subexposure.value(entity, options), new_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def valid?(entity)
|
31
|
+
@subexposure.valid? entity
|
32
|
+
end
|
33
|
+
|
34
|
+
def using_class
|
35
|
+
@using_class ||= if @using_class_name.respond_to? :constantize
|
36
|
+
@using_class_name.constantize
|
37
|
+
else
|
38
|
+
@using_class_name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def using_options_for(options)
|
45
|
+
options.for_nesting(key)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'grape_entity/exposure/base'
|
4
|
+
require 'grape_entity/exposure/represent_exposure'
|
5
|
+
require 'grape_entity/exposure/block_exposure'
|
6
|
+
require 'grape_entity/exposure/delegator_exposure'
|
7
|
+
require 'grape_entity/exposure/formatter_exposure'
|
8
|
+
require 'grape_entity/exposure/formatter_block_exposure'
|
9
|
+
require 'grape_entity/exposure/nesting_exposure'
|
10
|
+
require 'grape_entity/condition'
|
11
|
+
|
12
|
+
module Grape
|
13
|
+
class Entity
|
14
|
+
module Exposure
|
15
|
+
class << self
|
16
|
+
def new(attribute, options)
|
17
|
+
conditions = compile_conditions(attribute, options)
|
18
|
+
base_args = [attribute, options, conditions]
|
19
|
+
|
20
|
+
passed_proc = options[:proc]
|
21
|
+
using_class = options[:using]
|
22
|
+
format_with = options[:format_with]
|
23
|
+
|
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)
|
32
|
+
else
|
33
|
+
build_delegator_exposure(base_args)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
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) }
|
44
|
+
|
45
|
+
unless_conditions = [
|
46
|
+
options[:unless_extras],
|
47
|
+
options[:unless]
|
48
|
+
].compact.flatten.map { |cond| Condition.new_unless(cond) }
|
49
|
+
|
50
|
+
unless_conditions << expose_nil_condition(attribute, options) if options[:expose_nil] == false
|
51
|
+
|
52
|
+
if_conditions + unless_conditions
|
53
|
+
end
|
54
|
+
|
55
|
+
def expose_nil_condition(attribute, options)
|
56
|
+
Condition.new_unless(
|
57
|
+
proc do |object, _options|
|
58
|
+
if options[:proc].nil?
|
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
|
65
|
+
else
|
66
|
+
exec_with_object(options, &options[:proc]).nil?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_class_exposure(base_args, using_class, passed_proc)
|
73
|
+
exposure =
|
74
|
+
if passed_proc
|
75
|
+
build_block_exposure(base_args, passed_proc)
|
76
|
+
else
|
77
|
+
build_delegator_exposure(base_args)
|
78
|
+
end
|
79
|
+
|
80
|
+
RepresentExposure.new(*base_args, using_class, exposure)
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_formatter_exposure(base_args, format_with)
|
84
|
+
if format_with.is_a? Symbol
|
85
|
+
FormatterExposure.new(*base_args, format_with)
|
86
|
+
elsif format_with.respond_to?(:call)
|
87
|
+
FormatterBlockExposure.new(*base_args, &format_with)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def build_nesting_exposure(base_args)
|
92
|
+
NestingExposure.new(*base_args)
|
93
|
+
end
|
94
|
+
|
95
|
+
def build_block_exposure(base_args, passed_proc)
|
96
|
+
BlockExposure.new(*base_args, &passed_proc)
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_delegator_exposure(base_args)
|
100
|
+
DelegatorExposure.new(*base_args)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|