serega 0.20.1 → 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: 11094f59dcd5d1d918604998ac267dbd13297bf0f70a9f3da5fd5ba74eb76b06
4
- data.tar.gz: 97d1a419769b73937afde735e24210690e2f7702943d60db5be4252301e07255
3
+ metadata.gz: 9ea0f19662815497e76a3ace9b247e90268f7c44a3cf917b97a186887043fc7b
4
+ data.tar.gz: fb13ebbaa26603c0eda053e78634aaecf1a8d040c73b5824892b03c2b0d2a70d
5
5
  SHA512:
6
- metadata.gz: f9e3bfb207894f09c9c88658bad62e2a28e8b4d44648a74f807d332862b0cd7bdc67870f0d06786de0eb64537fd1a824c5d4f375406b71d18410b63c5a8c5320
7
- data.tar.gz: 1171f2aabb7ce1c63a9e2f95f5413e9d80a882326ada4fac561b527c8a1adcc263ff19d6224eb070f0ebb333254ff48c9c7f56861ff6418732848cde3ffbc5b5
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.1
1
+ 0.21.0
@@ -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
 
@@ -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.1
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: 2024-02-25 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