serega 0.20.0 → 0.21.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a8bc4f49c84bd623535d3134b77e01cd002b4a3fe6bea9b2dbc998e80911e53
4
- data.tar.gz: e06e06c478dc605afa417e0a97289b2930b7ce084e3d57f07d1b3f94f7ff7717
3
+ metadata.gz: 9ea0f19662815497e76a3ace9b247e90268f7c44a3cf917b97a186887043fc7b
4
+ data.tar.gz: fb13ebbaa26603c0eda053e78634aaecf1a8d040c73b5824892b03c2b0d2a70d
5
5
  SHA512:
6
- metadata.gz: 6b699d5ae9ed68da0147be26cd1f64ef5e26a99ce22b260889a7a831af8765f3fc02c02ce4c879672c719b4ad8e7c40aaf5903e3183bf770061e8cf54bcd37fc
7
- data.tar.gz: a82645b1ac1b2c68fea64c49235d78b926608de0e035d7042cb37ab5836fe6885742d63b4317cceb91889d9b87a54af9f4b2e6de1455cbfee9f886f79396bdcf
6
+ metadata.gz: a5847251f29dfd44a7da2cd9b5a99411aa430323d1d57e7706e176cc234f96ff7575298c5909c753aa4f3a2895d41a48c3c6a5b67a95e0bdac935f38c5c6f15d
7
+ data.tar.gz: 4fdf12f075816ea3ba155ace47b6f5e6c7b3b0bcd000179e43e4edf356e63b19360de227b6112b4fe98e310975da4afebc3d5c86476deadc749c4197330ddfd0
data/README.md CHANGED
@@ -549,6 +549,9 @@ UserSerializer.to_h(user)
549
549
  # => preloads {users_stats: {}, albums: { downloads: {} }}
550
550
  ```
551
551
 
552
+ For testing purposes preloading can be done manually with
553
+ `#preload_association_to(obj)` instance method
554
+
552
555
  ### Plugin :batch
553
556
 
554
557
  Helps to omit N+1.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.20.0
1
+ 0.21.0
@@ -70,6 +70,7 @@ class Serega
70
70
 
71
71
  # Patched in:
72
72
  # - plugin :if (conditionally skips attaching)
73
+ # - plugin :batch :if extension (removes prepared key)
73
74
  def attach_final_value(final_value, point, container)
74
75
  container[point.name] = final_value
75
76
  end
@@ -90,6 +90,22 @@ class Serega
90
90
  # Overrides Serega class instance methods
91
91
  #
92
92
  module InstanceMethods
93
+ #
94
+ # Preloads associations to object
95
+ #
96
+ # @param object [Object] Any object
97
+ # @return provided object
98
+ #
99
+ def preload_associations_to(object)
100
+ return object if object.nil? || (object.is_a?(Array) && object.empty?)
101
+
102
+ preloads = preloads() # `preloads()` method comes from :preloads plugin
103
+ return object if preloads.empty?
104
+
105
+ Preloader.preload(object, preloads)
106
+ object
107
+ end
108
+
93
109
  private
94
110
 
95
111
  #
@@ -97,18 +113,9 @@ class Serega
97
113
  # Preloads associations to object before serialization
98
114
  #
99
115
  def serialize(object, _opts)
100
- object = add_preloads(object)
116
+ preload_associations_to(object)
101
117
  super
102
118
  end
103
-
104
- def add_preloads(obj)
105
- return obj if obj.nil? || (obj.is_a?(Array) && obj.empty?)
106
-
107
- preloads = preloads() # `preloads()` method comes from :preloads plugin
108
- return obj if preloads.empty?
109
-
110
- Preloader.preload(obj, preloads)
111
- end
112
119
  end
113
120
  end
114
121
 
@@ -105,6 +105,11 @@ class Serega
105
105
  serializer_class::SeregaAttribute.include(PluginsExtensions::Formatters::SeregaAttributeInstanceMethods)
106
106
  end
107
107
 
108
+ if serializer_class.plugin_used?(:if)
109
+ require_relative "lib/plugins_extensions/if"
110
+ serializer_class::SeregaObjectSerializer.include(PluginsExtensions::If::ObjectSerializerInstanceMethods123)
111
+ end
112
+
108
113
  if serializer_class.plugin_used?(:preloads)
109
114
  require_relative "lib/plugins_extensions/preloads"
