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.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +124 -1207
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +186 -131
  5. data/lib/serega/attribute_normalizer.rb +164 -345
  6. data/lib/serega/attribute_normalizers/batch.rb +45 -0
  7. data/lib/serega/attribute_normalizers/block.rb +81 -0
  8. data/lib/serega/attribute_normalizers/delegate.rb +34 -0
  9. data/lib/serega/attribute_normalizers/hash_access.rb +37 -0
  10. data/lib/serega/attribute_normalizers/preloads.rb +46 -0
  11. data/lib/serega/attribute_value_resolvers/batch.rb +5 -54
  12. data/lib/serega/attribute_value_resolvers/const.rb +2 -24
  13. data/lib/serega/attribute_value_resolvers/delegate.rb +17 -28
  14. data/lib/serega/attribute_value_resolvers/hash_access.rb +34 -52
  15. data/lib/serega/attribute_value_resolvers/keyword.rb +10 -25
  16. data/lib/serega/attribute_values.rb +46 -0
  17. data/lib/serega/batch_loader.rb +37 -0
  18. data/lib/serega/conditions.rb +56 -0
  19. data/lib/serega/config.rb +105 -144
  20. data/lib/serega/engine/pulls.rb +64 -0
  21. data/lib/serega/engine/run.rb +106 -0
  22. data/lib/serega/errors.rb +36 -8
  23. data/lib/serega/formatter.rb +98 -0
  24. data/lib/serega/plan/cache.rb +96 -0
  25. data/lib/serega/plan/per_ractor_cache.rb +23 -0
  26. data/lib/serega/plan.rb +101 -128
  27. data/lib/serega/plan_point.rb +44 -151
  28. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +13 -80
  29. data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +8 -75
  30. data/lib/serega/plugins/context_metadata/context_metadata.rb +40 -123
  31. data/lib/serega/plugins/metadata/check_params.rb +80 -0
  32. data/lib/serega/plugins/metadata/meta_attribute.rb +40 -79
  33. data/lib/serega/plugins/metadata/metadata.rb +40 -168
  34. data/lib/serega/plugins/root/root.rb +91 -235
  35. data/lib/serega/plugins.rb +16 -49
  36. data/lib/serega/presenter.rb +61 -19
  37. data/lib/serega/result_builder.rb +68 -0
  38. data/lib/serega/result_code.rb +203 -0
  39. data/lib/serega/shareable.rb +52 -0
  40. data/lib/serega/source_group.rb +187 -0
  41. data/lib/serega/utils/collection_detector.rb +19 -13
  42. data/lib/serega/utils/deep_merge.rb +26 -0
  43. data/lib/serega/utils/enum_deep_dup.rb +10 -16
  44. data/lib/serega/utils/enum_deep_freeze.rb +8 -14
  45. data/lib/serega/utils/method_signature.rb +6 -15
  46. data/lib/serega/utils/parse_string_modifiers.rb +63 -0
  47. data/lib/serega/utils/ractor_local.rb +36 -0
  48. data/lib/serega/utils/serialized_attribute_error.rb +60 -20
  49. data/lib/serega/utils/to_hash.rb +5 -26
  50. data/lib/serega/validations/attribute/check_block.rb +3 -15
  51. data/lib/serega/validations/attribute/check_name.rb +4 -15
  52. data/lib/serega/validations/attribute/check_opt_base_serializer.rb +3 -15
  53. data/lib/serega/validations/attribute/check_opt_batch.rb +26 -56
  54. data/lib/serega/validations/attribute/check_opt_condition.rb +55 -0
  55. data/lib/serega/validations/attribute/check_opt_delegate.rb +10 -63
  56. data/lib/serega/validations/attribute/check_opt_format.rb +36 -0
  57. data/lib/serega/validations/attribute/check_opt_hash_access.rb +11 -59
  58. data/lib/serega/validations/attribute/check_opt_hide.rb +3 -14
  59. data/lib/serega/validations/attribute/check_opt_many.rb +18 -20
  60. data/lib/serega/validations/attribute/check_opt_method.rb +3 -25
  61. data/lib/serega/validations/attribute/check_opt_preload.rb +3 -26
  62. data/lib/serega/validations/attribute/check_opt_serializer.rb +4 -18
  63. data/lib/serega/validations/attribute/check_opt_value.rb +3 -21
  64. data/lib/serega/validations/check_attribute_params.rb +92 -71
  65. data/lib/serega/validations/check_batch_loader_params.rb +43 -62
  66. data/lib/serega/validations/check_formatter.rb +80 -0
  67. data/lib/serega/validations/check_serialize_keywords.rb +23 -0
  68. data/lib/serega/validations/initiate/check_modifiers.rb +14 -20
  69. data/lib/serega/validations/utils/check_allowed_keys.rb +3 -13
  70. data/lib/serega/validations/utils/check_hash_access_value.rb +44 -0
  71. data/lib/serega/validations/utils/check_opt_is_bool.rb +3 -13
  72. data/lib/serega/validations/utils/check_opt_is_hash.rb +3 -13
  73. data/lib/serega/validations/utils/check_opt_is_string_or_symbol.rb +3 -13
  74. data/lib/serega/version.rb +1 -4
  75. data/lib/serega.rb +459 -388
  76. data/sig/serega/plugins.rbs +49 -0
  77. data/sig/serega.rbs +142 -0
  78. metadata +42 -38
  79. data/lib/serega/data_builder.rb +0 -54
  80. data/lib/serega/engine/level.rb +0 -59
  81. data/lib/serega/engine/level_queue.rb +0 -56
  82. data/lib/serega/engine/loader.rb +0 -70
  83. data/lib/serega/helpers/serializer_class_helper.rb +0 -19
  84. data/lib/serega/object_serializer.rb +0 -125
  85. data/lib/serega/plugins/camel_case/camel_case.rb +0 -213
  86. data/lib/serega/plugins/depth_limit/depth_limit.rb +0 -206
  87. data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +0 -69
  88. data/lib/serega/plugins/explicit_many_option/validations/check_opt_many.rb +0 -38
  89. data/lib/serega/plugins/formatters/formatters.rb +0 -322
  90. data/lib/serega/plugins/if/if.rb +0 -287
  91. data/lib/serega/plugins/if/validations/check_opt_if.rb +0 -72
  92. data/lib/serega/plugins/if/validations/check_opt_if_value.rb +0 -77
  93. data/lib/serega/plugins/if/validations/check_opt_unless.rb +0 -72
  94. data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +0 -77
  95. data/lib/serega/plugins/metadata/validations/check_block.rb +0 -47
  96. data/lib/serega/plugins/metadata/validations/check_opt_const.rb +0 -39
  97. data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +0 -35
  98. data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +0 -35
  99. data/lib/serega/plugins/metadata/validations/check_opt_value.rb +0 -61
  100. data/lib/serega/plugins/metadata/validations/check_opts.rb +0 -59
  101. data/lib/serega/plugins/metadata/validations/check_path.rb +0 -50
  102. data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +0 -111
  103. data/lib/serega/plugins/string_modifiers/string_modifiers.rb +0 -63
  104. data/lib/serega/utils/symbol_name.rb +0 -39
  105. data/lib/serega/validations/attribute/check_opt_const.rb +0 -40
  106. data/lib/serega/validations/check_initiate_params.rb +0 -57
  107. data/lib/serega/validations/check_serialize_params.rb +0 -54
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.42.0
1
+ 0.50.0
@@ -4,148 +4,203 @@ class Serega
4
4
  #
