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
@@ -2,162 +2,93 @@
2
2
 
3
3
  class Serega
4
4
  module SeregaPlugins
5
+ # `plugin :context_metadata` adds metadata given per serialization with
6
+ # the `:meta` option. Load `plugin :root` first.
5
7
  #
6
- # Plugin :context_metadata
8
+ # ```ruby
9
+ # plugin :root
10
+ # plugin :context_metadata
7
11
  #
8
- # Depends on: `:root` plugin, that must be loaded first
9
- #
10
- # Adds metadata supplied per serialization call (as opposed to `:metadata`,
11
- # which is defined statically on the serializer).
12
- #
13
- # @example
14
- # class UserSerializer < Serega
15
- # plugin :root, root: :data
16
- # plugin :context_metadata, context_metadata_key: :meta
17
- # end
18
- #
19
- # UserSerializer.to_h(nil, meta: { version: '1.0.1' })
20
- # # => {:data=>nil, :version=>"1.0.1"}
12
+ # UserSerializer.to_h(user, meta: {version: "1.0.1"})
13
+ # # => {data: {username: "batman"}, version: "1.0.1"}
14
+ # ```
21
15
  #
16
+ # See [Metadata per serialization](https://github.com/andreyruby/serega/blob/master/docs/root-and-metadata.md#metadata-per-serialization).
22
17
  module ContextMetadata
23
18
  # Default context metadata option name
24
- DEFAULT_CONTEXT_METADATA_KEY = :meta
19
+ DEFAULT_CONTEXT_METADATA_KEY = :meta # :nodoc:
25
20
 
26
- # @return [Symbol] Plugin name
27
- # @private
28
- def self.plugin_name
29
- :context_metadata
30
- end
31
-
32
- # Checks requirements and loads additional plugins
33
- #
34
- # @param serializer_class [Class<Serega>] Current serializer class
35
- # @param opts [Hash] Plugin options
36
- #
37
- # @return [void]
38
- #
39
- # @private
40
- def self.before_load_plugin(serializer_class, **opts)
41
- allowed_keys = %i[context_metadata_key]
42
- opts.each_key do |key|
43
- next if allowed_keys.include?(key)
44
-
45
- raise SeregaError,
46
- "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
47
- " - :context_metadata_key [Symbol] - The key name that must be used to add metadata. Default is :meta."
48
- end
21
+ # Adds the plugin to the serializer
22
+ def self.apply(serializer_class, **opts) # :nodoc:
23
+ SeregaValidations::Utils::CheckAllowedKeys.call(opts, %i[context_metadata_key], "plugin :context_metadata")
49
24
 
50
25
  unless serializer_class.plugin_used?(:root)
51
- raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded after the :root plugin. Please load the :root plugin first"
26
+ raise SeregaError, "Plugin :context_metadata must be loaded after the :root plugin. Please load the :root plugin first"
52
27
  end
53
- end
54
28
 
55
- #
56
- # Applies plugin code to specific serializer
57
- #
58
- # @param serializer_class [Class<Serega>] Current serializer class
59
- # @param _opts [Hash] Plugin options
60
- #
61
- # @return [void]
62
- #
63
- # @private
64
- def self.load_plugin(serializer_class, **_opts)
65
29
  serializer_class.include(InstanceMethods)
66
30
  serializer_class::SeregaConfig.include(ConfigInstanceMethods)
67
- serializer_class::CheckSerializeParams.include(CheckSerializeParamsInstanceMethods)
68
- end
69
31
 
70
- #
71
- # Adds config options and runs other callbacks after plugin was loaded
72
- #
73
- # @param serializer_class [Class<Serega>] Current serializer class
74
- # @param opts [Hash] Plugin options
75
- #
76
- # @return [void]
77
- #
78
- # @private
79
- def self.after_load_plugin(serializer_class, **opts)
80
32
  config = serializer_class.config
81
33
  meta_key = opts[:context_metadata_key] || DEFAULT_CONTEXT_METADATA_KEY
82
34
  config.opts[:context_metadata] = {key: meta_key}
83
35
  config.serialize_keys << meta_key
84
36
  end
85
37
 