110
115
  serializer_class::SeregaAttributeNormalizer.include(PluginsExtensions::Preloads::AttributeNormalizerInstanceMethods)
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Batch
6
+ #
7
+ # Extensions (mini-plugins) that are enabled when :batch plugin used with other plugins
8
+ #
9
+ module PluginsExtensions
10
+ #
11
+ # Extension that is used when :if plugin is loaded
12
+ #
13
+ module If
14
+ #
15
+ # SeregaObjectSerializer additional/patched class methods
16
+ #
17
+ # @see Serega::SeregaObjectSerializer
18
+ #
19
+ module ObjectSerializerInstanceMethods123
20
+ private
21
+
22
+ # Removes key added by `batch` plugin at the start of serialization to preserve attributes ordering
23
+ def attach_final_value(value, point, container)
24
+ container.delete(point.name) if super == SeregaPlugins::If::KEY_SKIPPED
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -42,11 +42,27 @@ class Serega
42
42
  # end
43
43
  #
44
44
  module If
45
+ # This value must be returned to identify that serialization key was skipped
46
+ KEY_SKIPPED = :_key_skipped_with_serega_if_plugin
47
+
45
48
  # @return [Symbol] Plugin name
46
49
  def self.plugin_name
47
50
  :if
48
51
  end
49
52
 
53
+ # Checks requirements to load plugin
54
+ #
55
+ # @param serializer_class [Class<Serega>] Current serializer class
56
+ # @param opts [Hash] plugin options
57
+ #
58
+ # @return [void]
59
+ #
60
+ def self.before_load_plugin(serializer_class, **opts)
61
+ if serializer_class.plugin_used?(:batch)
62
+ raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded before the :batch plugin"
63
+ end
64
+ end
65
+
50
66
  #
51
67
  # Applies plugin code to specific serializer
52
68
  #
@@ -194,12 +210,13 @@ class Serega
194
210
  private
195
211
 
196
212
  def serialize_point(object, point, _container)
197
- return unless point.satisfy_if_conditions?(object, context)
213
+ return KEY_SKIPPED unless point.satisfy_if_conditions?(object, context)
198
214
  super
199
215
  end
200
216
 
201
217
  def attach_final_value(value, point, _container)
202
- return unless point.satisfy_if_value_conditions?(value, context)
218
+ return KEY_SKIPPED unless point.satisfy_if_value_conditions?(value, context)
219
+
203
220
  super
204
221
  end
205
222
  end
@@ -88,6 +88,10 @@ class Serega
88
88
  " - :auto_preload_attributes_with_serializer [Boolean] - Automatically adds `preload: <attribute_name>` option to attributes with :serializer option specified\n" \
89
89
  " - :auto_hide_attributes_with_preload [Boolean] - Automatically adds `hide: true` option to attributes with :preload option (specified manually or added automatically)"
90
90
  end
91
+
92
+ if serializer_class.plugin_used?(:batch)
93
+ raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded before the :batch plugin"
94
+ end
91
95
  end
92
96
 
93
97
  #
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "stringio"
4
-
5
3
  class Serega
6
4
  module SeregaPlugins
7
5
  #
@@ -25,6 +23,14 @@ class Serega
25
23
  # Modifiers parser
26
24
  #
27
25
  class ParseStringModifiers
26
+ COMMA = ","
27
+ COMMA_CODEPOINT = COMMA.ord
28
+ LPAREN = "("
29
+ LPAREN_CODEPOINT = LPAREN.ord
30
+ RPAREN = ")"
31
+ RPAREN_CODEPOINT = RPAREN.ord
32
+ private_constant :COMMA, :LPAREN, :RPAREN, :COMMA_CODEPOINT, :LPAREN_CODEPOINT, :RPAREN_CODEPOINT
33
+
28
34
  class << self
29
35
  #
30
36
  # Parses string modifiers
@@ -40,45 +46,52 @@ class Serega
40
46
  # parse("user,comments") => { user: {}, comments: {} }
41
47
  # parse("user(comments(text))") => { user: { comments: { text: {} } } }
42
48
  def parse(fields)
43
- res = {}
44
- attribute = +""
45
- char = +""
46
- path_stack = nil
47
- fields = StringIO.new(fields)
49
+ result = {}
50
+ attribute_storage = result
51
+ path_stack = (fields.include?(LPAREN) || fields.include?(RPAREN)) ? [] : nil
48
52
 
49
- while fields.read(1, char)
50
- case char
51
- when ","
52
- add_attribute(res, path_stack, attribute, FROZEN_EMPTY_HASH)
53
- when ")"
54
- add_attribute(res, path_stack, attribute, FROZEN_EMPTY_HASH)
55
- path_stack&.pop
56
- when "("
57
- name = add_attribute(res, path_stack, attribute, {})
58
- (path_stack ||= []).push(name) if name
59
- else
60
- attribute.insert(-1, char)
53
+ start_index = 0
54
+ end_index = 0
55
+ fields.each_codepoint do |codepoint|
56
+ case codepoint
57
+ when COMMA_CODEPOINT
58
+ attribute = extract_attribute(fields, start_index, end_index)
59
+ add_attribute(attribute_storage, attribute, FROZEN_EMPTY_HASH) if attribute
60
+ start_index = end_index + 1
61
+ when LPAREN_CODEPOINT
62
+ attribute = extract_attribute(fields, start_index, end_index)
63
+ if attribute
64
+ attribute_storage = add_attribute(attribute_storage, attribute, {})
65
+ path_stack.push(attribute)
66
+ end
67
+ start_index = end_index + 1
68
+ when RPAREN_CODEPOINT
69
+ attribute = extract_attribute(fields, start_index, end_index)
70
+ add_attribute(attribute_storage, attribute, FROZEN_EMPTY_HASH) if attribute
71
+ path_stack.pop
72
+ attribute_storage = dig?(result, path_stack)
73
+ start_index = end_index + 1
61
74
  end