5
5
  # Stores serialized attribute data
6
6
  #
7
- # @private
8
- class SeregaAttribute
9
- #
10
- # Attribute instance methods
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
- # @private
13
- module AttributeInstanceMethods
14
- # Attribute initial params
15
- # @return [Hash] Attribute initial params
16
- attr_reader :initials
17
-
18
- # Attribute name
19
- # @return [Symbol] Attribute name
20
- attr_reader :name
21
-
22
- # Attribute :many option
23
- # @return [Boolean, nil] Attribute :many option
24
- attr_reader :many
25
-
26
- # Attribute :default option
27
- # @return [Object, nil] Attribute :default option
28
- attr_reader :default
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
- # Shows whether attribute has specified serializer
70
- # @return [Boolean] Checks if attribute is relationship (if :serializer option exists)
71
- def relation?
72
- !@serializer.nil?
73
- end
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
- # Shows specified serializer class
76
- # @return [Serega, nil] Attribute serializer if exists
77
- def serializer
78
- serializer = @serializer
79
- return serializer if (serializer.is_a?(Class) && (serializer < Serega)) || !serializer
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
- @serializer = serializer.is_a?(String) ? Object.const_get(serializer, false) : serializer.call
82
- end
140
+ !hide
141
+ end
83
142
 
84
- #
85
- # Finds attribute value
86
- #
87
- # @param object [Object] Serialized object
88
- # @param context [Hash, nil] Serialization context
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
- # Checks if attribute must be added to serialized response
110
- #
111
- # @param modifiers [Hash] Serialization modifiers
112
- # @option modifiers [Hash] :only The only attributes to serialize
113
- # @option modifiers [Hash] :except Attributes to hide
114
- # @option modifiers [Hash] :with Hidden attributes to serialize additionally
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
- private
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
- attr_reader :value_block, :value_block_signature
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
- def set_normalized_vars(normalizer)
136
- @name = normalizer.name
137
- @many = normalizer.many
138
- @default = normalizer.default
139
- @value_block = normalizer.value_block
140
- @value_block_signature = normalizer.value_block_signature
141
- @hide = normalizer.hide
142
- @serializer = normalizer.serializer
143
- @preloads = normalizer.preloads
144
- @batch_loaders = normalizer.batch_loaders
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
- extend Serega::SeregaHelpers::SerializerClassHelper
149
- include AttributeInstanceMethods
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