86
- #
87
- # Config for `context_metadata` plugin
88
- #
38
+ # Config of the `:context_metadata` plugin, `config.context_metadata`
89
39
  class ContextMetadataConfig
90
- # @return [Hash] context_metadata options
91
- attr_reader :opts
40
+ # context_metadata options
41
+ attr_reader :opts # :nodoc:
92
42
 
93
- #
94
43
  # Initializes context_metadata config object
95
- #
96
- # @param opts [Hash] options
97
- #
98
- # @return [Serega::SeregaPlugins::ContextMetadata::ContextMetadataConfig]
99
- def initialize(opts)
44
+ def initialize(opts, serialize_keys) # :nodoc:
100
45
  @opts = opts
46
+ @serialize_keys = serialize_keys
101
47
  end
102
48
 
103
- # Key that should be used to define metadata
49
+ # Name of the serialization option with metadata, `:meta` by default
104
50
  def key
105
51
  opts.fetch(:key)
106
52
  end
107
53
 
108
- # Sets key that should be used to define metadata
109
- #
110
- # @param new_key [Symbol] New key
111
- #
112
- # @return [Symbol] New key
54
+ # Sets #key. The previous name raises Serega::SeregaError.
113
55
  def key=(new_key)
56
+ previous_key = key
114
57
  opts[:key] = new_key
58
+ @serialize_keys.delete(previous_key)
59
+ @serialize_keys << new_key if new_key
115
60
  end
116
61
  end
117
62
 
118
- #
119
- # Config class additional/patched instance methods
120
- #
121
- # @see Serega::SeregaConfig
122
- #
63
+ # Config methods the plugin adds to Serega::SeregaConfig
123
64
  module ConfigInstanceMethods
124
- # @return [Serega::SeregaPlugins::ContextMetadata::ContextMetadataConfig] context_metadata config
65
+ # Config of the plugin
125
66
  def context_metadata
126
- @context_metadata ||= ContextMetadataConfig.new(opts.fetch(:context_metadata))
67
+ @plugin_configs[:context_metadata] ||= ContextMetadataConfig.new(opts.fetch(:context_metadata), serialize_keys)
127
68
  end
128
- end
129
69
 
130
- #
131
- # CheckSerializeParams class additional/patched instance methods
132
- #
133
- # @see Serega::SeregaValidations::CheckSerializeParams
134
- #
135
- # @private
136
- module CheckSerializeParamsInstanceMethods
137
- private
138
-
139
- def check_opts
70
+ # Builds the context_metadata config before freezing
71
+ def freeze # :nodoc:
72
+ context_metadata
140
73
  super
141
-
142
- meta_key = self.class.serializer_class.config.context_metadata.key
143
- SeregaValidations::Utils::CheckOptIsHash.call(opts, meta_key)
144
74
  end
145
75
  end
146
76
 
147
- #
148
77
  # Serega additional/patched instance methods
149
- #
150
- # @see Serega
151
- #
152
- # @private
153
- module InstanceMethods
78
+ module InstanceMethods # :nodoc:
154
79
  private
155
80
 
156
- def serialize(object, opts)
81
+ def checked_opts(opts)
82
+ checked_opts = super
83
+ SeregaValidations::Utils::CheckOptIsHash.call(checked_opts, self.class.config.context_metadata.key)
84
+ checked_opts
85
+ end
86
+
87
+ def serialize(object, opts, context, many, mode)
157
88
  result = super
158
89
  return result unless result.is_a?(Hash) # return earlier if not a hash, so no root was added
159
90
 
160
- root = build_root(object, opts)
91
+ root = build_root(opts, many)
161
92
  return result unless root # return earlier when no root
162
93
 
163
94
  add_context_metadata(result, opts)
@@ -166,26 +97,12 @@ class Serega
166
97
 
167
98
  def add_context_metadata(hash, opts)
168
99
  context_metadata_key = self.class.config.context_metadata.key
169
- return unless context_metadata_key
170
-
171
100
  metadata = opts[context_metadata_key]
172
101
  return unless metadata
173
102
 
