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
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.50.0
|
data/lib/serega/attribute.rb
CHANGED
|
@@ -4,148 +4,203 @@ class Serega
|
|
|
4
4
|
#
|
|
5
5
|
# Stores serialized attribute data
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
class SeregaAttribute # :nodoc:
|
|
8
|
+
# Serializer of the attribute
|
|
9
|
+
attr_reader :serializer_class
|
|
10
|
+
|
|
11
|
+
# Attribute initial params
|
|
12
|
+
attr_reader :initials
|
|
13
|
+
|
|
14
|
+
# Attribute name
|
|
15
|
+
attr_reader :name
|
|
16
|
+
|
|
17
|
+
# Where the attribute is defined
|
|
18
|
+
def location
|
|
19
|
+
initials[:location]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Attribute :many option
|
|
23
|
+
attr_reader :many
|
|
24
|
+
|
|
25
|
+
# Attribute :default option
|
|
26
|
+
attr_reader :default
|
|
27
|
+
|
|
28
|
+
# Attribute :hide option
|
|
29
|
+
attr_reader :hide
|
|
30
|
+
|
|
31
|
+
# Attribute :preloads option
|
|
32
|
+
attr_reader :preloads
|
|
33
|
+
|
|
34
|
+
# Batch loader names required to detect attribute value
|
|
35
|
+
attr_reader :batch_loaders
|
|
36
|
+
|
|
37
|
+
# Batch loader given directly as the :batch option
|
|
38
|
+
attr_reader :inline_batch_loader
|
|
39
|
+
|
|
40
|
+
# Attribute :if, :unless, :if_value and :unless_value conditions
|
|
41
|
+
attr_reader :conditions
|
|
42
|
+
|
|
43
|
+
# Initializes new attribute
|
|
44
|
+
def initialize(serializer_class, name:, opts: {}, block: nil, location: nil)
|
|
45
|
+
SeregaValidations::CheckAttributeParams.new(serializer_class, name, opts, block).validate
|
|
46
|
+
|
|
47
|
+
@serializer_class = serializer_class
|
|
48
|
+
@initials = SeregaUtils::EnumDeepFreeze.call(
|
|
49
|
+
name: name,
|
|
50
|
+
opts: SeregaUtils::EnumDeepDup.call(opts),
|
|
51
|
+
block: block,
|
|
52
|
+
location: location
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
normalizer = SeregaAttributeNormalizer.new(serializer_class, initials)
|
|
56
|
+
set_normalized_vars(normalizer)
|
|
57
|
+
@value_method = define_value_method(serializer_class)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Method of the serialized object that gives the value, `nil` for a value
|
|
61
|
+
# from :value, :const, :delegate, :hash_access or :batch
|
|
62
|
+
def object_method
|
|
63
|
+
@value_block.keyword if @value_block.is_a?(AttributeValueResolvers::Keyword)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Name of the SeregaAttributeValues method that reads the value
|
|
67
|
+
attr_reader :value_method
|
|
68
|
+
|
|
69
|
+
# Whether the attribute has an :if, :unless, :if_value or :unless_value condition
|
|
70
|
+
def conditional?
|
|
71
|
+
!@conditions.nil?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Shows whether attribute has specified serializer
|
|
75
|
+
def relation?
|
|
76
|
+
!@serializer.nil?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Shows specified serializer class. A frozen attribute resolves its
|
|
80
|
+
# String or Proc serializer on each call.
|
|
81
|
+
def serializer
|
|
82
|
+
serializer = @serializer
|
|
83
|
+
return serializer if (serializer.is_a?(Class) && (serializer < Serega)) || !serializer
|
|
84
|
+
|
|
85
|
+
serializer = serializer.is_a?(String) ? Object.const_get(serializer, false) : serializer.call
|
|
86
|
+
frozen? ? serializer : (@serializer = serializer)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Finds attribute value
|
|
11
90
|
#
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# Attribute :hide option
|
|
31
|
-
# @return [Boolean, nil] Attribute :hide option
|
|
32
|
-
attr_reader :hide
|
|
33
|
-
|
|
34
|
-
# Attribute :preloads option
|
|
35
|
-
# @return [Hash, nil] Attribute :preloads option
|
|
36
|
-
attr_reader :preloads
|
|
37
|
-
|
|
38
|
-
# Batch loader names required to detect attribute value
|
|
39
|
-
# @return [Array<Symbol>] Batch loader names
|
|
40
|
-
attr_reader :batch_loaders
|
|
41
|
-
|
|
42
|
-
#
|
|
43
|
-
# Initializes new attribute
|
|
44
|
-
#
|
|
45
|
-
# @param name [Symbol, String] Name of attribute
|
|
46
|
-
# @param opts [Hash] Attribute options
|
|
47
|
-
# @option opts [Symbol] :method Object method name to fetch attribute value
|
|
48
|
-
# @option opts [Hash] :delegate Allows to fetch value from nested object
|
|
49
|
-
# @option opts [Boolean] :hide Specify `true` to not serialize this attribute by default
|
|
50
|
-
# @option opts [Boolean] :many Specifies has_many relationship. By default is detected via object.is_a?(Enumerable) && !object.is_a?(Hash) && !object.is_a?(Struct)
|
|
51
|
-
# @option opts [Proc, #call] :value Custom block or callable to find attribute value
|
|
52
|
-
# @option opts [Serega, Proc] :serializer Relationship serializer class. Use `proc { MySerializer }` if serializers have cross references
|
|
53
|
-
# @param block [Proc] Defines attributes of a nested anonymous serializer
|
|
54
|
-
#
|
|
55
|
-
def initialize(name:, opts: {}, block: nil)
|
|
56
|
-
serializer_class = self.class.serializer_class
|
|
57
|
-
serializer_class::CheckAttributeParams.new(name, opts, block).validate
|
|
58
|
-
|
|
59
|
-
@initials = SeregaUtils::EnumDeepFreeze.call(
|
|
60
|
-
name: name,
|
|
61
|
-
opts: SeregaUtils::EnumDeepDup.call(opts),
|
|
62
|
-
block: block
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
normalizer = serializer_class::SeregaAttributeNormalizer.new(initials)
|
|
66
|
-
set_normalized_vars(normalizer)
|
|
67
|
-
end
|
|
91
|
+
# Generated code reads plain attributes without this method, with
|
|
92
|
+
# their #value_code.
|
|
93
|
+
def value(object, context, batches: nil)
|
|
94
|
+
# Signatures should match allowed signatures in CheckOptValue
|
|
95
|
+
result =
|
|
96
|
+
case @value_block_signature
|
|
97
|
+
when "1" then @value_block.call(object)
|
|
98
|
+
when "1_ctx" then @value_block.call(object, ctx: context)
|
|
99
|
+
when "1_batches" then @value_block.call(object, batches: batches)
|
|
100
|
+
when "1_batches_ctx" then @value_block.call(object, ctx: context, batches: batches)
|
|
101
|
+
when "2" then @value_block.call(object, context)
|
|
102
|
+
when "2_batches_ctx" then @value_block.call(object, context, ctx: context, batches: batches)
|
|
103
|
+
else @value_block.call # signature is "0" - no parameters
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
result = @default if result.nil?
|
|
107
|
+
@formatter ? @formatter.call(result, context) : result
|
|
108
|
+
end
|
|
68
109
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
110
|
+
# Ruby code that reads the attribute value of the source. `:const` and
|
|
111
|
+
# `:default` values and formatters come from the `CONSTANTS`,
|
|
112
|
+
# `DEFAULTS`, `FORMATTERS` and `FORMATTER_ARGS` of SeregaAttributeValues.
|
|
113
|
+
def value_code(source_variable, constants_prefix = "")
|
|
114
|
+
code =
|
|
115
|
+
case @value_block
|
|
116
|
+
when AttributeValueResolvers::Keyword,
|
|
117
|
+
AttributeValueResolvers::Delegate,
|
|
118
|
+
AttributeValueResolvers::DelegateAllowNil,
|
|
119
|
+
AttributeValueResolvers::HashAccessKeyword,
|
|
120
|
+
AttributeValueResolvers::HashAccessDelegate,
|
|
121
|
+
AttributeValueResolvers::HashAccessDelegateAllowNil
|
|
122
|
+
@value_block.code(source_variable)
|
|
123
|
+
when AttributeValueResolvers::Const
|
|
124
|
+
"#{constants_prefix}CONSTANTS[#{name.inspect}]"
|
|
125
|
+
end
|
|
126
|
+
return unless code
|
|
127
|
+
|
|
128
|
+
code = "(value = #{code}).nil? ? #{constants_prefix}DEFAULTS[#{name.inspect}] : value" unless @default.nil?
|
|
129
|
+
return code unless @formatter
|
|
130
|
+
|
|
131
|
+
@formatter.code(code, "#{constants_prefix}FORMATTERS[#{name.inspect}]", "#{constants_prefix}FORMATTER_ARGS[#{name.inspect}]")
|
|
132
|
+
end
|
|
74
133
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
134
|
+
# Checks if attribute must be added to serialized response
|
|
135
|
+
def visible?(only, except, with, only_limits)
|
|
136
|
+
return false if except.member?(name) && except[name].empty?
|
|
137
|
+
return true if only.member?(name) || with.member?(name)
|
|
138
|
+
return false if only_limits
|
|
80
139
|
|
|
81
|
-
|
|
82
|
-
|
|
140
|
+
!hide
|
|
141
|
+
end
|
|
83
142
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# @return [Object] Serialized attribute value
|
|
91
|
-
#
|
|
92
|
-
def value(object, context, batches: nil)
|
|
93
|
-
# Signatures should match allowed signatures in CheckOptValue
|
|
94
|
-
result =
|
|
95
|
-
case value_block_signature
|
|
96
|
-
when "1" then value_block.call(object)
|
|
97
|
-
when "1_ctx" then value_block.call(object, ctx: context)
|
|
98
|
-
when "1_batches" then value_block.call(object, batches: batches)
|
|
99
|
-
when "1_batches_ctx" then value_block.call(object, ctx: context, batches: batches)
|
|
100
|
-
when "2" then value_block.call(object, context)
|
|
101
|
-
when "2_batches_ctx" then value_block.call(object, context, ctx: context, batches: batches)
|
|
102
|
-
else value_block.call # signature is "0" - no parameters
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
result.nil? ? default : result
|
|
106
|
-
end
|
|
143
|
+
# Defines the methods of the code on the owner, at the file and line of
|
|
144
|
+
# the attribute, thus their backtraces point to the attribute
|
|
145
|
+
def define_methods(owner, code)
|
|
146
|
+
file, _, line = location ? location.rpartition(":") : ["(attribute #{name})", nil, "1"]
|
|
147
|
+
owner.class_eval(code, file, line.to_i)
|
|
148
|
+
end
|
|
107
149
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# @return [Boolean]
|
|
117
|
-
#
|
|
118
|
-
def visible?(modifiers)
|
|
119
|
-
except = modifiers[:except] || FROZEN_EMPTY_HASH
|
|
120
|
-
only = modifiers[:only] || FROZEN_EMPTY_HASH
|
|
121
|
-
with = modifiers[:with] || FROZEN_EMPTY_HASH
|
|
122
|
-
|
|
123
|
-
return false if except.member?(name) && except[name].empty?
|
|
124
|
-
return true if only.member?(name)
|
|
125
|
-
return true if with.member?(name)
|
|
126
|
-
return false unless only.empty?
|
|
127
|
-
|
|
128
|
-
!hide
|
|
129
|
-
end
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
# Defines the SeregaAttributeValues method that reads the value of a
|
|
153
|
+
# source directly, for example `read_full_name`. The method has the
|
|
154
|
+
# file and line of the attribute.
|
|
155
|
+
def define_value_method(serializer_class)
|
|
156
|
+
code = value_code("source")
|
|
157
|
+
return unless code
|
|
130
158
|
|
|
131
|
-
|
|
159
|
+
values_class = serializer_class::SeregaAttributeValues
|
|
160
|
+
values_class::CONSTANTS[name] = @value_block.call if @value_block.is_a?(AttributeValueResolvers::Const)
|
|
161
|
+
values_class::DEFAULTS[name] = @default unless @default.nil?
|
|
162
|
+
values_class::FORMATTERS[name] = @formatter.callable if @formatter&.callable
|
|
163
|
+
values_class::FORMATTER_ARGS[name] = @formatter.args if @formatter&.args
|
|
132
164
|
|
|
133
|
-
|
|
165
|
+
method_name = value_method_name(values_class)
|
|
166
|
+
define_methods(values_class, "def #{method_name}(source) = #{code}")
|
|
167
|
+
method_name
|
|
168
|
+
end
|
|
134
169
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
170
|
+
# Plain method name of the attribute, for example `read_first_name` for
|
|
171
|
+
# `first-name`. The name keeps a final `?` or `!`. A name taken by
|
|
172
|
+
# another attribute gets a number, for example `read_first_name_2`. The
|
|
173
|
+
# same attribute keeps its name.
|
|
174
|
+
def value_method_name(values_class)
|
|
175
|
+
method_names = values_class::METHOD_NAMES
|
|
176
|
+
method_name = method_names[name]
|
|
177
|
+
return method_name if method_name
|
|
178
|
+
|
|
179
|
+
base, mark = name.to_s.match(/\A(.*?)([?!]?)\z/).captures
|
|
180
|
+
base = "read_#{base.gsub(/\W/, "_")}"
|
|
181
|
+
method_name = :"#{base}#{mark}"
|
|
182
|
+
number = 1
|
|
183
|
+
while method_names.value?(method_name)
|
|
184
|
+
number += 1
|
|
185
|
+
method_name = :"#{base}_#{number}#{mark}"
|
|
145
186
|
end
|
|
187
|
+
method_names[name] = method_name
|
|
146
188
|
end
|
|
147
189
|
|
|
148
|
-
|
|
149
|
-
|
|
190
|
+
def set_normalized_vars(normalizer)
|
|
191
|
+
@name = normalizer.name
|
|
192
|
+
@many = normalizer.many
|
|
193
|
+
@default = normalizer.default
|
|
194
|
+
@value_block = normalizer.value_block
|
|
195
|
+
@value_block_signature = normalizer.value_block_signature
|
|
196
|
+
@hide = normalizer.hide
|
|
197
|
+
@serializer = normalizer.serializer
|
|
198
|
+
@preloads = normalizer.preloads
|
|
199
|
+
@batch_loaders = normalizer.batch_loaders
|
|
200
|
+
@inline_batch_loader = normalizer.inline_batch_loader
|
|
201
|
+
@formatter = normalizer.formatter
|
|
202
|
+
conditions = normalizer.conditions
|
|
203
|
+
@conditions = conditions && SeregaConditions.new(self, conditions)
|
|
204
|
+
end
|
|
150
205
|
end
|
|
151
206
|
end
|