serega 0.35.0 → 0.36.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 +13 -1
- data/VERSION +1 -1
- data/lib/serega/batch/attribute_loaders.rb +1 -1
- data/lib/serega/data_builder.rb +52 -0
- data/lib/serega/plan.rb +33 -3
- data/lib/serega/plugins/if/if.rb +23 -0
- data/lib/serega/plugins/if/validations/check_opt_if.rb +7 -4
- data/lib/serega/plugins/if/validations/check_opt_if_value.rb +7 -4
- data/lib/serega/plugins/if/validations/check_opt_unless.rb +7 -4
- data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +7 -4
- data/lib/serega/plugins/root/root.rb +80 -0
- data/lib/serega.rb +74 -20
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 461e4c9b6b9dcaf1c93cc490c8a4270bcc8ae576eed523ef14ee3bc1e6be0944
|
|
4
|
+
data.tar.gz: 7ac9f86f0952070deb5c6a5a812ec9384d9cadab9f783fa5ae5e705ff6cd3116
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: addb468352c4adc023c4f04e30dc6533fefbf7faba9755e02cd243e9f4631c30a0b3b4dd65b160a0f5c012e76717e2d67ddf3c0ac712e5cd0b3008ea3cd8df7d
|
|
7
|
+
data.tar.gz: da936fb8dd2fac9bafe4e670da795f3b3075d591b3c88411b306898d9a59f944354dc8b69941e19b7b2012bda6a6111a25f9dd0347d5ad8c76b56f310c0353ec
|
data/README.md
CHANGED
|
@@ -141,7 +141,7 @@ end
|
|
|
141
141
|
|
|
142
142
|
### Serializing
|
|
143
143
|
|
|
144
|
-
We can serialize objects using class method `.call` aliased
|
|
144
|
+
We can serialize objects using class method `.call` aliased as `.to_h` and
|
|
145
145
|
same instance methods `#call` and its alias `#to_h`.
|
|
146
146
|
|
|
147
147
|
```ruby
|
|
@@ -155,6 +155,18 @@ UserSerializer.to_h(user) # => {username: "serega"}
|
|
|
155
155
|
UserSerializer.to_h([user]) # => [{username: "serega"}]
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
+
Use `.to_data` / `#to_data` to get the same result as Ruby `Data` objects
|
|
159
|
+
(immutable value objects, Ruby 3.2+). Nested serialized relations are also
|
|
160
|
+
converted to `Data` objects.
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
result = UserSerializer.to_data(user)
|
|
164
|
+
result # => #<data username="serega">
|
|
165
|
+
result.username # => "serega"
|
|
166
|
+
|
|
167
|
+
UserSerializer.to_data([user]) # => [#<data username="serega">]
|
|
168
|
+
```
|
|
169
|
+
|
|
158
170
|
If serialized fields are constant, then it's a good idea to initiate the
|
|
159
171
|
serializer and reuse it.
|
|
160
172
|
It will be a bit faster (the serialization plan will be prepared only once).
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.36.0
|
|
@@ -18,7 +18,7 @@ class Serega
|
|
|
18
18
|
@point_index = {}.compare_by_identity
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
# Remembers data for batch
|
|
21
|
+
# Remembers data for batch serialization:
|
|
22
22
|
#
|
|
23
23
|
# @param point [SeregaPlanPoint] Serialization plan point
|
|
24
24
|
# @param object [Object] Serialized object
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Serega
|
|
4
|
+
#
|
|
5
|
+
# Builds Data objects from serialized hashes.
|
|
6
|
+
# Used by `#to_data` / `.to_data`.
|
|
7
|
+
#
|
|
8
|
+
class SeregaDataBuilder
|
|
9
|
+
# SeregaDataBuilder class methods
|
|
10
|
+
module SeregaDataBuilderClassMethods
|
|
11
|
+
#
|
|
12
|
+
# Converts serialized Hash/Array result into a tree of Ruby Data objects
|
|
13
|
+
# following the serializer's plan structure.
|
|
14
|
+
#
|
|
15
|
+
# @param serializer [Serega] Serializer instance carrying the plan
|
|
16
|
+
# @param serialized [Hash, Array, nil] Serialized output from `#to_h`
|
|
17
|
+
#
|
|
18
|
+
# @return [Data, Array<Data>, nil] Serialization result as Data object(s)
|
|
19
|
+
#
|
|
20
|
+
def call(serializer, serialized)
|
|
21
|
+
build(serialized, serializer.plan)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def build(serialized, plan)
|
|
27
|
+
case serialized
|
|
28
|
+
when Array then serialized.map { |item| hash_to_data(item, plan) }
|
|
29
|
+
when Hash then hash_to_data(serialized, plan)
|
|
30
|
+
else serialized
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def hash_to_data(hash, plan)
|
|
35
|
+
hash_data = hash.to_h do |key, value|
|
|
36
|
+
child_plan = plan.points_hash.fetch(key).child_plan
|
|
37
|
+
value = build(value, child_plan) if child_plan
|
|
38
|
+
[key, value]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
build_data_object(plan, hash_data)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def build_data_object(plan, hash_data)
|
|
45
|
+
plan.data_class.new(**hash_data)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
extend SeregaHelpers::SerializerClassHelper
|
|
50
|
+
extend SeregaDataBuilderClassMethods
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/serega/plan.rb
CHANGED
|
@@ -27,6 +27,18 @@ class Serega
|
|
|
27
27
|
cached_plan_for(opts, max_cache_size)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
#
|
|
31
|
+
# Returns (and caches) the Data class for the given set of field names.
|
|
32
|
+
# Uses the Array as cache key so the same Data class is reused across
|
|
33
|
+
# all plan instances with identical fields.
|
|
34
|
+
#
|
|
35
|
+
# @param point_names [Array<Symbol>] Attribute names for the Data members
|
|
36
|
+
# @return [Class] Subclass of Data
|
|
37
|
+
#
|
|
38
|
+
def data_class_for(point_names)
|
|
39
|
+
(@data_classes ||= {})[point_names] ||= Data.define(*point_names)
|
|
40
|
+
end
|
|
41
|
+
|
|
30
42
|
private
|
|
31
43
|
|
|
32
44
|
def cached_plan_for(opts, max_cache_size)
|
|
@@ -57,6 +69,10 @@ class Serega
|
|
|
57
69
|
# SeregaPlan instance methods
|
|
58
70
|
#
|
|
59
71
|
module InstanceMethods
|
|
72
|
+
# Shows if plan includes batch points
|
|
73
|
+
# @return [Array<SeregaPlanPoint>] points to serialize
|
|
74
|
+
attr_reader :has_batch_points
|
|
75
|
+
|
|
60
76
|
# Parent plan point
|
|
61
77
|
# @return [SeregaPlanPoint, nil]
|
|
62
78
|
attr_reader :parent_plan_point
|
|
@@ -65,9 +81,9 @@ class Serega
|
|
|
65
81
|
# @return [Array<SeregaPlanPoint>] points to serialize
|
|
66
82
|
attr_reader :points
|
|
67
83
|
|
|
68
|
-
#
|
|
69
|
-
# @return [
|
|
70
|
-
attr_reader :
|
|
84
|
+
# Named serialization points
|
|
85
|
+
# @return [Hash] Named points
|
|
86
|
+
attr_reader :points_hash
|
|
71
87
|
|
|
72
88
|
#
|
|
73
89
|
# Instantiate new serialization plan
|
|
@@ -87,6 +103,7 @@ class Serega
|
|
|
87
103
|
@has_batch_points = false # should be before assigning points, generated points can change this attribute
|
|
88
104
|
@parent_plan_point = parent_plan_point
|
|
89
105
|
@points = attributes_points(modifiers)
|
|
106
|
+
@points_hash = points.to_h { |point| [point.name, point] }
|
|
90
107
|
end
|
|
91
108
|
|
|
92
109
|
#
|
|
@@ -96,6 +113,15 @@ class Serega
|
|
|
96
113
|
self.class.serializer_class
|
|
97
114
|
end
|
|
98
115
|
|
|
116
|
+
# Returns the Data class whose members match this plan's serialized fields.
|
|
117
|
+
# Delegates to the class-level cache so identical field sets share one Data class.
|
|
118
|
+
#
|
|
119
|
+
# @return [Class] Subclass of Data
|
|
120
|
+
#
|
|
121
|
+
def data_class
|
|
122
|
+
@data_class ||= self.class.data_class_for(point_names)
|
|
123
|
+
end
|
|
124
|
+
|
|
99
125
|
#
|
|
100
126
|
# Marks current plan and top-level plans as `has_batch_points`
|
|
101
127
|
# It is needed to initialize Batch Attribute Loaders when serialization starts
|
|
@@ -130,6 +156,10 @@ class Serega
|
|
|
130
156
|
|
|
131
157
|
points.freeze
|
|
132
158
|
end
|
|
159
|
+
|
|
160
|
+
def point_names
|
|
161
|
+
@point_names ||= points.map(&:name)
|
|
162
|
+
end
|
|
133
163
|
end
|
|
134
164
|
|
|
135
165
|
extend ClassMethods
|
data/lib/serega/plugins/if/if.rb
CHANGED
|
@@ -69,6 +69,7 @@ class Serega
|
|
|
69
69
|
serializer_class::SeregaPlanPoint.include(PlanPointInstanceMethods)
|
|
70
70
|
serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
|
|
71
71
|
serializer_class::SeregaObjectSerializer.include(ObjectSerializerInstanceMethods)
|
|
72
|
+
serializer_class::SeregaDataBuilder.extend(DataBuilderClassMethods)
|
|
72
73
|
end
|
|
73
74
|
|
|
74
75
|
#
|
|
@@ -215,6 +216,7 @@ class Serega
|
|
|
215
216
|
when "1" then condition.call(object)
|
|
216
217
|
when "2" then condition.call(object, context)
|
|
217
218
|
when "1_ctx" then condition.call(object, ctx: context)
|
|
219
|
+
when "2_ctx" then condition.call(object, context, ctx: context)
|
|
218
220
|
else # "0"
|
|
219
221
|
condition.call
|
|
220
222
|
end
|
|
@@ -239,6 +241,27 @@ class Serega
|
|
|
239
241
|
end
|
|
240
242
|
end
|
|
241
243
|
|
|
244
|
+
#
|
|
245
|
+
# SeregaDataBuilder additional/patched class methods
|
|
246
|
+
#
|
|
247
|
+
# Overrides `build_data_object` to handle plans where some keys were skipped
|
|
248
|
+
# by `:if`/`:unless`/`:if_value`/`:unless_value` conditions, so the Data class
|
|
249
|
+
# is built from the actually-present keys rather than the full plan.
|
|
250
|
+
#
|
|
251
|
+
# @see Serega::SeregaDataBuilder
|
|
252
|
+
#
|
|
253
|
+
module DataBuilderClassMethods
|
|
254
|
+
private
|
|
255
|
+
|
|
256
|
+
def build_data_object(plan, hash_data)
|
|
257
|
+
if hash_data.size < plan.points.size
|
|
258
|
+
plan.class.data_class_for(hash_data.keys).new(**hash_data)
|
|
259
|
+
else
|
|
260
|
+
super
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
242
265
|
#
|
|
243
266
|
# SeregaObjectSerializer additional/patched class methods
|
|
244
267
|
#
|
|
@@ -47,6 +47,8 @@ class Serega
|
|
|
47
47
|
true
|
|
48
48
|
when "1_ctx" # (object, :ctx)
|
|
49
49
|
true
|
|
50
|
+
when "2_ctx" # (object, context, :ctx)
|
|
51
|
+
true
|
|
50
52
|
else
|
|
51
53
|
false
|
|
52
54
|
end
|
|
@@ -55,10 +57,11 @@ class Serega
|
|
|
55
57
|
def signature_error
|
|
56
58
|
<<~ERROR.strip
|
|
57
59
|
Invalid attribute option :if parameters, valid parameters signatures:
|
|
58
|
-
- ()
|
|
59
|
-
- (object)
|
|
60
|
-
- (object, context)
|
|
61
|
-
- (object, :ctx)
|
|
60
|
+
- () # no parameters
|
|
61
|
+
- (object) # one positional parameter
|
|
62
|
+
- (object, context) # two positional parameters
|
|
63
|
+
- (object, :ctx) # one positional parameter and :ctx keyword
|
|
64
|
+
- (object, context, :ctx) # two positional parameters and :ctx keyword
|
|
62
65
|
ERROR
|
|
63
66
|
end
|
|
64
67
|
end
|
|
@@ -52,6 +52,8 @@ class Serega
|
|
|
52
52
|
true
|
|
53
53
|
when "1_ctx" # (value, :ctx)
|
|
54
54
|
true
|
|
55
|
+
when "2_ctx" # (value, context, :ctx)
|
|
56
|
+
true
|
|
55
57
|
else
|
|
56
58
|
false
|
|
57
59
|
end
|
|
@@ -60,10 +62,11 @@ class Serega
|
|
|
60
62
|
def signature_error
|
|
61
63
|
<<~ERROR.strip
|
|
62
64
|
Invalid attribute option :if_value parameters, valid parameters signatures:
|
|
63
|
-
- ()
|
|
64
|
-
- (value)
|
|
65
|
-
- (value, context)
|
|
66
|
-
- (value, :ctx)
|
|
65
|
+
- () # no parameters
|
|
66
|
+
- (value) # one positional parameter
|
|
67
|
+
- (value, context) # two positional parameters
|
|
68
|
+
- (value, :ctx) # one positional parameter and :ctx keyword
|
|
69
|
+
- (value, context, :ctx) # two positional parameters and :ctx keyword
|
|
67
70
|
ERROR
|
|
68
71
|
end
|
|
69
72
|
end
|
|
@@ -47,6 +47,8 @@ class Serega
|
|
|
47
47
|
true
|
|
48
48
|
when "1_ctx" # (object, :ctx)
|
|
49
49
|
true
|
|
50
|
+
when "2_ctx" # (object, context, :ctx)
|
|
51
|
+
true
|
|
50
52
|
else
|
|
51
53
|
false
|
|
52
54
|
end
|
|
@@ -55,10 +57,11 @@ class Serega
|
|
|
55
57
|
def signature_error
|
|
56
58
|
<<~ERROR.strip
|
|
57
59
|
Invalid attribute option :unless parameters, valid parameters signatures:
|
|
58
|
-
- ()
|
|
59
|
-
- (object)
|
|
60
|
-
- (object, context)
|
|
61
|
-
- (object, :ctx)
|
|
60
|
+
- () # no parameters
|
|
61
|
+
- (object) # one positional parameter
|
|
62
|
+
- (object, context) # two positional parameters
|
|
63
|
+
- (object, :ctx) # one positional parameter and :ctx keyword
|
|
64
|
+
- (object, context, :ctx) # two positional parameters and :ctx keyword
|
|
62
65
|
ERROR
|
|
63
66
|
end
|
|
64
67
|
end
|
|
@@ -52,6 +52,8 @@ class Serega
|
|
|
52
52
|
true
|
|
53
53
|
when "1_ctx" # (value, :ctx)
|
|
54
54
|
true
|
|
55
|
+
when "2_ctx" # (value, context, :ctx)
|
|
56
|
+
true
|
|
55
57
|
else
|
|
56
58
|
false
|
|
57
59
|
end
|
|
@@ -60,10 +62,11 @@ class Serega
|
|
|
60
62
|
def signature_error
|
|
61
63
|
<<~ERROR.strip
|
|
62
64
|
Invalid attribute option :unless_value parameters, valid parameters signatures:
|
|
63
|
-
- ()
|
|
64
|
-
- (value)
|
|
65
|
-
- (value, context)
|
|
66
|
-
- (value, :ctx)
|
|
65
|
+
- () # no parameters
|
|
66
|
+
- (value) # one positional parameter
|
|
67
|
+
- (value, context) # two positional parameters
|
|
68
|
+
- (value, :ctx) # one positional parameter and :ctx keyword
|
|
69
|
+
- (value, context, :ctx) # two positional parameters and :ctx keyword
|
|
67
70
|
ERROR
|
|
68
71
|
end
|
|
69
72
|
end
|
|
@@ -92,6 +92,7 @@ class Serega
|
|
|
92
92
|
serializer_class.extend(ClassMethods)
|
|
93
93
|
serializer_class.include(InstanceMethods)
|
|
94
94
|
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
|
|
95
|
+
serializer_class::SeregaDataBuilder.extend(DataBuilderClassMethods)
|
|
95
96
|
end
|
|
96
97
|
|
|
97
98
|
#
|
|
@@ -214,6 +215,22 @@ class Serega
|
|
|
214
215
|
# @see Serega
|
|
215
216
|
#
|
|
216
217
|
module InstanceMethods
|
|
218
|
+
#
|
|
219
|
+
# Serializes provided object to a tree of Ruby Data objects.
|
|
220
|
+
# When a root key is configured, the result is wrapped in an outer Data object
|
|
221
|
+
# whose members include the root key plus any metadata keys.
|
|
222
|
+
#
|
|
223
|
+
# @param object [Object] Serialized object
|
|
224
|
+
# @param opts [Hash, nil] Serializing options (`:root`, `:many`, `:context`, etc.)
|
|
225
|
+
#
|
|
226
|
+
# @return [Data, Array<Data>, nil] Serialization result as Data object(s)
|
|
227
|
+
#
|
|
228
|
+
def to_data(object, opts = nil)
|
|
229
|
+
opts = prepare_initial_serialization_opts(object, opts)
|
|
230
|
+
serialized_data = serialize(object, opts)
|
|
231
|
+
self.class::SeregaDataBuilder.call(self, serialized_data, opts)
|
|
232
|
+
end
|
|
233
|
+
|
|
217
234
|
private
|
|
218
235
|
|
|
219
236
|
def serialize(object, opts)
|
|
@@ -230,6 +247,69 @@ class Serega
|
|
|
230
247
|
(opts.fetch(:many) { object.is_a?(Enumerable) }) ? root.many : root.one
|
|
231
248
|
end
|
|
232
249
|
end
|
|
250
|
+
|
|
251
|
+
#
|
|
252
|
+
# SeregaDataBuilder additional/patched class methods
|
|
253
|
+
#
|
|
254
|
+
# Overrides `call` to handle the root key: the serialized result is
|
|
255
|
+
# unwrapped from the root key, converted to a Data tree by the base
|
|
256
|
+
# implementation, and then re-wrapped together with any metadata keys
|
|
257
|
+
# in an outer Data object. Metadata values (arbitrary hashes/arrays)
|
|
258
|
+
# are recursively converted to Data objects via `deep_hash_to_data`.
|
|
259
|
+
#
|
|
260
|
+
# When the root key is `nil` (disabled), delegates directly to the base.
|
|
261
|
+
#
|
|
262
|
+
# @see Serega::SeregaDataBuilder
|
|
263
|
+
#
|
|
264
|
+
module DataBuilderClassMethods
|
|
265
|
+
#
|
|
266
|
+
# @param serializer [Serega] Serializer instance carrying the plan
|
|
267
|
+
# @param serialized_result [Hash, Array, nil] Full serialized output (possibly wrapped under root key)
|
|
268
|
+
# @param serialization_opts [Hash] Serialization options used to determine the root key
|
|
269
|
+
#
|
|
270
|
+
# @return [Data, Array<Data>, nil] Serialization result as Data object(s)
|
|
271
|
+
#
|
|
272
|
+
def call(serializer, serialized_result, serialization_opts)
|
|
273
|
+
root_key = resolve_root_key(serializer, serialization_opts)
|
|
274
|
+
return super(serializer, serialized_result) unless root_key
|
|
275
|
+
|
|
276
|
+
root_data = super(serializer, serialized_result.fetch(root_key))
|
|
277
|
+
build_data_objects(serialized_result, root_key, root_data)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
private
|
|
281
|
+
|
|
282
|
+
def resolve_root_key(serializer, serialization_opts)
|
|
283
|
+
if serialization_opts.key?(:root)
|
|
284
|
+
serialization_opts[:root]
|
|
285
|
+
else
|
|
286
|
+
config_root = serializer.class.config.root
|
|
287
|
+
serialization_opts[:many] ? config_root.many : config_root.one
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def build_data_objects(serialized_result, root_key, root_data)
|
|
292
|
+
converted_hash =
|
|
293
|
+
serialized_result.to_h do |key, val|
|
|
294
|
+
value = (key == root_key) ? root_data : deep_hash_to_data(val)
|
|
295
|
+
[key, value]
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
Data.define(*converted_hash.keys).new(**converted_hash)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def deep_hash_to_data(value)
|
|
302
|
+
case value
|
|
303
|
+
when Hash
|
|
304
|
+
converted = value.transform_values { |nested| deep_hash_to_data(nested) }
|
|
305
|
+
Data.define(*value.keys).new(**converted)
|
|
306
|
+
when Array
|
|
307
|
+
value.map { |item| deep_hash_to_data(item) }
|
|
308
|
+
else
|
|
309
|
+
value
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
233
313
|
end
|
|
234
314
|
|
|
235
315
|
register_plugin(Root.plugin_name, Root)
|
data/lib/serega.rb
CHANGED
|
@@ -11,6 +11,10 @@ class Serega
|
|
|
11
11
|
# Frozen array
|
|
12
12
|
# @return [Array] frozen array
|
|
13
13
|
FROZEN_EMPTY_ARRAY = [].freeze
|
|
14
|
+
|
|
15
|
+
# Empty modifiers/serialization options (used when serializing with no opts provided)
|
|
16
|
+
FROZEN_EMPTY_OPTS = [FROZEN_EMPTY_HASH, nil].freeze
|
|
17
|
+
private_constant :FROZEN_EMPTY_OPTS
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
require_relative "serega/errors"
|
|
@@ -58,6 +62,7 @@ require_relative "serega/config"
|
|
|
58
62
|
require_relative "serega/object_serializer"
|
|
59
63
|
require_relative "serega/plan_point"
|
|
60
64
|
require_relative "serega/plan"
|
|
65
|
+
require_relative "serega/data_builder"
|
|
61
66
|
require_relative "serega/plugins"
|
|
62
67
|
|
|
63
68
|
class Serega
|
|
@@ -230,25 +235,45 @@ class Serega
|
|
|
230
235
|
# @return [Hash] Serialization result
|
|
231
236
|
#
|
|
232
237
|
def call(object, opts = nil)
|
|
233
|
-
opts
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if opts.empty?
|
|
237
|
-
modifiers_opts = FROZEN_EMPTY_HASH
|
|
238
|
-
serialize_opts = nil
|
|
239
|
-
else
|
|
240
|
-
opts.transform_keys!(&:to_sym)
|
|
241
|
-
serialize_opts = opts.except(*initiate_keys)
|
|
242
|
-
modifiers_opts = opts.slice(*initiate_keys)
|
|
243
|
-
end
|
|
244
|
-
|
|
238
|
+
opts = opts&.transform_keys(&:to_sym)
|
|
239
|
+
modifiers_opts = init_modifier_opts(opts)
|
|
240
|
+
serialize_opts = init_serialize_opts(opts)
|
|
245
241
|
new(modifiers_opts).to_h(object, serialize_opts)
|
|
246
242
|
end
|
|
247
243
|
|
|
244
|
+
#
|
|
245
|
+
# Serializes provided object to a tree of Ruby Data objects
|
|
246
|
+
#
|
|
247
|
+
# @param object [Object] Serialized object
|
|
248
|
+
# @param opts [Hash, nil] Serializer modifiers and other instantiating options
|
|
249
|
+
# @option opts [Array, Hash, String, Symbol] :only The only attributes to serialize
|
|
250
|
+
# @option opts [Array, Hash, String, Symbol] :except Attributes to hide
|
|
251
|
+
# @option opts [Array, Hash, String, Symbol] :with Attributes (usually hidden) to serialize additionally
|
|
252
|
+
# @option opts [Boolean] :validate Validates provided modifiers (Default is true)
|
|
253
|
+
# @option opts [Hash] :context Serialization context
|
|
254
|
+
# @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
|
|
255
|
+
#
|
|
256
|
+
# @return [Data, Array<Data>, nil] Serialization result as Data object(s)
|
|
257
|
+
#
|
|
258
|
+
def to_data(object, opts = nil)
|
|
259
|
+
opts = opts&.transform_keys(&:to_sym)
|
|
260
|
+
modifiers_opts = init_modifier_opts(opts)
|
|
261
|
+
serialize_opts = init_serialize_opts(opts)
|
|
262
|
+
new(modifiers_opts).to_data(object, serialize_opts)
|
|
263
|
+
end
|
|
264
|
+
|
|
248
265
|
alias_method :to_h, :call
|
|
249
266
|
|
|
250
267
|
private
|
|
251
268
|
|
|
269
|
+
def init_modifier_opts(opts)
|
|
270
|
+
(!opts || opts.empty?) ? FROZEN_EMPTY_HASH : opts.slice(*config.initiate_keys)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def init_serialize_opts(opts)
|
|
274
|
+
(!opts || opts.empty?) ? nil : opts.except(*config.initiate_keys)
|
|
275
|
+
end
|
|
276
|
+
|
|
252
277
|
# Patched in:
|
|
253
278
|
# - plugin :metadata (defines MetaAttribute and copies meta_attributes to subclasses)
|
|
254
279
|
# - plugin :presenter (defines Presenter)
|
|
@@ -266,6 +291,10 @@ class Serega
|
|
|
266
291
|
attribute_normalizer_class.serializer_class = subclass
|
|
267
292
|
subclass.const_set(:SeregaAttributeNormalizer, attribute_normalizer_class)
|
|
268
293
|
|
|
294
|
+
data_builder_class = Class.new(self::SeregaDataBuilder)
|
|
295
|
+
data_builder_class.serializer_class = subclass
|
|
296
|
+
subclass.const_set(:SeregaDataBuilder, data_builder_class)
|
|
297
|
+
|
|
269
298
|
plan_class = Class.new(self::SeregaPlan)
|
|
270
299
|
plan_class.serializer_class = subclass
|
|
271
300
|
subclass.const_set(:SeregaPlan, plan_class)
|
|
@@ -355,17 +384,14 @@ class Serega
|
|
|
355
384
|
# Serializes provided object to Hash
|
|
356
385
|
#
|
|
357
386
|
# @param object [Object] Serialized object
|
|
358
|
-
# @param opts [Hash, nil]
|
|
387
|
+
# @param opts [Hash, nil] Serializing options
|
|
359
388
|
# @option opts [Hash] :context Serialization context
|
|
360
389
|
# @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
|
|
361
390
|
#
|
|
362
391
|
# @return [Hash] Serialization result
|
|
363
392
|
#
|
|
364
393
|
def call(object, opts = nil)
|
|
365
|
-
opts =
|
|
366
|
-
self.class::CheckSerializeParams.new(opts).validate unless opts.empty?
|
|
367
|
-
|
|
368
|
-
opts[:context] ||= {}
|
|
394
|
+
opts = prepare_initial_serialization_opts(object, opts)
|
|
369
395
|
serialize(object, opts)
|
|
370
396
|
end
|
|
371
397
|
|
|
@@ -374,6 +400,24 @@ class Serega
|
|
|
374
400
|
call(object, opts)
|
|
375
401
|
end
|
|
376
402
|
|
|
403
|
+
#
|
|
404
|
+
# Serializes provided object to Data objects
|
|
405
|
+
# Patched in:
|
|
406
|
+
# - plugin :root (adds a data-object for a root level keys)
|
|
407
|
+
#
|
|
408
|
+
# @param object [Object] Serialized object
|
|
409
|
+
# @param opts [Hash, nil] Serializing options
|
|
410
|
+
# @option opts [Hash] :context Serialization context
|
|
411
|
+
# @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
|
|
412
|
+
#
|
|
413
|
+
# @return [Data] Serialization result
|
|
414
|
+
#
|
|
415
|
+
def to_data(object, opts = nil)
|
|
416
|
+
opts = prepare_initial_serialization_opts(object, opts)
|
|
417
|
+
serialized_data = serialize(object, opts)
|
|
418
|
+
self.class::SeregaDataBuilder.call(self, serialized_data)
|
|
419
|
+
end
|
|
420
|
+
|
|
377
421
|
# @return [Hash] merged preloads of all serialized attributes
|
|
378
422
|
def preloads
|
|
379
423
|
@preloads ||= SeregaUtils::PreloadsConstructor.call(plan)
|
|
@@ -404,15 +448,25 @@ class Serega
|
|
|
404
448
|
SeregaUtils::ToHash.call(value)
|
|
405
449
|
end
|
|
406
450
|
|
|
451
|
+
def prepare_initial_serialization_opts(object, opts)
|
|
452
|
+
opts = opts ? opts.transform_keys(&:to_sym) : {}
|
|
453
|
+
self.class::CheckSerializeParams.new(opts).validate unless opts.empty?
|
|
454
|
+
|
|
455
|
+
opts[:context] ||= {}
|
|
456
|
+
opts[:batch_loaders] = SeregaBatch::AttributeLoaders.new if plan.has_batch_points
|
|
457
|
+
opts[:many] = object.is_a?(Enumerable) unless opts.key?(:many)
|
|
458
|
+
opts[:plan] = plan
|
|
459
|
+
opts
|
|
460
|
+
end
|
|
461
|
+
|
|
407
462
|
# Patched in:
|
|
408
463
|
# - plugin :activerecord_preloads (loads defined :preloads to object)
|
|
409
464
|
# - plugin :root (wraps result `{ root => result }`)
|
|
410
465
|
# - plugin :context_metadata (adds context metadata to final result)
|
|
411
466
|
# - plugin :metadata (adds metadata to final result)
|
|
412
467
|
def serialize(object, opts)
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
batch_loaders&.load_all(opts[:context])
|
|
468
|
+
result = self.class::SeregaObjectSerializer.new(**opts).serialize(object)
|
|
469
|
+
opts[:batch_loaders]&.load_all(opts[:context])
|
|
416
470
|
result
|
|
417
471
|
end
|
|
418
472
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: serega
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.36.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrey Glushkov
|
|
@@ -37,6 +37,7 @@ files:
|
|
|
37
37
|
- lib/serega/batch/attribute_loaders.rb
|
|
38
38
|
- lib/serega/batch/loader.rb
|
|
39
39
|
- lib/serega/config.rb
|
|
40
|
+
- lib/serega/data_builder.rb
|
|
40
41
|
- lib/serega/errors.rb
|
|
41
42
|
- lib/serega/helpers/serializer_class_helper.rb
|
|
42
43
|
- lib/serega/object_serializer.rb
|
|
@@ -114,7 +115,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
114
115
|
requirements:
|
|
115
116
|
- - ">="
|
|
116
117
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: 2.
|
|
118
|
+
version: 3.2.0
|
|
118
119
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
120
|
requirements:
|
|
120
121
|
- - ">="
|