75
+
76
+ end_index += 1
62
77
  end
63
78
 
64
- add_attribute(res, path_stack, attribute, FROZEN_EMPTY_HASH)
79
+ attribute = extract_attribute(fields, start_index, end_index)
80
+ add_attribute(attribute_storage, attribute, FROZEN_EMPTY_HASH) if attribute
65
81
 
66
- res
82
+ result
67
83
  end
68
84
 
69
85
  private
70
86
 
71
- def add_attribute(res, path_stack, attribute, nested_attributes = FROZEN_EMPTY_HASH)
87
+ def extract_attribute(fields, start_index, end_index)
88
+ attribute = fields[start_index, end_index - start_index]
72
89
  attribute.strip!
73
- return if attribute.empty?
74
-
75
- name = attribute.to_sym
76
- attribute.clear
77
-
78
- current_attrs = dig?(res, path_stack)
79
- current_attrs[name] = nested_attributes
90
+ attribute.empty? ? nil : attribute.to_sym
91
+ end
80
92
 
81
- name
93
+ def add_attribute(storage, attribute, nested_attributes = FROZEN_EMPTY_HASH)
94
+ storage[attribute] = nested_attributes
82
95
  end
83
96
 
84
97
  def dig?(hash, path)
data/lib/serega.rb CHANGED
@@ -167,6 +167,7 @@ class Serega
167
167
  modifiers_opts = FROZEN_EMPTY_HASH
168
168
  serialize_opts = nil
169
169
  else
170
+ opts.transform_keys!(&:to_sym)
170
171
  serialize_opts = opts.except(*initiate_keys)
171
172
  modifiers_opts = opts.slice(*initiate_keys)
172
173
  end
@@ -296,7 +297,14 @@ class Serega
296
297
  # @option opts [Boolean] :validate Validates provided modifiers (Default is true)
297
298
  #
298
299
  def initialize(opts = nil)
299
- @opts = (opts.nil? || opts.empty?) ? FROZEN_EMPTY_HASH : parse_modifiers(opts)
300
+ @opts =
301
+ if opts.nil? || opts.empty?
302
+ FROZEN_EMPTY_HASH
303
+ else
304
+ opts.transform_keys!(&:to_sym)
305
+ parse_modifiers(opts)
306
+ end
307
+
300
308
  self.class::CheckInitiateParams.new(@opts).validate if opts&.fetch(:check_initiate_params) { config.check_initiate_params }
301
309
 
302
310
  @plan = self.class::SeregaPlan.call(@opts)
@@ -320,10 +328,10 @@ class Serega
320
328
  # @return [Hash] Serialization result
321
329
  #
322
330
  def call(object, opts = nil)
323
- self.class::CheckSerializeParams.new(opts).validate if opts&.any?
324
- opts ||= {}
325
- opts[:context] ||= {}
331
+ opts = opts ? opts.transform_keys!(&:to_sym) : {}
332
+ self.class::CheckSerializeParams.new(opts).validate unless opts.empty?
326
333
 
334
+ opts[:context] ||= {}
327
335
  serialize(object, opts)
328
336
  end
329
337
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serega
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.0
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Glushkov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-28 00:00:00.000000000 Z
11
+ date: 2024-11-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  JSON Serializer
@@ -54,6 +54,7 @@ files:
54
54
  - lib/serega/plugins/batch/lib/modules/plan_point.rb
55
55
  - lib/serega/plugins/batch/lib/plugins_extensions/activerecord_preloads.rb
56
56
  - lib/serega/plugins/batch/lib/plugins_extensions/formatters.rb
57
+ - lib/serega/plugins/batch/lib/plugins_extensions/if.rb
57
58
  - lib/serega/plugins/batch/lib/plugins_extensions/preloads.rb
58
59
  - lib/serega/plugins/batch/lib/validations/check_batch_opt_id_method.rb
59
60
  - lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb
@@ -140,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
141
  - !ruby/object:Gem::Version
141
142
  version: '0'
142
143
  requirements: []
143
- rubygems_version: 3.5.3
144
+ rubygems_version: 3.5.6
144
145
  signing_key:
145
146
  specification_version: 4
146
147
  summary: JSON Serializer