174
- deep_merge_context_metadata(hash, metadata)
175
- end
176
-
177
- def deep_merge_context_metadata(hash, metadata)
178
- hash.merge!(metadata) do |_key, this_val, other_val|
179
- if this_val.is_a?(Hash) && other_val.is_a?(Hash)
180
- deep_merge_context_metadata(this_val, other_val)
181
- else
182
- other_val
183
- end
184
- end
103
+ SeregaUtils::DeepMerge.call(hash, metadata)
185
104
  end
186
105
  end
187
106
  end
188
-
189
- register_plugin(ContextMetadata.plugin_name, ContextMetadata)
190
107
  end
191
108
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Metadata
6
+ class MetaAttribute
7
+ #
8
+ # Validator of the path, the options and the block of a meta attribute
9
+ #
10
+ class CheckParams # :nodoc:
11
+ # Options of `meta_attribute`
12
+ KEYS = %i[const value hide_nil hide_empty].freeze
13
+
14
+ # Characters allowed in each key of the path
15
+ PATH_FORMAT = /\A[\w~-]+\z/
16
+
17
+ # Signatures of a value or a block: up to two parameters, the objects
18
+ # and the context
19
+ VALID_SIGNATURES = %w[0 1 2].freeze
20
+
21
+ class << self
22
+ # Checks the meta attribute params. `check_path` is
23
+ # `config.check_attribute_name`.
24
+ def call(path, opts, block, check_path)
25
+ path.each { |key| check_path_key(key) } if check_path
26
+ SeregaValidations::Utils::CheckAllowedKeys.call(opts, KEYS, :meta_attribute)
27
+ check_true(opts, :hide_empty)
28
+ check_true(opts, :hide_nil)
29
+ check_value_source(opts, block)
30
+ check_value(opts[:value]) if opts.key?(:value)
31
+ check_block(block) if block
32
+ end
33
+
34
+ private
35
+
36
+ def check_path_key(key)
37
+ key = key.to_s
38
+ return if PATH_FORMAT.match?(key)
39
+
40
+ raise SeregaError,
41
+ "Invalid metadata path #{key.inspect}. Allowed characters: \"a-z\", \"A-Z\", \"0-9\", \"_\", \"-\", \"~\""
42
+ end
43
+
44
+ def check_true(opts, key)
45
+ return unless opts.key?(key)
46
+
47
+ value = opts[key]
48
+ raise SeregaError, "Invalid option #{key.inspect} => #{value.inspect}. Must be true" unless value == true
49
+ end
50
+
51
+ # Exactly one of :value, :const and the block gives the value
52
+ def check_value_source(opts, block)
53
+ value = opts.key?(:value)
54
+ const = opts.key?(:const)
55
+
56
+ raise SeregaError, "Option :value can not be used together with option :const" if value && const
57
+ raise SeregaError, "Option :value can not be used together with block" if value && block
58
+ raise SeregaError, "Option :const can not be used together with block" if const && block
59
+ raise SeregaError, "Please provide block argument or add :value or :const option" unless value || const || block
60
+ end
61
+
62
+ def check_value(value)
63
+ raise SeregaError, "Option :value value must be a Proc or respond to #call" unless value.respond_to?(:call)
64
+
65
+ signature = SeregaUtils::MethodSignature.call(value, pos_limit: 2, keyword_args: [])
66
+ return if VALID_SIGNATURES.include?(signature)
67
+
68
+ raise SeregaError, "Option :value value can have maximum 2 parameters (object(s), context)"
69
+ end
70
+
71
+ def check_block(block)
72
+ signature = SeregaUtils::MethodSignature.call(block, pos_limit: 2, keyword_args: [])
73
+ raise SeregaError, "Block can have maximum two parameters (object(s), context)" unless VALID_SIGNATURES.include?(signature)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -6,93 +6,54 @@ class Serega
6
6
  #
7
7
  # Stores Attribute data
8
8
  #
