serega 0.42.0 → 0.50.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 +4 -4
- data/README.md +124 -1207
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +186 -131
- data/lib/serega/attribute_normalizer.rb +164 -345
- data/lib/serega/attribute_normalizers/batch.rb +45 -0
- data/lib/serega/attribute_normalizers/block.rb +81 -0
- data/lib/serega/attribute_normalizers/delegate.rb +34 -0
- data/lib/serega/attribute_normalizers/hash_access.rb +37 -0
- data/lib/serega/attribute_normalizers/preloads.rb +46 -0
- data/lib/serega/attribute_value_resolvers/batch.rb +5 -54
- data/lib/serega/attribute_value_resolvers/const.rb +2 -24
- data/lib/serega/attribute_value_resolvers/delegate.rb +17 -28
- data/lib/serega/attribute_value_resolvers/hash_access.rb +34 -52
- data/lib/serega/attribute_value_resolvers/keyword.rb +10 -25
- data/lib/serega/attribute_values.rb +46 -0
- data/lib/serega/batch_loader.rb +37 -0
- data/lib/serega/conditions.rb +56 -0
- data/lib/serega/config.rb +105 -144
- data/lib/serega/engine/pulls.rb +64 -0
- data/lib/serega/engine/run.rb +106 -0
- data/lib/serega/errors.rb +36 -8
- data/lib/serega/formatter.rb +98 -0
- data/lib/serega/plan/cache.rb +96 -0
- data/lib/serega/plan/per_ractor_cache.rb +23 -0
- data/lib/serega/plan.rb +101 -128
- data/lib/serega/plan_point.rb +44 -151
- data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +13 -80
- data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +8 -75
- data/lib/serega/plugins/context_metadata/context_metadata.rb +40 -123
- data/lib/serega/plugins/metadata/check_params.rb +80 -0
- data/lib/serega/plugins/metadata/meta_attribute.rb +40 -79
- data/lib/serega/plugins/metadata/metadata.rb +40 -168
- data/lib/serega/plugins/root/root.rb +91 -235
- data/lib/serega/plugins.rb +16 -49
- data/lib/serega/presenter.rb +61 -19
- data/lib/serega/result_builder.rb +68 -0
- data/lib/serega/result_code.rb +203 -0
- data/lib/serega/shareable.rb +52 -0
- data/lib/serega/source_group.rb +187 -0
- data/lib/serega/utils/collection_detector.rb +19 -13
- data/lib/serega/utils/deep_merge.rb +26 -0
- data/lib/serega/utils/enum_deep_dup.rb +10 -16
- data/lib/serega/utils/enum_deep_freeze.rb +8 -14
- data/lib/serega/utils/method_signature.rb +6 -15
- data/lib/serega/utils/parse_string_modifiers.rb +63 -0
- data/lib/serega/utils/ractor_local.rb +36 -0
- data/lib/serega/utils/serialized_attribute_error.rb +60 -20
- data/lib/serega/utils/to_hash.rb +5 -26
- data/lib/serega/validations/attribute/check_block.rb +3 -15
- data/lib/serega/validations/attribute/check_name.rb +4 -15
- data/lib/serega/validations/attribute/check_opt_base_serializer.rb +3 -15
- data/lib/serega/validations/attribute/check_opt_batch.rb +26 -56
- data/lib/serega/validations/attribute/check_opt_condition.rb +55 -0
- data/lib/serega/validations/attribute/check_opt_delegate.rb +10 -63
- data/lib/serega/validations/attribute/check_opt_format.rb +36 -0
- data/lib/serega/validations/attribute/check_opt_hash_access.rb +11 -59
- data/lib/serega/validations/attribute/check_opt_hide.rb +3 -14
- data/lib/serega/validations/attribute/check_opt_many.rb +18 -20
- data/lib/serega/validations/attribute/check_opt_method.rb +3 -25
- data/lib/serega/validations/attribute/check_opt_preload.rb +3 -26
- data/lib/serega/validations/attribute/check_opt_serializer.rb +4 -18
- data/lib/serega/validations/attribute/check_opt_value.rb +3 -21
- data/lib/serega/validations/check_attribute_params.rb +92 -71
- data/lib/serega/validations/check_batch_loader_params.rb +43 -62
- data/lib/serega/validations/check_formatter.rb +80 -0
- data/lib/serega/validations/check_serialize_keywords.rb +23 -0
- data/lib/serega/validations/initiate/check_modifiers.rb +14 -20
- data/lib/serega/validations/utils/check_allowed_keys.rb +3 -13
- data/lib/serega/validations/utils/check_hash_access_value.rb +44 -0
- data/lib/serega/validations/utils/check_opt_is_bool.rb +3 -13
- data/lib/serega/validations/utils/check_opt_is_hash.rb +3 -13
- data/lib/serega/validations/utils/check_opt_is_string_or_symbol.rb +3 -13
- data/lib/serega/version.rb +1 -4
- data/lib/serega.rb +459 -388
- data/sig/serega/plugins.rbs +49 -0
- data/sig/serega.rbs +142 -0
- metadata +42 -38
- data/lib/serega/data_builder.rb +0 -54
- data/lib/serega/engine/level.rb +0 -59
- data/lib/serega/engine/level_queue.rb +0 -56
- data/lib/serega/engine/loader.rb +0 -70
- data/lib/serega/helpers/serializer_class_helper.rb +0 -19
- data/lib/serega/object_serializer.rb +0 -125
- data/lib/serega/plugins/camel_case/camel_case.rb +0 -213
- data/lib/serega/plugins/depth_limit/depth_limit.rb +0 -206
- data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +0 -69
- data/lib/serega/plugins/explicit_many_option/validations/check_opt_many.rb +0 -38
- data/lib/serega/plugins/formatters/formatters.rb +0 -322
- data/lib/serega/plugins/if/if.rb +0 -287
- data/lib/serega/plugins/if/validations/check_opt_if.rb +0 -72
- data/lib/serega/plugins/if/validations/check_opt_if_value.rb +0 -77
- data/lib/serega/plugins/if/validations/check_opt_unless.rb +0 -72
- data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +0 -77
- data/lib/serega/plugins/metadata/validations/check_block.rb +0 -47
- data/lib/serega/plugins/metadata/validations/check_opt_const.rb +0 -39
- data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +0 -35
- data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +0 -35
- data/lib/serega/plugins/metadata/validations/check_opt_value.rb +0 -61
- data/lib/serega/plugins/metadata/validations/check_opts.rb +0 -59
- data/lib/serega/plugins/metadata/validations/check_path.rb +0 -50
- data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +0 -111
- data/lib/serega/plugins/string_modifiers/string_modifiers.rb +0 -63
- data/lib/serega/utils/symbol_name.rb +0 -39
- data/lib/serega/validations/attribute/check_opt_const.rb +0 -40
- data/lib/serega/validations/check_initiate_params.rb +0 -57
- data/lib/serega/validations/check_serialize_params.rb +0 -54
|
@@ -4,398 +4,217 @@ class Serega
|
|
|
4
4
|
#
|
|
5
5
|
# Prepares provided attribute options
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# Instantiates attribute options normalizer
|
|
20
|
-
#
|
|
21
|
-
# @param initials [Hash] new attribute options
|
|
22
|
-
#
|
|
23
|
-
# @return [SeregaAttributeNormalizer] Instantiated attribute options normalizer
|
|
24
|
-
#
|
|
25
|
-
def initialize(initials)
|
|
26
|
-
@init_name = initials[:name]
|
|
27
|
-
@init_opts = initials[:opts]
|
|
28
|
-
@init_block = initials[:block]
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
#
|
|
32
|
-
# Symbolized initial attribute name
|
|
33
|
-
#
|
|
34
|
-
# @return [Symbol] Attribute normalized name
|
|
35
|
-
#
|
|
36
|
-
def name
|
|
37
|
-
@name ||= prepare_name
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
# Symbolized initial attribute method name
|
|
42
|
-
#
|
|
43
|
-
# @return [Symbol] Attribute normalized method name
|
|
44
|
-
#
|
|
45
|
-
def method_name
|
|
46
|
-
@method_name ||= prepare_method_name
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
#
|
|
50
|
-
# Combines all options to return single block that will be used to find
|
|
51
|
-
# attribute value during serialization
|
|
52
|
-
#
|
|
53
|
-
# @return [#call] Attribute normalized callable value block
|
|
54
|
-
#
|
|
55
|
-
def value_block
|
|
56
|
-
@value_block ||= prepare_value_block
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
#
|
|
60
|
-
# Detects value block parameters signature
|
|
61
|
-
#
|
|
62
|
-
# @return [String] value block parameters signature
|
|
63
|
-
#
|
|
64
|
-
def value_block_signature
|
|
65
|
-
@value_block_signature ||= SeregaUtils::MethodSignature.call(value_block, pos_limit: 2, keyword_args: [:ctx])
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
#
|
|
69
|
-
# Shows if attribute is specified to be hidden
|
|
70
|
-
#
|
|
71
|
-
# @return [Boolean, nil] if attribute must be hidden by default
|
|
72
|
-
#
|
|
73
|
-
def hide
|
|
74
|
-
return @hide if instance_variable_defined?(:@hide)
|
|
75
|
-
|
|
76
|
-
@hide = prepare_hide
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
#
|
|
80
|
-
# Shows if attribute is specified to be a one-to-many relationship
|
|
81
|
-
#
|
|
82
|
-
# @return [Boolean, nil] if attribute is specified to be a one-to-many relationship
|
|
83
|
-
#
|
|
84
|
-
def many
|
|
85
|
-
return @many if instance_variable_defined?(:@many)
|
|
7
|
+
class SeregaAttributeNormalizer # :nodoc:
|
|
8
|
+
# Attribute initial params
|
|
9
|
+
attr_reader :init_name, :init_opts, :init_block
|
|
10
|
+
|
|
11
|
+
# Instantiates attribute options normalizer
|
|
12
|
+
def initialize(serializer_class, initials)
|
|
13
|
+
@serializer_class = serializer_class
|
|
14
|
+
@init_name = initials[:name]
|
|
15
|
+
@init_opts = initials[:opts]
|
|
16
|
+
@init_block = initials[:block]
|
|
17
|
+
@method_name = nil
|
|
18
|
+
end
|
|
86
19
|
|
|
87
|
-
|
|
88
|
-
|
|
20
|
+
# Symbolized initial attribute name
|
|
21
|
+
def name
|
|
22
|
+
@name ||= prepare_name
|
|
23
|
+
end
|
|
89
24
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def serializer
|
|
95
|
-
return @serializer if instance_variable_defined?(:@serializer)
|
|
25
|
+
# Symbolized initial attribute method name
|
|
26
|
+
def method_name
|
|
27
|
+
@method_name ||= prepare_method_name
|
|
28
|
+
end
|
|
96
29
|
|
|
97
|
-
|
|
98
|
-
|
|
30
|
+
# Combines all options to return single block that will be used to find
|
|
31
|
+
# attribute value during serialization
|
|
32
|
+
def value_block
|
|
33
|
+
@value_block ||= prepare_value_block
|
|
34
|
+
end
|
|
99
35
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
#
|
|
105
|
-
# @return [Object] Attribute default value
|
|
106
|
-
#
|
|
107
|
-
def default
|
|
108
|
-
return @default if instance_variable_defined?(:@default)
|
|
109
|
-
|
|
110
|
-
@default = prepare_default
|
|
111
|
-
end
|
|
36
|
+
# Detects value block parameters signature
|
|
37
|
+
def value_block_signature
|
|
38
|
+
@value_block_signature ||= SeregaUtils::MethodSignature.call(value_block, pos_limit: 2, keyword_args: [:ctx])
|
|
39
|
+
end
|
|
112
40
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# @return [Object, nil] declared preloads, or nil when the attribute has none
|
|
117
|
-
#
|
|
118
|
-
def preloads
|
|
119
|
-
return @preloads if instance_variable_defined?(:@preloads)
|
|
41
|
+
# Shows if attribute is specified to be hidden
|
|
42
|
+
def hide
|
|
43
|
+
return @hide if instance_variable_defined?(:@hide)
|
|
120
44
|
|
|
121
|
-
|
|
122
|
-
|
|
45
|
+
@hide = prepare_hide
|
|
46
|
+
end
|
|
123
47
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def batch_loaders
|
|
128
|
-
@batch_loaders ||= prepare_batch_loaders
|
|
129
|
-
end
|
|
48
|
+
# Shows if attribute is specified to be a one-to-many relationship
|
|
49
|
+
def many
|
|
50
|
+
return @many if instance_variable_defined?(:@many)
|
|
130
51
|
|
|
131
|
-
|
|
52
|
+
@many = prepare_many
|
|
53
|
+
end
|
|
132
54
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
55
|
+
# Shows specified attribute serializer
|
|
56
|
+
def serializer
|
|
57
|
+
return @serializer if instance_variable_defined?(:@serializer)
|
|
136
58
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|| prepare_const_block \
|
|
140
|
-
|| prepare_delegate_block \
|
|
141
|
-
|| prepare_batch_loader_block \
|
|
142
|
-
|| prepare_keyword_block
|
|
143
|
-
end
|
|
59
|
+
@serializer = prepare_serializer
|
|
60
|
+
end
|
|
144
61
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
62
|
+
# Shows the default attribute value. It is a value that replaces found nils.
|
|
63
|
+
#
|
|
64
|
+
# When custom :default is not specified, we set empty array as default when `many: true` specified
|
|
65
|
+
def default
|
|
66
|
+
return @default if instance_variable_defined?(:@default)
|
|
149
67
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return true if hide_setting == :auto && preload_or_batch?
|
|
68
|
+
@default = prepare_default
|
|
69
|
+
end
|
|
153
70
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
end
|
|
71
|
+
# Preloads declared for this attribute, passed through as provided.
|
|
72
|
+
def preloads
|
|
73
|
+
return @preloads if instance_variable_defined?(:@preloads)
|
|
158
74
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
#
|
|
162
|
-
# This is intentionally narrower than a "goes through the batch mechanism"
|
|
163
|
-
# check (`SeregaPlanPoint#batch?`): a plain relation (`serializer:` without
|
|
164
|
-
# `:preload`) is batch-processed too, but it only serializes an already-loaded
|
|
165
|
-
# nested object, so it stays visible by default.
|
|
166
|
-
def preload_or_batch?
|
|
167
|
-
!!(preloads || init_opts.key?(:batch))
|
|
168
|
-
end
|
|
75
|
+
@preloads = prepare_preloads
|
|
76
|
+
end
|
|
169
77
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
78
|
+
# Shows specified batch loaders names
|
|
79
|
+
def batch_loaders
|
|
80
|
+
@batch_loaders ||= prepare_batch_loaders
|
|
81
|
+
end
|
|
173
82
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
83
|
+
# Batch loader given directly as the :batch option. It is named after the
|
|
84
|
+
# attribute and belongs to the attribute only.
|
|
85
|
+
def inline_batch_loader
|
|
86
|
+
batch = batch_option
|
|
87
|
+
return unless batch&.inline?
|
|
177
88
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return init_opts[:serializer] unless block
|
|
89
|
+
SeregaBatchLoader.new(name: name, block: batch.use)
|
|
90
|
+
end
|
|
181
91
|
|
|
182
|
-
|
|
183
|
-
|
|
92
|
+
# Callable :if, :unless, :if_value and :unless_value conditions. A Symbol
|
|
93
|
+
# condition calls the method of this name.
|
|
94
|
+
def conditions
|
|
95
|
+
conditions = init_opts.slice(:if, :unless, :if_value, :unless_value)
|
|
96
|
+
return if conditions.empty?
|
|
184
97
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# The serializer is a regular subclass of an explicitly chosen base —
|
|
188
|
-
# the :base_serializer attribute option or `config.base_serializer` —
|
|
189
|
-
# so it inherits everything the base has: plugins, config, attributes,
|
|
190
|
-
# batch loaders, the preload handler. Nested values can be any objects,
|
|
191
|
-
# which is why the base is chosen explicitly instead of inheriting from
|
|
192
|
-
# the current serializer. Its `inspect` is overridden to
|
|
193
|
-
# `CurrentSerializer.<attribute_name>` so error messages and debugging
|
|
194
|
-
# output point back to the defining attribute.
|
|
195
|
-
# Guards against cyclic definitions before building: inheriting the base
|
|
196
|
-
# serializer copies its attributes, and copying a block attribute builds
|
|
197
|
-
# its nested serializer — meeting the same block again while it is being
|
|
198
|
-
# built means the base serializer (transitively) contains the block
|
|
199
|
-
# attribute being built, so inheriting from it would recurse forever.
|
|
200
|
-
# The "in progress" mark is kept on the block itself — its identity is
|
|
201
|
-
# what defines the cycle (the same Proc object is shared by all copies
|
|
202
|
-
# of the attribute).
|
|
203
|
-
def prepare_block_serializer(block)
|
|
204
|
-
if block.instance_variable_defined?(:@serega_building_nested_serializer)
|
|
205
|
-
raise SeregaError,
|
|
206
|
-
"Can not define a nested serializer for attribute :#{name} —" \
|
|
207
|
-
" its base serializer #{block_base_serializer.inspect} (transitively)" \
|
|
208
|
-
" contains this same block attribute (cyclic definition)"
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
block.instance_variable_set(:@serega_building_nested_serializer, true)
|
|
212
|
-
begin
|
|
213
|
-
build_block_serializer(block)
|
|
214
|
-
ensure
|
|
215
|
-
block.remove_instance_variable(:@serega_building_nested_serializer)
|
|
216
|
-
end
|
|
98
|
+
conditions.transform_values do |condition|
|
|
99
|
+
condition.is_a?(Symbol) ? AttributeValueResolvers::Keyword.new(condition) : condition
|
|
217
100
|
end
|
|
101
|
+
end
|
|
218
102
|
|
|
219
|
-
|
|
220
|
-
|
|
103
|
+
# Formatter of the :format option. A Symbol option is the name of a
|
|
104
|
+
# formatter defined with `Serega.formatter`.
|
|
105
|
+
def formatter
|
|
106
|
+
format = init_opts[:format]
|
|
107
|
+
return unless format
|
|
221
108
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
define_singleton_method(:to_s) { label }
|
|
225
|
-
instance_exec(&block)
|
|
226
|
-
end
|
|
109
|
+
format.is_a?(Symbol) ? @serializer_class.formatters.fetch(format) : SeregaFormatter.new(format)
|
|
110
|
+
end
|
|
227
111
|
|
|
228
|
-
|
|
229
|
-
# old-style value block — raise the explaining error.
|
|
230
|
-
raise SeregaError, SeregaValidations::Attribute::CheckBlock::ERROR_MESSAGE if serializer.attributes.empty?
|
|
112
|
+
private
|
|
231
113
|
|
|
232
|
-
|
|
233
|
-
|
|
114
|
+
def prepare_name
|
|
115
|
+
init_name.to_sym
|
|
116
|
+
end
|
|
234
117
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
raise SeregaError,
|
|
243
|
-
"Attribute block requires a base serializer for the nested serializer." \
|
|
244
|
-
" Provide the `base_serializer: <SerializerClass>` attribute option" \
|
|
245
|
-
" or set `config.base_serializer = <SerializerClass>`"
|
|
246
|
-
end
|
|
118
|
+
def prepare_value_block
|
|
119
|
+
init_opts[:value] \
|
|
120
|
+
|| prepare_const_block \
|
|
121
|
+
|| prepare_delegate_block \
|
|
122
|
+
|| prepare_batch_loader_block \
|
|
123
|
+
|| prepare_keyword_block
|
|
124
|
+
end
|
|
247
125
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
126
|
+
def prepare_hide
|
|
127
|
+
# Return provided directly value
|
|
128
|
+
hide = init_opts[:hide]
|
|
129
|
+
return hide if (hide == true) || (hide == false)
|
|
251
130
|
|
|
252
|
-
|
|
253
|
-
|
|
131
|
+
hide_setting = config.hide_by_default
|
|
132
|
+
return true if hide_setting == true
|
|
133
|
+
return true if hide_setting == :auto && preload_or_batch?
|
|
254
134
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
mode, allow_nil = hash_access
|
|
260
|
-
return AttributeValueResolvers::KeywordResolver.get(method_name) unless mode
|
|
135
|
+
# Return nil for undefined value which means "not hide" but allows
|
|
136
|
+
# to change this value by plugins
|
|
137
|
+
nil
|
|
138
|
+
end
|
|
261
139
|
|
|
262
|
-
|
|
263
|
-
|
|
140
|
+
# The `hide_by_default = :auto` criterion: hide attributes that fetch extra
|
|
141
|
+
# data on demand — those declared with `:preload` or an explicit `:batch`.
|
|
142
|
+
#
|
|
143
|
+
# This is intentionally narrower than a "goes through the batch mechanism"
|
|
144
|
+
# check (`SeregaPlanPoint#batch?`): a plain relation (`serializer:` without
|
|
145
|
+
# `:preload`) is batch-processed too, but it only serializes an already-loaded
|
|
146
|
+
# nested object, so it stays visible by default.
|
|
147
|
+
def preload_or_batch?
|
|
148
|
+
!!(preloads || init_opts.key?(:batch))
|
|
149
|
+
end
|
|
264
150
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
151
|
+
def config
|
|
152
|
+
@serializer_class.config
|
|
153
|
+
end
|
|
268
154
|
|
|
269
|
-
|
|
270
|
-
|
|
155
|
+
def prepare_many
|
|
156
|
+
init_opts[:many]
|
|
157
|
+
end
|
|
271
158
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def prepare_batch_loaders
|
|
276
|
-
batch_opt = init_opts[:batch]
|
|
277
|
-
return explicit_batch_loaders(batch_opt) if batch_opt
|
|
159
|
+
def prepare_serializer
|
|
160
|
+
block = init_block
|
|
161
|
+
return init_opts[:serializer] unless block
|
|
278
162
|
|
|
279
|
-
|
|
280
|
-
|
|
163
|
+
AttributeNormalizers::Block.call(@serializer_class, name, block, init_opts[:base_serializer])
|
|
164
|
+
end
|
|
281
165
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
elsif batch_opt.is_a?(Symbol)
|
|
286
|
-
[batch_opt]
|
|
287
|
-
elsif batch_opt.is_a?(String)
|
|
288
|
-
[batch_opt.to_sym]
|
|
289
|
-
else
|
|
290
|
-
use_opt = batch_opt.fetch(:use)
|
|
291
|
-
use_opt.respond_to?(:call) ? [name] : Array(use_opt).map(&:to_sym)
|
|
292
|
-
end
|
|
293
|
-
end
|
|
166
|
+
def prepare_method_name
|
|
167
|
+
(init_opts[:method] || init_name).to_sym
|
|
168
|
+
end
|
|
294
169
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
end
|
|
170
|
+
def prepare_const_block
|
|
171
|
+
return unless init_opts.key?(:const)
|
|
298
172
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
# final (:method) read. A step without its sub-option keeps a plain
|
|
302
|
-
# method read.
|
|
303
|
-
def prepare_delegate_block
|
|
304
|
-
delegate = init_opts[:delegate]
|
|
305
|
-
return unless delegate
|
|
306
|
-
|
|
307
|
-
key_method_name = delegate[:method] || method_name
|
|
308
|
-
delegate_to = delegate[:to]
|
|
309
|
-
delegate_allow_nil = delegate.fetch(:allow_nil) { config.delegate_default_allow_nil }
|
|
310
|
-
|
|
311
|
-
to_access = delegate[:to_hash_access]
|
|
312
|
-
final_access = delegate[:hash_access]
|
|
313
|
-
unless to_access || final_access
|
|
314
|
-
return AttributeValueResolvers::DelegateResolver.get(delegate_to, key_method_name, delegate_allow_nil)
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
to_step = delegate_to_step(delegate_to, to_access)
|
|
318
|
-
final_step = delegate_final_step(key_method_name, final_access)
|
|
319
|
-
AttributeValueResolvers::HashAccessDelegateResolver.get(to_step, final_step, delegate_allow_nil)
|
|
320
|
-
end
|
|
173
|
+
AttributeValueResolvers::Const.new(init_opts[:const])
|
|
174
|
+
end
|
|
321
175
|
|
|
322
|
-
|
|
323
|
-
|
|
176
|
+
def prepare_keyword_block
|
|
177
|
+
AttributeNormalizers::HashAccess.call(init_opts[:hash_access], method_name, config.hash_access)
|
|
178
|
+
end
|
|
324
179
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
180
|
+
# Reads the value from the loaded batch of the only batch loader
|
|
181
|
+
def prepare_batch_loader_block
|
|
182
|
+
batch = batch_option
|
|
183
|
+
return unless batch
|
|
328
184
|
|
|
329
|
-
|
|
330
|
-
|
|
185
|
+
AttributeValueResolvers::Batch.new(batch_loaders.first, batch.id || config.batch_id_option)
|
|
186
|
+
end
|
|
331
187
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
188
|
+
# Batch loader names whose loaded data this attribute's value needs. An
|
|
189
|
+
# attribute without the :batch option has none.
|
|
190
|
+
def prepare_batch_loaders
|
|
191
|
+
batch = batch_option
|
|
192
|
+
return FROZEN_EMPTY_ARRAY unless batch
|
|
335
193
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
# or nil when hash access is not enabled for this attribute
|
|
339
|
-
def hash_access
|
|
340
|
-
option = init_opts[:hash_access]
|
|
341
|
-
return unless option
|
|
194
|
+
batch.loader_names
|
|
195
|
+
end
|
|
342
196
|
|
|
343
|
-
|
|
344
|
-
|
|
197
|
+
# The parsed :batch option, `nil` without it
|
|
198
|
+
def batch_option
|
|
199
|
+
return @batch_option if instance_variable_defined?(:@batch_option)
|
|
345
200
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
# `config.hash_access.default_mode`.
|
|
350
|
-
def parse_hash_access(option)
|
|
351
|
-
defaults = config.hash_access
|
|
352
|
-
|
|
353
|
-
case option
|
|
354
|
-
when true then [defaults.default_mode, defaults.default_allow_missing_key]
|
|
355
|
-
when Symbol then [option, defaults.default_allow_missing_key]
|
|
356
|
-
else
|
|
357
|
-
mode = option.fetch(:mode) { defaults.default_mode }
|
|
358
|
-
allow_missing_key = option.fetch(:allow_missing_key) { defaults.default_allow_missing_key }
|
|
359
|
-
[mode, allow_missing_key]
|
|
360
|
-
end
|
|
361
|
-
end
|
|
201
|
+
batch = init_opts[:batch]
|
|
202
|
+
@batch_option = batch && AttributeNormalizers::Batch.new(batch, name)
|
|
203
|
+
end
|
|
362
204
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
# custom value an ORM understands) — it is the data handed to the
|
|
367
|
-
# serializer's `preload_with` handler.
|
|
368
|
-
#
|
|
369
|
-
# @return [Object, nil] preloads as provided, or nil when the attribute has none
|
|
370
|
-
def prepare_preloads
|
|
371
|
-
# Explicit preload option (false or nil disables preloading)
|
|
372
|
-
return init_opts[:preload] || nil if init_opts.key?(:preload)
|
|
373
|
-
|
|
374
|
-
# Auto-preload the delegated association
|
|
375
|
-
if config.auto_preload.fetch(:has_delegate_option) && init_opts[:delegate]
|
|
376
|
-
return auto_preload_value(init_opts[:delegate][:to])
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
# Auto-preload the nested serializer's association
|
|
380
|
-
# (a block defines a nested serializer, same as the :serializer option)
|
|
381
|
-
if config.auto_preload.fetch(:has_serializer_option) && (init_opts[:serializer] || init_block) && !init_opts.key?(:batch)
|
|
382
|
-
return auto_preload_value(method_name)
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
nil
|
|
386
|
-
end
|
|
205
|
+
def prepare_default
|
|
206
|
+
init_opts.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
|
|
207
|
+
end
|
|
387
208
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
def auto_preload_value(preload_method)
|
|
392
|
-
return nil if config.auto_preload_excluded_methods.include?(preload_method.to_sym)
|
|
209
|
+
def prepare_delegate_block
|
|
210
|
+
delegate = init_opts[:delegate]
|
|
211
|
+
return unless delegate
|
|
393
212
|
|
|
394
|
-
|
|
395
|
-
end
|
|
213
|
+
AttributeNormalizers::Delegate.call(delegate, method_name, config)
|
|
396
214
|
end
|
|
397
215
|
|
|
398
|
-
|
|
399
|
-
|
|
216
|
+
def prepare_preloads
|
|
217
|
+
AttributeNormalizers::Preloads.call(init_opts, init_block, method_name, config)
|
|
218
|
+
end
|
|
400
219
|
end
|
|
401
220
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Serega
|
|
4
|
+
#
|
|
5
|
+
# Parsers of attribute options, used by SeregaAttributeNormalizer
|
|
6
|
+
#
|
|
7
|
+
module AttributeNormalizers # :nodoc:
|
|
8
|
+
#
|
|
9
|
+
# The attribute :batch option, parsed into the loaders and the id method.
|
|
10
|
+
# `true`, a loader name, a callable or a Hash with `:use` and `:id`; `true`
|
|
11
|
+
# and a Hash without `:use` use the loader named after the attribute.
|
|
12
|
+
#
|
|
13
|
+
class Batch # :nodoc:
|
|
14
|
+
# Name of the attribute with the option
|
|
15
|
+
attr_reader :attribute_name
|
|
16
|
+
|
|
17
|
+
# Loader name, Array of loader names, or a callable inline loader
|
|
18
|
+
attr_reader :use
|
|
19
|
+
|
|
20
|
+
# Method that gives the id of an object, `nil` when not given
|
|
21
|
+
attr_reader :id
|
|
22
|
+
|
|
23
|
+
# Parses the :batch option of the attribute
|
|
24
|
+
def initialize(batch, attribute_name)
|
|
25
|
+
batch = {use: batch} unless batch.is_a?(Hash)
|
|
26
|
+
use = batch.fetch(:use, true)
|
|
27
|
+
|
|
28
|
+
@attribute_name = attribute_name
|
|
29
|
+
@use = (use == true) ? attribute_name : use
|
|
30
|
+
@id = batch[:id]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Whether the option is the loader itself, a callable
|
|
34
|
+
def inline?
|
|
35
|
+
use.respond_to?(:call)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Names of the batch loaders the attribute value needs. An inline loader
|
|
39
|
+
# is named after the attribute.
|
|
40
|
+
def loader_names
|
|
41
|
+
inline? ? [attribute_name.to_sym] : Array(use).map(&:to_sym)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|