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.
- checksums.yaml +4 -4
- data/README.md +124 -1207
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +186 -131
- data/lib/serega/attribute_normalizer.rb +164 -345
- data/lib/serega/attribute_normalizers/batch.rb +45 -0
- data/lib/serega/attribute_normalizers/block.rb +81 -0
- data/lib/serega/attribute_normalizers/delegate.rb +34 -0
- data/lib/serega/attribute_normalizers/hash_access.rb +37 -0
- data/lib/serega/attribute_normalizers/preloads.rb +46 -0
- data/lib/serega/attribute_value_resolvers/batch.rb +5 -54
- data/lib/serega/attribute_value_resolvers/const.rb +2 -24
- data/lib/serega/attribute_value_resolvers/delegate.rb +17 -28
- data/lib/serega/attribute_value_resolvers/hash_access.rb +34 -52
- data/lib/serega/attribute_value_resolvers/keyword.rb +10 -25
- data/lib/serega/attribute_values.rb +46 -0
- data/lib/serega/batch_loader.rb +37 -0
- data/lib/serega/conditions.rb +56 -0
- data/lib/serega/config.rb +105 -144
- data/lib/serega/engine/pulls.rb +64 -0
- data/lib/serega/engine/run.rb +106 -0
- data/lib/serega/errors.rb +36 -8
- data/lib/serega/formatter.rb +98 -0
- data/lib/serega/plan/cache.rb +96 -0
- data/lib/serega/plan/per_ractor_cache.rb +23 -0
- data/lib/serega/plan.rb +101 -128
- data/lib/serega/plan_point.rb +44 -151
- data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +13 -80
- data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +8 -75
- data/lib/serega/plugins/context_metadata/context_metadata.rb +40 -123
- data/lib/serega/plugins/metadata/check_params.rb +80 -0
- data/lib/serega/plugins/metadata/meta_attribute.rb +40 -79
- data/lib/serega/plugins/metadata/metadata.rb +40 -168
- data/lib/serega/plugins/root/root.rb +91 -235
- data/lib/serega/plugins.rb +16 -49
- data/lib/serega/presenter.rb +61 -19
- data/lib/serega/result_builder.rb +68 -0
- data/lib/serega/result_code.rb +203 -0
- data/lib/serega/shareable.rb +52 -0
- data/lib/serega/source_group.rb +187 -0
- data/lib/serega/utils/collection_detector.rb +19 -13
- data/lib/serega/utils/deep_merge.rb +26 -0
- data/lib/serega/utils/enum_deep_dup.rb +10 -16
- data/lib/serega/utils/enum_deep_freeze.rb +8 -14
- data/lib/serega/utils/method_signature.rb +6 -15
- data/lib/serega/utils/parse_string_modifiers.rb +63 -0
- data/lib/serega/utils/ractor_local.rb +36 -0
- data/lib/serega/utils/serialized_attribute_error.rb +60 -20
- data/lib/serega/utils/to_hash.rb +5 -26
- data/lib/serega/validations/attribute/check_block.rb +3 -15
- data/lib/serega/validations/attribute/check_name.rb +4 -15
- data/lib/serega/validations/attribute/check_opt_base_serializer.rb +3 -15
- data/lib/serega/validations/attribute/check_opt_batch.rb +26 -56
- data/lib/serega/validations/attribute/check_opt_condition.rb +55 -0
- data/lib/serega/validations/attribute/check_opt_delegate.rb +10 -63
- data/lib/serega/validations/attribute/check_opt_format.rb +36 -0
- data/lib/serega/validations/attribute/check_opt_hash_access.rb +11 -59
- data/lib/serega/validations/attribute/check_opt_hide.rb +3 -14
- data/lib/serega/validations/attribute/check_opt_many.rb +18 -20
- data/lib/serega/validations/attribute/check_opt_method.rb +3 -25
- data/lib/serega/validations/attribute/check_opt_preload.rb +3 -26
- data/lib/serega/validations/attribute/check_opt_serializer.rb +4 -18
- data/lib/serega/validations/attribute/check_opt_value.rb +3 -21
- data/lib/serega/validations/check_attribute_params.rb +92 -71
- data/lib/serega/validations/check_batch_loader_params.rb +43 -62
- data/lib/serega/validations/check_formatter.rb +80 -0
- data/lib/serega/validations/check_serialize_keywords.rb +23 -0
- data/lib/serega/validations/initiate/check_modifiers.rb +14 -20
- data/lib/serega/validations/utils/check_allowed_keys.rb +3 -13
- data/lib/serega/validations/utils/check_hash_access_value.rb +44 -0
- data/lib/serega/validations/utils/check_opt_is_bool.rb +3 -13
- data/lib/serega/validations/utils/check_opt_is_hash.rb +3 -13
- data/lib/serega/validations/utils/check_opt_is_string_or_symbol.rb +3 -13
- data/lib/serega/version.rb +1 -4
- data/lib/serega.rb +459 -388
- data/sig/serega/plugins.rbs +49 -0
- data/sig/serega.rbs +142 -0
- metadata +42 -38
- data/lib/serega/data_builder.rb +0 -54
- data/lib/serega/engine/level.rb +0 -59
- data/lib/serega/engine/level_queue.rb +0 -56
- data/lib/serega/engine/loader.rb +0 -70
- data/lib/serega/helpers/serializer_class_helper.rb +0 -19
- data/lib/serega/object_serializer.rb +0 -125
- data/lib/serega/plugins/camel_case/camel_case.rb +0 -213
- data/lib/serega/plugins/depth_limit/depth_limit.rb +0 -206
- data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +0 -69
- data/lib/serega/plugins/explicit_many_option/validations/check_opt_many.rb +0 -38
- data/lib/serega/plugins/formatters/formatters.rb +0 -322
- data/lib/serega/plugins/if/if.rb +0 -287
- data/lib/serega/plugins/if/validations/check_opt_if.rb +0 -72
- data/lib/serega/plugins/if/validations/check_opt_if_value.rb +0 -77
- data/lib/serega/plugins/if/validations/check_opt_unless.rb +0 -72
- data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +0 -77
- data/lib/serega/plugins/metadata/validations/check_block.rb +0 -47
- data/lib/serega/plugins/metadata/validations/check_opt_const.rb +0 -39
- data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +0 -35
- data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +0 -35
- data/lib/serega/plugins/metadata/validations/check_opt_value.rb +0 -61
- data/lib/serega/plugins/metadata/validations/check_opts.rb +0 -59
- data/lib/serega/plugins/metadata/validations/check_path.rb +0 -50
- data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +0 -111
- data/lib/serega/plugins/string_modifiers/string_modifiers.rb +0 -63
- data/lib/serega/utils/symbol_name.rb +0 -39
- data/lib/serega/validations/attribute/check_opt_const.rb +0 -40
- data/lib/serega/validations/check_initiate_params.rb +0 -57
- data/lib/serega/validations/check_serialize_params.rb +0 -54
|
@@ -2,164 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
class Serega
|
|
4
4
|
module SeregaPlugins
|
|
5
|
+
# `plugin :metadata` adds metadata defined with
|
|
6
|
+
# Serega::SeregaPlugins::Metadata::ClassMethods#meta_attribute. Load
|
|
7
|
+
# `plugin :root` first.
|
|
5
8
|
#
|
|
6
|
-
#
|
|
9
|
+
# ```ruby
|
|
10
|
+
# plugin :root
|
|
11
|
+
# plugin :metadata
|
|
7
12
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# Adds metadata to the serialized response via the class-level
|
|
11
|
-
# `meta_attribute` method, which accepts:
|
|
12
|
-
#
|
|
13
|
-
# - `*path` [Array<Symbol>] - nested hash keys
|
|
14
|
-
# - `**options` [Hash]
|
|
15
|
-
# - `:const` - a constant metadata value
|
|
16
|
-
# - `:value` - a `#callable` producing the metadata value
|
|
17
|
-
# - `:hide_nil` - hide the key if the value is nil (`false` by default)
|
|
18
|
-
# - `:hide_empty` - hide the key if the value is nil or empty (`false` by default)
|
|
19
|
-
# - `&block` - computes the value for this meta attribute
|
|
20
|
-
#
|
|
21
|
-
# @example
|
|
22
|
-
# class AppSerializer < Serega
|
|
23
|
-
# plugin :root
|
|
24
|
-
# plugin :metadata
|
|
25
|
-
#
|
|
26
|
-
# meta_attribute(:version, const: '1.2.3')
|
|
27
|
-
# meta_attribute(:ab_tests, :names, value: ABTests.new.method(:names))
|
|
28
|
-
# meta_attribute(:meta, :paging, hide_nil: true) do |records, ctx|
|
|
29
|
-
# next unless records.respond_to?(:total_count)
|
|
30
|
-
#
|
|
31
|
-
# { page: records.page, per_page: records.per_page, total_count: records.total_count }
|
|
32
|
-
# end
|
|
33
|
-
# end
|
|
34
|
-
#
|
|
35
|
-
# AppSerializer.to_h(nil) # => {:data=>nil, :version=>"1.2.3", :ab_tests=>{:names=> ... }}
|
|
13
|
+
# meta_attribute :version, const: "1.2.3"
|
|
14
|
+
# ```
|
|
36
15
|
#
|
|
16
|
+
# See [Metadata](https://github.com/andreyruby/serega/blob/master/docs/root-and-metadata.md#metadata).
|
|
37
17
|
module Metadata
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
def self.plugin_name
|
|
41
|
-
:metadata
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Checks requirements and loads additional plugins
|
|
45
|
-
#
|
|
46
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
|
47
|
-
# @param _opts [Hash] Plugin options
|
|
48
|
-
#
|
|
49
|
-
# @return [void]
|
|
50
|
-
#
|
|
51
|
-
# @private
|
|
52
|
-
def self.before_load_plugin(serializer_class, **_opts)
|
|
18
|
+
# Adds the plugin to the serializer
|
|
19
|
+
def self.apply(serializer_class, **_opts) # :nodoc:
|
|
53
20
|
unless serializer_class.plugin_used?(:root)
|
|
54
|
-
raise SeregaError, "Plugin
|
|
21
|
+
raise SeregaError, "Plugin :metadata must be loaded after the :root plugin. Please load the :root plugin first"
|
|
55
22
|
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
#
|
|
59
|
-
# Applies plugin code to specific serializer
|
|
60
|
-
#
|
|
61
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
|
62
|
-
# @param _opts [Hash] Plugin options
|
|
63
|
-
#
|
|
64
|
-
# @return [void]
|
|
65
|
-
#
|
|
66
|
-
# @private
|
|
67
|
-
def self.load_plugin(serializer_class, **_opts)
|
|
68
|
-
serializer_class.extend(ClassMethods)
|
|
69
|
-
serializer_class.include(InstanceMethods)
|
|
70
|
-
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
|
|
71
23
|
|
|
72
24
|
require_relative "meta_attribute"
|
|
73
|
-
require_relative "
|
|
74
|
-
require_relative "validations/check_opt_const"
|
|
75
|
-
require_relative "validations/check_opt_hide_nil"
|
|
76
|
-
require_relative "validations/check_opt_hide_empty"
|
|
77
|
-
require_relative "validations/check_opt_value"
|
|
78
|
-
require_relative "validations/check_opts"
|
|
79
|
-
require_relative "validations/check_path"
|
|
80
|
-
|
|
81
|
-
meta_attribute_class = Class.new(MetaAttribute)
|
|
82
|
-
meta_attribute_class.serializer_class = serializer_class
|
|
83
|
-
serializer_class.const_set(:MetaAttribute, meta_attribute_class)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
# Adds config options and runs other callbacks after plugin was loaded
|
|
88
|
-
#
|
|
89
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
|
90
|
-
# @param _opts [Hash] Plugin options
|
|
91
|
-
#
|
|
92
|
-
# @return [void]
|
|
93
|
-
#
|
|
94
|
-
# @private
|
|
95
|
-
def self.after_load_plugin(serializer_class, **_opts)
|
|
96
|
-
serializer_class.config.opts[:metadata] = {attribute_keys: %i[const hide_nil hide_empty value]}
|
|
97
|
-
end
|
|
25
|
+
require_relative "check_params"
|
|
98
26
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class MetadataConfig
|
|
103
|
-
# @return [Hash] metadata options
|
|
104
|
-
attr_reader :opts
|
|
105
|
-
|
|
106
|
-
#
|
|
107
|
-
# Initializes context_metadata config object
|
|
108
|
-
#
|
|
109
|
-
# @param opts [Hash] options
|
|
110
|
-
#
|
|
111
|
-
# @return [Serega::SeregaPlugins::Metadata::MetadataConfig]
|
|
112
|
-
#
|
|
113
|
-
def initialize(opts)
|
|
114
|
-
@opts = opts
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
#
|
|
118
|
-
# Returns allowed metadata attribute keys
|
|
119
|
-
#
|
|
120
|
-
def attribute_keys
|
|
121
|
-
opts.fetch(:attribute_keys)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
#
|
|
126
|
-
# Config class additional/patched instance methods
|
|
127
|
-
#
|
|
128
|
-
# @see Serega::SeregaConfig
|
|
129
|
-
#
|
|
130
|
-
module ConfigInstanceMethods
|
|
131
|
-
# @return [Serega::SeregaPlugins::Metadata::MetadataConfig] metadata config
|
|
132
|
-
def metadata
|
|
133
|
-
@metadata ||= MetadataConfig.new(opts.fetch(:metadata))
|
|
134
|
-
end
|
|
27
|
+
serializer_class.extend(ClassMethods)
|
|
28
|
+
serializer_class.include(InstanceMethods)
|
|
29
|
+
serializer_class.instance_variable_set(:@meta_attributes, {})
|
|
135
30
|
end
|
|
136
31
|
|
|
137
|
-
#
|
|
138
|
-
# Serega class additional/patched class methods
|
|
139
|
-
#
|
|
140
|
-
# @see Serega::SeregaConfig
|
|
141
|
-
#
|
|
32
|
+
# Class methods the plugin adds to serializers
|
|
142
33
|
module ClassMethods
|
|
143
|
-
#
|
|
144
34
|
# List of added metadata attributes
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
#
|
|
148
|
-
def meta_attributes
|
|
149
|
-
@meta_attributes ||= {}
|
|
35
|
+
def meta_attributes # :nodoc:
|
|
36
|
+
@meta_attributes
|
|
150
37
|
end
|
|
151
38
|
|
|
39
|
+
# Adds metadata under a path of keys. The value is `const:`, a `value:`
|
|
40
|
+
# callable or a block, which receive the serialized objects and the
|
|
41
|
+
# context. `hide_nil: true` skips `nil` values, `hide_empty: true` skips
|
|
42
|
+
# `nil` and empty values.
|
|
152
43
|
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
# @param opts [Hash] Metadata attribute options
|
|
157
|
-
# @param block [Proc] Block to fetch metadata attribute value
|
|
158
|
-
#
|
|
159
|
-
# @return [Serega::SeregaPlugins::Metadata::MetaAttribute] Added metadata attribute
|
|
44
|
+
# ```ruby
|
|
45
|
+
# meta_attribute :meta, :paging, hide_nil: true do |records, ctx|
|
|
46
|
+
# next unless records.respond_to?(:total_count)
|
|
160
47
|
#
|
|
48
|
+
# {page: records.current_page, total_count: records.total_count}
|
|
49
|
+
# end
|
|
50
|
+
# ```
|
|
161
51
|
def meta_attribute(*path, **opts, &block)
|
|
162
|
-
|
|
52
|
+
check_unlocked
|
|
53
|
+
attribute = MetaAttribute.new(self, path: path, opts: opts, block: block)
|
|
163
54
|
meta_attributes[attribute.name] = attribute
|
|
164
55
|
end
|
|
165
56
|
|
|
@@ -168,9 +59,7 @@ class Serega
|
|
|
168
59
|
def inherited(subclass)
|
|
169
60
|
super
|
|
170
61
|
|
|
171
|
-
|
|
172
|
-
meta_attribute_class.serializer_class = subclass
|
|
173
|
-
subclass.const_set(:MetaAttribute, meta_attribute_class)
|
|
62
|
+
subclass.instance_variable_set(:@meta_attributes, {})
|
|
174
63
|
|
|
175
64
|
# Assign same metadata attributes
|
|
176
65
|
meta_attributes.each_value do |attr|
|
|
@@ -179,23 +68,18 @@ class Serega
|
|
|
179
68
|
end
|
|
180
69
|
end
|
|
181
70
|
|
|
182
|
-
#
|
|
183
71
|
# Serega additional/patched instance methods
|
|
184
|
-
#
|
|
185
|
-
# @see Serega
|
|
186
|
-
#
|
|
187
|
-
# @private
|
|
188
|
-
module InstanceMethods
|
|
72
|
+
module InstanceMethods # :nodoc:
|
|
189
73
|
private
|
|
190
74
|
|
|
191
|
-
def serialize(object, opts)
|
|
75
|
+
def serialize(object, opts, context, many, mode)
|
|
192
76
|
result = super
|
|
193
77
|
return result unless result.is_a?(Hash) # return earlier if not a hash, so no root was added
|
|
194
78
|
|
|
195
|
-
root = build_root(
|
|
79
|
+
root = build_root(opts, many)
|
|
196
80
|
return result unless root # return earlier when no root
|
|
197
81
|
|
|
198
|
-
add_metadata(object,
|
|
82
|
+
add_metadata(object, context, result)
|
|
199
83
|
result
|
|
200
84
|
end
|
|
201
85
|
|
|
@@ -204,7 +88,7 @@ class Serega
|
|
|
204
88
|
metadata = meta_attribute_value(object, context, meta_attribute)
|
|
205
89
|
next unless metadata
|
|
206
90
|
|
|
207
|
-
|
|
91
|
+
SeregaUtils::DeepMerge.call(hash, metadata)
|
|
208
92
|
rescue => error
|
|
209
93
|
raise error.exception(<<~MESSAGE.strip)
|
|
210
94
|
#{error.message}
|
|
@@ -217,23 +101,11 @@ class Serega
|
|
|
217
101
|
value = meta_attribute.value(object, context)
|
|
218
102
|
return if meta_attribute.hide?(value)
|
|
219
103
|
|
|
220
|
-
# Example:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def deep_merge_metadata(hash, metadata)
|
|
226
|
-
hash.merge!(metadata) do |_key, this_val, other_val|
|
|
227
|
-
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
|
228
|
-
deep_merge_metadata(this_val, other_val)
|
|
229
|
-
else
|
|
230
|
-
other_val
|
|
231
|
-
end
|
|
232
|
-
end
|
|
104
|
+
# Example: path [:foo, :bar] and value :bazz give { foo: { bar: :bazz } }
|
|
105
|
+
meta_attribute.path.reverse_each { |key| value = {key => value} }
|
|
106
|
+
value
|
|
233
107
|
end
|
|
234
108
|
end
|
|
235
109
|
end
|
|
236
|
-
|
|
237
|
-
register_plugin(Metadata.plugin_name, Metadata)
|
|
238
110
|
end
|
|
239
111
|
end
|
|
@@ -2,110 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
class Serega
|
|
4
4
|
module SeregaPlugins
|
|
5
|
+
# `plugin :root` wraps results in a root key, `:data` by default
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
+
# ```ruby
|
|
8
|
+
# plugin :root # {data: ...}
|
|
9
|
+
# plugin :root, root: :users # one key for objects and lists
|
|
10
|
+
# plugin :root, root_one: :user, root_many: :users # separate keys
|
|
7
11
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# - :root - specifies root for all responses
|
|
12
|
-
# - :root_one - specifies root for single object serialization only
|
|
13
|
-
# - :root_many - specifies root for multiple objects serialization only
|
|
14
|
-
#
|
|
15
|
-
# Adds additional config options:
|
|
16
|
-
# - config.root.one
|
|
17
|
-
# - config.root.many
|
|
18
|
-
# - config.root.one=
|
|
19
|
-
# - config.root.many=
|
|
20
|
-
#
|
|
21
|
-
# Default root is `:data`.
|
|
22
|
-
#
|
|
23
|
-
# Root can also be changed per serialization, or removed entirely by
|
|
24
|
-
# providing `root: nil` (per serialization it can still be added back).
|
|
25
|
-
#
|
|
26
|
-
# @example Define plugin
|
|
27
|
-
# class UserSerializer < Serega
|
|
28
|
-
# plugin :root # default root is :data
|
|
29
|
-
# end
|
|
30
|
-
#
|
|
31
|
-
# class UserSerializer < Serega
|
|
32
|
-
# plugin :root, root: :users
|
|
33
|
-
# end
|
|
34
|
-
#
|
|
35
|
-
# class UserSerializer < Serega
|
|
36
|
-
# plugin :root, root_one: :user, root_many: :people
|
|
37
|
-
# end
|
|
38
|
-
#
|
|
39
|
-
# class UserSerializer < Serega
|
|
40
|
-
# plugin :root, root: nil # no root by default
|
|
41
|
-
# end
|
|
42
|
-
#
|
|
43
|
-
# @example Change root per serialization:
|
|
44
|
-
# class UserSerializer < Serega
|
|
45
|
-
# plugin :root
|
|
46
|
-
# end
|
|
47
|
-
#
|
|
48
|
-
# UserSerializer.to_h(nil) # => {:data=>nil}
|
|
49
|
-
# UserSerializer.to_h(nil, root: :user) # => {:user=>nil}
|
|
50
|
-
# UserSerializer.to_h(nil, root: nil) # => nil
|
|
12
|
+
# UserSerializer.to_h(user, root: :user) # => {user: {username: "batman"}}
|
|
13
|
+
# UserSerializer.to_h(user, root: nil) # => {username: "batman"}
|
|
14
|
+
# ```
|
|
51
15
|
#
|
|
16
|
+
# See [Root key](https://github.com/andreyruby/serega/blob/master/docs/root-and-metadata.md#root-key).
|
|
52
17
|
module Root
|
|
53
|
-
#
|
|
54
|
-
ROOT_DEFAULT = :data
|
|
18
|
+
# Default response root key
|
|
19
|
+
ROOT_DEFAULT = :data # :nodoc:
|
|
55
20
|
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
:root
|
|
60
|
-
end
|
|
21
|
+
# Adds the plugin to the serializer
|
|
22
|
+
def self.apply(serializer_class, **opts) # :nodoc:
|
|
23
|
+
SeregaValidations::Utils::CheckAllowedKeys.call(opts, %i[root root_one root_many], "plugin :root")
|
|
61
24
|
|
|
62
|
-
# Checks requirements to load plugin
|
|
63
|
-
#
|
|
64
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
|
65
|
-
# @param opts [Hash] plugin options
|
|
66
|
-
#
|
|
67
|
-
# @return [void]
|
|
68
|
-
#
|
|
69
|
-
# @private
|
|
70
|
-
def self.before_load_plugin(serializer_class, **opts)
|
|
71
|
-
allowed_keys = %i[root root_one root_many]
|
|
72
|
-
opts.each_key do |key|
|
|
73
|
-
next if allowed_keys.include?(key)
|
|
74
|
-
|
|
75
|
-
raise SeregaError,
|
|
76
|
-
"Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
|
|
77
|
-
" - :root [String, Symbol, nil] Specifies common root keyword used when serializing one or multiple objects\n" \
|
|
78
|
-
" - :root_one [String, Symbol, nil] Specifies root keyword used when serializing one object\n" \
|
|
79
|
-
" - :root_many [String, Symbol, nil] Specifies root keyword used when serializing multiple objects" \
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
#
|
|
84
|
-
# Applies plugin code to specific serializer
|
|
85
|
-
#
|
|
86
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
|
87
|
-
# @param _opts [Hash] Plugin options
|
|
88
|
-
#
|
|
89
|
-
# @return [void]
|
|
90
|
-
#
|
|
91
|
-
# @private
|
|
92
|
-
def self.load_plugin(serializer_class, **_opts)
|
|
93
25
|
serializer_class.extend(ClassMethods)
|
|
94
26
|
serializer_class.include(InstanceMethods)
|
|
95
27
|
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
|
|
96
|
-
serializer_class::SeregaDataBuilder.extend(DataBuilderClassMethods)
|
|
97
|
-
end
|
|
98
28
|
|
|
99
|
-
#
|
|
100
|
-
# Adds config options and runs other callbacks after plugin was loaded
|
|
101
|
-
#
|
|
102
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
|
103
|
-
# @param opts [Hash] Plugin options
|
|
104
|
-
#
|
|
105
|
-
# @return [void]
|
|
106
|
-
#
|
|
107
|
-
# @private
|
|
108
|
-
def self.after_load_plugin(serializer_class, **opts)
|
|
109
29
|
config = serializer_class.config
|
|
110
30
|
default = opts.fetch(:root, ROOT_DEFAULT)
|
|
111
31
|
one = opts.fetch(:root_one, default)
|
|
@@ -116,21 +36,37 @@ class Serega
|
|
|
116
36
|
config.serialize_keys << :root
|
|
117
37
|
end
|
|
118
38
|
|
|
119
|
-
#
|
|
120
|
-
# Serega additional/patched class methods
|
|
121
|
-
#
|
|
122
|
-
# @see Serega
|
|
123
|
-
#
|
|
39
|
+
# Class methods the plugin adds to serializers
|
|
124
40
|
module ClassMethods
|
|
41
|
+
def call(object, only: nil, with: nil, except: nil, check_initiate_params: nil, context: nil, many: nil, **opts) # :nodoc:
|
|
42
|
+
new(only: only, with: with, except: except, check_initiate_params: check_initiate_params)
|
|
43
|
+
.to_h(object, context: context, many: many, **opts)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_data(object, only: nil, with: nil, except: nil, check_initiate_params: nil, context: nil, many: nil, **opts) # :nodoc:
|
|
47
|
+
new(only: only, with: with, except: except, check_initiate_params: check_initiate_params)
|
|
48
|
+
.to_data(object, context: context, many: many, **opts)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_struct(object, only: nil, with: nil, except: nil, check_initiate_params: nil, context: nil, many: nil, **opts) # :nodoc:
|
|
52
|
+
new(only: only, with: with, except: except, check_initiate_params: check_initiate_params)
|
|
53
|
+
.to_struct(object, context: context, many: many, **opts)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def to_json(object, only: nil, with: nil, except: nil, check_initiate_params: nil, context: nil, many: nil, **opts) # :nodoc:
|
|
57
|
+
new(only: only, with: with, except: except, check_initiate_params: check_initiate_params)
|
|
58
|
+
.to_json(object, context: context, many: many, **opts)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
alias_method :to_h, :call # :nodoc:
|
|
62
|
+
|
|
63
|
+
# Sets the root keys: one key for objects and lists, or separate `one:`
|
|
64
|
+
# and `many:` keys
|
|
125
65
|
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
# @param many [String, Symbol, nil] Specifies root keyword used when serializing multiple objects
|
|
131
|
-
#
|
|
132
|
-
# @return [Hash] Configured root names
|
|
133
|
-
#
|
|
66
|
+
# ```ruby
|
|
67
|
+
# root :users
|
|
68
|
+
# root one: :user, many: :users
|
|
69
|
+
# ```
|
|
134
70
|
def root(root = nil, one: nil, many: nil)
|
|
135
71
|
one ||= root
|
|
136
72
|
many ||= root
|
|
@@ -139,185 +75,105 @@ class Serega
|
|
|
139
75
|
end
|
|
140
76
|
end
|
|
141
77
|
|
|
142
|
-
# Root config
|
|
78
|
+
# Root keys, `config.root`
|
|
143
79
|
class RootConfig
|
|
144
|
-
attr_reader :opts
|
|
80
|
+
attr_reader :opts # :nodoc:
|
|
145
81
|
|
|
146
|
-
#
|
|
147
82
|
# Initializes RootConfig object
|
|
148
|
-
#
|
|
149
|
-
# @param opts [Hash] root options
|
|
150
|
-
# @option opts [Symbol, String, nil] :one root for single-object serialization
|
|
151
|
-
# @option opts [Symbol, String, nil] :many root for many-objects serialization
|
|
152
|
-
#
|
|
153
|
-
# @return [SeregaPlugins::Root::RootConfig] RootConfig object
|
|
154
|
-
#
|
|
155
|
-
def initialize(opts)
|
|
83
|
+
def initialize(opts) # :nodoc:
|
|
156
84
|
@opts = opts
|
|
157
85
|
end
|
|
158
86
|
|
|
159
|
-
#
|
|
87
|
+
# Root key of one object
|
|
160
88
|
def one
|
|
161
89
|
opts.fetch(:one)
|
|
162
90
|
end
|
|
163
91
|
|
|
164
|
-
#
|
|
92
|
+
# Root key of a list
|
|
165
93
|
def many
|
|
166
94
|
opts.fetch(:many)
|
|
167
95
|
end
|
|
168
96
|
|
|
169
|
-
#
|
|
170
|
-
# Set root for single-object serialization
|
|
171
|
-
#
|
|
172
|
-
# @param value [Symbol, String, nil] root key
|
|
173
|
-
#
|
|
174
|
-
# @return [Symbol, String, nil] root key for single-object serialization
|
|
97
|
+
# Sets #one
|
|
175
98
|
def one=(value)
|
|
176
99
|
opts[:one] = value
|
|
177
100
|
end
|
|
178
101
|
|
|
179
|
-
#
|
|
180
|
-
# Set root for multiple-object serialization
|
|
181
|
-
#
|
|
182
|
-
# @param value [Symbol, String, nil] root key
|
|
183
|
-
#
|
|
184
|
-
# @return [Symbol, String, nil] root key for multiple-object serialization
|
|
102
|
+
# Sets #many
|
|
185
103
|
def many=(value)
|
|
186
104
|
opts[:many] = value
|
|
187
105
|
end
|
|
188
106
|
end
|
|
189
107
|
|
|
190
|
-
#
|
|
191
|
-
# Serega::SeregaConfig additional/patched class methods
|
|
192
|
-
#
|
|
193
|
-
# @see Serega::SeregaConfig
|
|
194
|
-
#
|
|
108
|
+
# Config methods the plugin adds to Serega::SeregaConfig
|
|
195
109
|
module ConfigInstanceMethods
|
|
196
|
-
#
|
|
110
|
+
# Root keys
|
|
197
111
|
def root
|
|
198
|
-
@root ||= RootConfig.new(opts.fetch(:root))
|
|
112
|
+
@plugin_configs[:root] ||= RootConfig.new(opts.fetch(:root))
|
|
199
113
|
end
|
|
200
114
|
|
|
201
|
-
#
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
#
|
|
115
|
+
# Builds the root config before freezing
|
|
116
|
+
def freeze # :nodoc:
|
|
117
|
+
root
|
|
118
|
+
super
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Sets the root keys from a Hash with `:one` and `:many`
|
|
208
122
|
def root=(value)
|
|
209
123
|
root.one = value.fetch(:one)
|
|
210
124
|
root.many = value.fetch(:many)
|
|
211
125
|
end
|
|
212
126
|
end
|
|
213
127
|
|
|
214
|
-
#
|
|
215
128
|
# Serega additional/patched instance methods
|
|
216
129
|
#
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
module InstanceMethods
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
# When a root key is configured, the result is wrapped in an outer Data object
|
|
224
|
-
# whose members include the root key plus any metadata keys.
|
|
225
|
-
#
|
|
226
|
-
# @param object [Object] Serialized object
|
|
227
|
-
# @param opts [Hash, nil] Serializing options (`:root`, `:many`, `:context`, etc.)
|
|
228
|
-
#
|
|
229
|
-
# @return [Data, Array<Data>, nil] Serialization result as Data object(s)
|
|
230
|
-
#
|
|
231
|
-
def to_data(object, opts = nil)
|
|
232
|
-
opts = normalize_serialization_opts(opts)
|
|
233
|
-
object = prepare_objects(object, opts[:context])
|
|
234
|
-
opts = prepare_initial_serialization_opts(object, opts)
|
|
235
|
-
serialized_data = serialize(object, opts)
|
|
236
|
-
self.class::SeregaDataBuilder.call(self, serialized_data, opts)
|
|
130
|
+
# The serialization methods take `**opts`, see ClassMethods. The
|
|
131
|
+
# options reach `#serialize` as its `opts`, after #checked_opts
|
|
132
|
+
# validates them.
|
|
133
|
+
module InstanceMethods # :nodoc:
|
|
134
|
+
def call(object, context: nil, many: nil, **opts)
|
|
135
|
+
serialize_to(:hash, object, context, many, checked_opts(opts))
|
|
237
136
|
end
|
|
238
137
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def serialize(object, opts)
|
|
242
|
-
result = super
|
|
243
|
-
root = build_root(object, opts)
|
|
244
|
-
result = {root => result} if root
|
|
245
|
-
result
|
|
138
|
+
def to_data(object, context: nil, many: nil, **opts)
|
|
139
|
+
serialize_to(:data, object, context, many, checked_opts(opts))
|
|
246
140
|
end
|
|
247
141
|
|
|
248
|
-
def
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
root = self.class.config.root
|
|
252
|
-
(opts.fetch(:many) { SeregaUtils::CollectionDetector.call(object) }) ? root.many : root.one
|
|
142
|
+
def to_struct(object, context: nil, many: nil, **opts)
|
|
143
|
+
serialize_to(:struct, object, context, many, checked_opts(opts))
|
|
253
144
|
end
|
|
254
|
-
end
|
|
255
145
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
#
|
|
259
|
-
# Overrides `call` to handle the root key: the serialized result is
|
|
260
|
-
# unwrapped from the root key, converted to a Data tree by the base
|
|
261
|
-
# implementation, and then re-wrapped together with any metadata keys
|
|
262
|
-
# in an outer Data object. Metadata values (arbitrary hashes/arrays)
|
|
263
|
-
# are recursively converted to Data objects via `deep_hash_to_data`.
|
|
264
|
-
#
|
|
265
|
-
# When the root key is `nil` (disabled), delegates directly to the base.
|
|
266
|
-
#
|
|
267
|
-
# @see Serega::SeregaDataBuilder
|
|
268
|
-
#
|
|
269
|
-
# @private
|
|
270
|
-
module DataBuilderClassMethods
|
|
271
|
-
#
|
|
272
|
-
# @param serializer [Serega] Serializer instance carrying the plan
|
|
273
|
-
# @param serialized_result [Hash, Array, nil] Full serialized output (possibly wrapped under root key)
|
|
274
|
-
# @param serialization_opts [Hash] Serialization options used to determine the root key
|
|
275
|
-
#
|
|
276
|
-
# @return [Data, Array<Data>, nil] Serialization result as Data object(s)
|
|
277
|
-
#
|
|
278
|
-
def call(serializer, serialized_result, serialization_opts)
|
|
279
|
-
root_key = resolve_root_key(serializer, serialization_opts)
|
|
280
|
-
return super(serializer, serialized_result) unless root_key
|
|
281
|
-
|
|
282
|
-
root_data = super(serializer, serialized_result.fetch(root_key))
|
|
283
|
-
build_data_objects(serialized_result, root_key, root_data)
|
|
146
|
+
def to_json(object, context: nil, many: nil, **opts)
|
|
147
|
+
JSON.generate(call(object, context: context, many: many, **opts))
|
|
284
148
|
end
|
|
285
149
|
|
|
150
|
+
alias_method :to_h, :call
|
|
151
|
+
|
|
286
152
|
private
|
|
287
153
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
config_root = serializer.class.config.root
|
|
293
|
-
serialization_opts[:many] ? config_root.many : config_root.one
|
|
294
|
-
end
|
|
295
|
-
end
|
|
154
|
+
# Patched in:
|
|
155
|
+
# - plugin :context_metadata (checks the context metadata is a Hash)
|
|
156
|
+
def checked_opts(opts)
|
|
157
|
+
return FROZEN_EMPTY_HASH if opts.empty?
|
|
296
158
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
value = (key == root_key) ? root_data : deep_hash_to_data(val)
|
|
301
|
-
[key, value]
|
|
302
|
-
end
|
|
159
|
+
SeregaValidations::Utils::CheckAllowedKeys.call(opts, self.class.config.serialize_keys, :serialize)
|
|
160
|
+
opts
|
|
161
|
+
end
|
|
303
162
|
|
|
304
|
-
|
|
163
|
+
def serialize(object, opts, context, many, mode)
|
|
164
|
+
result = super
|
|
165
|
+
root = build_root(opts, many)
|
|
166
|
+
result = {root => result} if root
|
|
167
|
+
result
|
|
305
168
|
end
|
|
306
169
|
|
|
307
|
-
def
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
when Array
|
|
313
|
-
value.map { |item| deep_hash_to_data(item) }
|
|
314
|
-
else
|
|
315
|
-
value
|
|
316
|
-
end
|
|
170
|
+
def build_root(opts, many)
|
|
171
|
+
return opts[:root] if opts.key?(:root)
|
|
172
|
+
|
|
173
|
+
root = self.class.config.root
|
|
174
|
+
many ? root.many : root.one
|
|
317
175
|
end
|
|
318
176
|
end
|
|
319
177
|
end
|
|
320
|
-
|
|
321
|
-
register_plugin(Root.plugin_name, Root)
|
|
322
178
|
end
|
|
323
179
|
end
|