9
- # @private
10
- class MetaAttribute
11
- #
12
- # Stores Attribute instance methods
13
- #
14
- # @private
15
- module InstanceMethods
16
- # @return [Symbol] Meta attribute name
17
- attr_reader :name
18
-
19
- # @return [Symbol] Meta attribute full path
20
- attr_reader :path
21
-
22
- # @return [Proc] Meta attribute options
23
- attr_reader :opts
24
-
25
- # @return [Proc,nil] Meta attribute originally added block
26
- attr_reader :block
27
-
28
- #
29
- # Initializes new meta attribute
30
- #
31
- # @param path [Array<Symbol, String>] Path for metadata of attribute
32
- #
33
- # @param opts [Hash] metadata attribute options
34
- #
35
- # @param block [Proc] Proc that receives object(s) and context and finds value
36
- #
37
- def initialize(path:, opts:, block:)
38
- check(path, opts, block)
39
-
40
- @name = path.join(".").to_sym
41
- @path = SeregaUtils::EnumDeepDup.call(path)
42
- @opts = SeregaUtils::EnumDeepDup.call(opts)
43
- @block = block
44
- @normalized_block = normalize_block(opts[:value], opts[:const], block)
45
- @normalized_block_signature =
46
- SeregaUtils::MethodSignature.call(@normalized_block, pos_limit: 2, keyword_args: [])
47
- end
48
-
49
- #
50
- # Finds attribute value
51
- #
52
- # @param object [Object] Serialized object(s)
53
- # @param context [Hash, nil] Serialization context
54
- #
55
- # @return [Object] Serialized meta attribute value
56
- #
57
- def value(object, context)
58
- case normalized_block_signature
59
- when "0" then normalized_block.call
60
- when "1" then normalized_block.call(object)
61
- else normalized_block.call(object, context)
62
- end
63
- end
9
+ class MetaAttribute # :nodoc:
10
+ # Meta attribute name
11
+ attr_reader :name
12
+
13
+ # Meta attribute full path
14
+ attr_reader :path
15
+
16
+ # Meta attribute options
17
+ attr_reader :opts
18
+
19
+ # Meta attribute originally added block
20
+ attr_reader :block
21
+
22
+ # Initializes new meta attribute
23
+ def initialize(serializer_class, path:, opts:, block:)
24
+ CheckParams.call(path, opts, block, serializer_class.config.check_attribute_name)
25
+
26
+ @name = path.join(".").to_sym
27
+ @path = SeregaUtils::EnumDeepDup.call(path)
28
+ @opts = SeregaUtils::EnumDeepDup.call(opts)
29
+ @block = block
30
+ @normalized_block = normalize_block(opts[:value], opts[:const], block)
31
+ @normalized_block_signature =
32
+ SeregaUtils::MethodSignature.call(@normalized_block, pos_limit: 2, keyword_args: [])
33
+ end
64
34
 
65
- def hide?(value)
66
- (!!opts[:hide_nil] && value.nil?) || (!!opts[:hide_empty] && (value.nil? || (value.respond_to?(:empty?) && value.empty?)))
35
+ # Finds attribute value
36
+ def value(object, context)
37
+ case normalized_block_signature
38
+ when "0" then normalized_block.call
39
+ when "1" then normalized_block.call(object)
40
+ else normalized_block.call(object, context)
67
41
  end
42
+ end
68
43
 
69
- private
44
+ def hide?(value)
45
+ (!!opts[:hide_nil] && value.nil?) || (!!opts[:hide_empty] && (value.nil? || (value.respond_to?(:empty?) && value.empty?)))
46
+ end
70
47
 
71
- attr_reader :normalized_block, :normalized_block_signature
48
+ private
72
49
 
73
- def normalize_block(value, const, block)
74
- return proc { const } if const
50
+ attr_reader :normalized_block, :normalized_block_signature
75
51
 
76
- value || block
77
- end
78
-
79
- def check(path, opts, block)
80
- CheckPath.call(path) if check_attribute_name
81
- CheckOpts.call(opts, block, attribute_keys)
82
- CheckBlock.call(block) if block
83
- end
52
+ def normalize_block(value, const, block)
53
+ return proc { const } if const
84
54
 
85
- def attribute_keys
86
- self.class.serializer_class.config.metadata.attribute_keys
87
- end
88
-
89
- def check_attribute_name
90
- self.class.serializer_class.config.check_attribute_name
91
- end
55
+ value || block
92
56
  end
93
-
94
- extend Serega::SeregaHelpers::SerializerClassHelper
95
- include InstanceMethods
96
57
  end
97
58
  end
98
59
  end