serega 0.6.1 → 0.7.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/VERSION +1 -1
- data/lib/serega/attribute.rb +2 -2
- data/lib/serega/config.rb +23 -1
- data/lib/serega/errors.rb +8 -5
- data/lib/serega/helpers/serializer_class_helper.rb +5 -0
- data/lib/serega/json/adapter.rb +6 -0
- data/lib/serega/json/json.rb +20 -0
- data/lib/serega/json/oj.rb +20 -0
- data/lib/serega/map.rb +17 -0
- data/lib/serega/map_point.rb +36 -1
- data/lib/serega/object_serializer.rb +17 -4
- data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +66 -18
- data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +100 -40
- data/lib/serega/plugins/batch/batch.rb +136 -10
- data/lib/serega/plugins/batch/lib/loader.rb +33 -1
- data/lib/serega/plugins/batch/lib/loaders.rb +15 -2
- data/lib/serega/plugins/batch/lib/plugins_extensions.rb +23 -1
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_key.rb +12 -0
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +12 -0
- data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +12 -0
- data/lib/serega/plugins/context_metadata/context_metadata.rb +95 -13
- data/lib/serega/plugins/formatters/formatters.rb +94 -8
- data/lib/serega/plugins/hide_nil/hide_nil.rb +33 -7
- data/lib/serega/plugins/metadata/metadata.rb +108 -35
- data/lib/serega/plugins/metadata/validations/check_block.rb +3 -0
- data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +4 -1
- data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +4 -1
- data/lib/serega/plugins/metadata/validations/check_opts.rb +3 -0
- data/lib/serega/plugins/metadata/validations/check_path.rb +3 -0
- data/lib/serega/plugins/preloads/lib/enum_deep_freeze.rb +10 -1
- data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +13 -4
- data/lib/serega/plugins/preloads/lib/main_preload_path.rb +16 -3
- data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +4 -6
- data/lib/serega/plugins/preloads/preloads.rb +145 -12
- data/lib/serega/plugins/preloads/validations/check_opt_preload.rb +11 -0
- data/lib/serega/plugins/preloads/validations/check_opt_preload_path.rb +12 -0
- data/lib/serega/plugins/presenter/presenter.rb +20 -11
- data/lib/serega/plugins/root/root.rb +131 -19
- data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +42 -12
- data/lib/serega/plugins/string_modifiers/string_modifiers.rb +14 -0
- data/lib/serega/plugins.rb +7 -1
- data/lib/serega/utils/enum_deep_dup.rb +5 -0
- data/lib/serega/utils/to_hash.rb +21 -3
- data/lib/serega/validations/attribute/check_block.rb +7 -2
- data/lib/serega/validations/attribute/check_name.rb +6 -0
- data/lib/serega/validations/attribute/check_opt_const.rb +12 -9
- data/lib/serega/validations/attribute/check_opt_delegate.rb +13 -10
- data/lib/serega/validations/attribute/check_opt_hide.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_key.rb +12 -9
- data/lib/serega/validations/attribute/check_opt_many.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_serializer.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_value.rb +12 -9
- data/lib/serega/validations/check_attribute_params.rb +29 -1
- data/lib/serega/validations/check_initiate_params.rb +17 -0
- data/lib/serega/validations/check_serialize_params.rb +16 -0
- data/lib/serega/validations/initiate/check_modifiers.rb +15 -0
- data/lib/serega/validations/utils/check_allowed_keys.rb +14 -0
- data/lib/serega/validations/utils/check_opt_is_bool.rb +11 -0
- data/lib/serega/validations/utils/check_opt_is_hash.rb +11 -0
- data/lib/serega/validations/utils/check_opt_is_string_or_symbol.rb +11 -0
- data/lib/serega/version.rb +4 -0
- data/lib/serega.rb +83 -24
- metadata +2 -3
- data/lib/serega/serializer.rb +0 -32
@@ -2,23 +2,66 @@
|
|
2
2
|
|
3
3
|
class Serega
|
4
4
|
module SeregaPlugins
|
5
|
+
#
|
6
|
+
# Plugin :context_metadata
|
7
|
+
#
|
8
|
+
# Depends on: `:root` plugin, that must be loaded first
|
9
|
+
#
|
10
|
+
# Allows to specify metadata to be added to serialized response.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# class UserSerializer < Serega
|
14
|
+
# plugin :root, root: :data
|
15
|
+
# plugin :context_metadata, context_metadata_key: :meta
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# UserSerializer.to_h(nil, meta: { version: '1.0.1' })
|
19
|
+
# # => {:data=>nil, :version=>"1.0.1"}
|
20
|
+
#
|
5
21
|
module ContextMetadata
|
22
|
+
# Default context metadata option name
|
6
23
|
DEFAULT_CONTEXT_METADATA_KEY = :meta
|
7
24
|
|
25
|
+
# @return [Symbol] Plugin name
|
8
26
|
def self.plugin_name
|
9
27
|
:context_metadata
|
10
28
|
end
|
11
29
|
|
30
|
+
# Checks requirements and loads additional plugins
|
31
|
+
#
|
32
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
33
|
+
# @param opts [Hash] loaded plugins opts
|
34
|
+
#
|
35
|
+
# @return [void]
|
36
|
+
#
|
12
37
|
def self.before_load_plugin(serializer_class, **opts)
|
13
|
-
|
38
|
+
unless serializer_class.plugin_used?(:root)
|
39
|
+
raise SeregaError, "Please load :root plugin first so we can wrap serialization response into top-level hash to add metadata there"
|
40
|
+
end
|
14
41
|
end
|
15
42
|
|
43
|
+
#
|
44
|
+
# Applies plugin code to specific serializer
|
45
|
+
#
|
46
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
47
|
+
# @param _opts [Hash] Loaded plugins options
|
48
|
+
#
|
49
|
+
# @return [void]
|
50
|
+
#
|
16
51
|
def self.load_plugin(serializer_class, **_opts)
|
52
|
+
serializer_class.include(InstanceMethods)
|
17
53
|
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
|
18
|
-
serializer_class::SeregaSerializer.include(SeregaSerializerInstanceMethods)
|
19
54
|
serializer_class::CheckSerializeParams.include(CheckSerializeParamsInstanceMethods)
|
20
55
|
end
|
21
56
|
|
57
|
+
#
|
58
|
+
# Adds config options and runs other callbacks after plugin was loaded
|
59
|
+
#
|
60
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
61
|
+
# @param opts [Hash] loaded plugins opts
|
62
|
+
#
|
63
|
+
# @return [void]
|
64
|
+
#
|
22
65
|
def self.after_load_plugin(serializer_class, **opts)
|
23
66
|
config = serializer_class.config
|
24
67
|
meta_key = opts[:context_metadata_key] || DEFAULT_CONTEXT_METADATA_KEY
|
@@ -26,29 +69,58 @@ class Serega
|
|
26
69
|
config.serialize_keys << meta_key
|
27
70
|
end
|
28
71
|
|
72
|
+
#
|
73
|
+
# Config for `context_metadata` plugin
|
74
|
+
#
|
29
75
|
class ContextMetadataConfig
|
76
|
+
# @return [Hash] context_metadata options
|
30
77
|
attr_reader :opts
|
31
78
|
|
79
|
+
#
|
80
|
+
# Initializes context_metadata config object
|
81
|
+
#
|
82
|
+
# @param opts [Hash] options
|
83
|
+
#
|
84
|
+
# @return [Serega::SeregaPlugins::ContextMetadata::ContextMetadataConfig]
|
32
85
|
def initialize(opts)
|
33
86
|
@opts = opts
|
34
87
|
end
|
35
88
|
|
89
|
+
# Key that should be used to define metadata
|
36
90
|
def key
|
37
91
|
opts.fetch(:key)
|
38
92
|
end
|
39
93
|
|
40
|
-
|
41
|
-
|
94
|
+
# Sets key that should be used to define metadata
|
95
|
+
#
|
96
|
+
# @param new_key [Symbol] New key
|
97
|
+
#
|
98
|
+
# @return [Symbol] New key
|
99
|
+
def key=(new_key)
|
100
|
+
opts[:key] = new_key
|
42
101
|
end
|
43
102
|
end
|
44
103
|
|
104
|
+
#
|
105
|
+
# Config class additional/patched instance methods
|
106
|
+
#
|
107
|
+
# @see Serega::SeregaConfig
|
108
|
+
#
|
45
109
|
module ConfigInstanceMethods
|
110
|
+
# @return [Serega::SeregaPlugins::ContextMetadata::ContextMetadataConfig] context_metadata config
|
46
111
|
def context_metadata
|
47
112
|
@context_metadata ||= ContextMetadataConfig.new(opts.fetch(:context_metadata))
|
48
113
|
end
|
49
114
|
end
|
50
115
|
|
116
|
+
#
|
117
|
+
# CheckSerializeParams class additional/patched instance methods
|
118
|
+
#
|
119
|
+
# @see Serega::SeregaValidations::CheckSerializeParams
|
120
|
+
#
|
51
121
|
module CheckSerializeParamsInstanceMethods
|
122
|
+
private
|
123
|
+
|
52
124
|
def check_opts
|
53
125
|
super
|
54
126
|
|
@@ -57,17 +129,27 @@ class Serega
|
|
57
129
|
end
|
58
130
|
end
|
59
131
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
132
|
+
#
|
133
|
+
# Serega additional/patched instance methods
|
134
|
+
#
|
135
|
+
# @see Serega
|
136
|
+
#
|
137
|
+
module InstanceMethods
|
67
138
|
private
|
68
139
|
|
69
|
-
def
|
70
|
-
|
140
|
+
def serialize(object, opts)
|
141
|
+
result = super
|
142
|
+
return result unless result.is_a?(Hash) # return earlier if not a hash, so no root was added
|
143
|
+
|
144
|
+
root = build_root(object, opts)
|
145
|
+
return result unless root # return earlier when no root
|
146
|
+
|
147
|
+
add_context_metadata(result, opts)
|
148
|
+
result
|
149
|
+
end
|
150
|
+
|
151
|
+
def add_context_metadata(hash, opts)
|
152
|
+
context_metadata_key = self.class.config.context_metadata.key
|
71
153
|
return unless context_metadata_key
|
72
154
|
|
73
155
|
metadata = opts[context_metadata_key]
|
@@ -2,22 +2,82 @@
|
|
2
2
|
|
3
3
|
class Serega
|
4
4
|
module SeregaPlugins
|
5
|
+
#
|
6
|
+
# Plugin :formatters
|
7
|
+
#
|
8
|
+
# Allows to define value formatters one time and apply them on any attributes.
|
9
|
+
#
|
10
|
+
# Config option `config.formatters.add()` can be used to add formatters.
|
11
|
+
#
|
12
|
+
# Attribute option `:format` now can be used with name of formatter or with callable instance.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# class AppSerializer < Serega
|
16
|
+
# plugin :formatters, formatters: {
|
17
|
+
# iso8601: ->(value) { time.iso8601.round(6) },
|
18
|
+
# on_off: ->(value) { value ? 'ON' : 'OFF' },
|
19
|
+
# money: ->(value) { value.round(2) }
|
20
|
+
# }
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# class UserSerializer < Serega
|
24
|
+
# # Additionally we can add formatters via config in subclasses
|
25
|
+
# config.formatters.add(
|
26
|
+
# iso8601: ->(value) { time.iso8601.round(6) },
|
27
|
+
# on_off: ->(value) { value ? 'ON' : 'OFF' },
|
28
|
+
# money: ->(value) { value.round(2) }
|
29
|
+
# )
|
30
|
+
#
|
31
|
+
# # Using predefined formatter
|
32
|
+
# attribute :commission, format: :money
|
33
|
+
# attribute :is_logined, format: :on_off
|
34
|
+
# attribute :created_at, format: :iso8601
|
35
|
+
# attribute :updated_at, format: :iso8601
|
36
|
+
#
|
37
|
+
# # Using `callable` formatter
|
38
|
+
# attribute :score_percent, format: proc { |percent| "#{percent.round(2)}%" }
|
39
|
+
# end
|
40
|
+
#
|
5
41
|
module Formatters
|
42
|
+
# @return [Symbol] Plugin name
|
6
43
|
def self.plugin_name
|
7
44
|
:formatters
|
8
45
|
end
|
9
46
|
|
47
|
+
# Checks requirements and loads additional plugins
|
48
|
+
#
|
49
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
50
|
+
# @param opts [Hash] loaded plugins opts
|
51
|
+
#
|
52
|
+
# @return [void]
|
53
|
+
#
|
10
54
|
def self.before_load_plugin(serializer_class, **opts)
|
11
55
|
if serializer_class.plugin_used?(:batch)
|
12
56
|
raise SeregaError, "Plugin `formatters` must be loaded before `batch`"
|
13
57
|
end
|
14
58
|
end
|
15
59
|
|
60
|
+
#
|
61
|
+
# Applies plugin code to specific serializer
|
62
|
+
#
|
63
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
64
|
+
# @param _opts [Hash] Loaded plugins options
|
65
|
+
#
|
66
|
+
# @return [void]
|
67
|
+
#
|
16
68
|
def self.load_plugin(serializer_class, **_opts)
|
17
69
|
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
|
18
70
|
serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
|
19
71
|
end
|
20
72
|
|
73
|
+
#
|
74
|
+
# Adds config options and runs other callbacks after plugin was loaded
|
75
|
+
#
|
76
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
77
|
+
# @param opts [Hash] loaded plugins opts
|
78
|
+
#
|
79
|
+
# @return [void]
|
80
|
+
#
|
21
81
|
def self.after_load_plugin(serializer_class, **opts)
|
22
82
|
config = serializer_class.config
|
23
83
|
config.opts[:formatters] = {}
|
@@ -25,13 +85,26 @@ class Serega
|
|
25
85
|
config.attribute_keys << :format
|
26
86
|
end
|
27
87
|
|
88
|
+
# Formatters plugin config
|
28
89
|
class FormattersConfig
|
29
90
|
attr_reader :opts
|
30
91
|
|
92
|
+
#
|
93
|
+
# Initializes formatters config object
|
94
|
+
#
|
95
|
+
# @param opts [Hash] options
|
96
|
+
#
|
97
|
+
# @return FormattersConfig
|
31
98
|
def initialize(opts)
|
32
99
|
@opts = opts
|
33
100
|
end
|
34
101
|
|
102
|
+
# Adds new formatters
|
103
|
+
#
|
104
|
+
# @param formatters [Hash<Symbol, #call>] hash key is a formatter name and
|
105
|
+
# hash value is a callable instance to format value
|
106
|
+
#
|
107
|
+
# @return [void]
|
35
108
|
def add(formatters)
|
36
109
|
formatters.each_pair do |key, value|
|
37
110
|
opts[key] = value
|
@@ -39,27 +112,32 @@ class Serega
|
|
39
112
|
end
|
40
113
|
end
|
41
114
|
|
115
|
+
#
|
116
|
+
# Config class additional/patched instance methods
|
117
|
+
#
|
118
|
+
# @see Serega::SeregaConfig
|
119
|
+
#
|
42
120
|
module ConfigInstanceMethods
|
121
|
+
# @return [Serega::SeregaPlugins::Formatters::FormattersConfig] current formatters config
|
43
122
|
def formatters
|
44
123
|
@formatters ||= FormattersConfig.new(opts.fetch(:formatters))
|
45
124
|
end
|
46
125
|
end
|
47
126
|
|
127
|
+
#
|
128
|
+
# Attribute class additional/patched instance methods
|
129
|
+
#
|
130
|
+
# @see Serega::SeregaAttribute
|
131
|
+
#
|
48
132
|
module AttributeInstanceMethods
|
133
|
+
# @return [#call] callable formatter
|
49
134
|
def formatter
|
50
135
|
return @formatter if instance_variable_defined?(:@formatter)
|
51
136
|
|
52
137
|
@formatter = formatter_resolved
|
53
138
|
end
|
54
139
|
|
55
|
-
|
56
|
-
formatter = opts[:format]
|
57
|
-
return unless formatter
|
58
|
-
|
59
|
-
formatter = self.class.serializer_class.config.formatters.opts.fetch(formatter) if formatter.is_a?(Symbol)
|
60
|
-
formatter
|
61
|
-
end
|
62
|
-
|
140
|
+
# @return [#call] callable, that should be used to fetch attribute value
|
63
141
|
def value_block
|
64
142
|
return @value_block if instance_variable_defined?(:@value_block)
|
65
143
|
return @value_block = super unless formatter
|
@@ -77,6 +155,14 @@ class Serega
|
|
77
155
|
|
78
156
|
private
|
79
157
|
|
158
|
+
def formatter_resolved
|
159
|
+
formatter = opts[:format]
|
160
|
+
return unless formatter
|
161
|
+
|
162
|
+
formatter = self.class.serializer_class.config.formatters.opts.fetch(formatter) if formatter.is_a?(Symbol)
|
163
|
+
formatter
|
164
|
+
end
|
165
|
+
|
80
166
|
def formatted_block(formatter, original_block)
|
81
167
|
proc do |object, context|
|
82
168
|
value = original_block.call(object, context)
|
@@ -7,36 +7,54 @@ class Serega
|
|
7
7
|
# if value is nil
|
8
8
|
#
|
9
9
|
module HideNil
|
10
|
-
# @return [Symbol]
|
10
|
+
# @return [Symbol] Plugin name
|
11
11
|
def self.plugin_name
|
12
12
|
:hide_nil
|
13
13
|
end
|
14
14
|
|
15
15
|
#
|
16
|
-
#
|
16
|
+
# Applies plugin code to specific serializer
|
17
17
|
#
|
18
|
-
# @param serializer_class [Class]
|
19
|
-
# @param _opts [Hash]
|
18
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
19
|
+
# @param _opts [Hash] Loaded plugins options
|
20
20
|
#
|
21
21
|
# @return [void]
|
22
22
|
#
|
23
23
|
def self.load_plugin(serializer_class, **_opts)
|
24
|
-
serializer_class::SeregaAttribute.include(
|
24
|
+
serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
|
25
25
|
serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
|
26
26
|
serializer_class::SeregaObjectSerializer.include(SeregaObjectSerializerInstanceMethods)
|
27
27
|
end
|
28
28
|
|
29
|
+
#
|
30
|
+
# Adds config options and runs other callbacks after plugin was loaded
|
31
|
+
#
|
32
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
33
|
+
# @param opts [Hash] loaded plugins opts
|
34
|
+
#
|
35
|
+
# @return [void]
|
36
|
+
#
|
29
37
|
def self.after_load_plugin(serializer_class, **opts)
|
30
38
|
serializer_class.config.attribute_keys << :hide_nil
|
31
39
|
end
|
32
40
|
|
33
|
-
#
|
34
|
-
|
41
|
+
#
|
42
|
+
# Serega::SeregaAttribute additional/patched instance methods
|
43
|
+
#
|
44
|
+
# @see Serega::SeregaValidations::CheckAttributeParams
|
45
|
+
#
|
46
|
+
module AttributeInstanceMethods
|
47
|
+
# Check hide_nil is specified
|
35
48
|
def hide_nil?
|
36
49
|
!!opts[:hide_nil]
|
37
50
|
end
|
38
51
|
end
|
39
52
|
|
53
|
+
#
|
54
|
+
# Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
|
55
|
+
#
|
56
|
+
# @see Serega::SeregaValidations::CheckAttributeParams
|
57
|
+
#
|
40
58
|
module CheckAttributeParamsInstanceMethods
|
41
59
|
private
|
42
60
|
|
@@ -46,6 +64,9 @@ class Serega
|
|
46
64
|
end
|
47
65
|
end
|
48
66
|
|
67
|
+
#
|
68
|
+
# Validator class for :hide_nil attribute option
|
69
|
+
#
|
49
70
|
class CheckOptHideNil
|
50
71
|
#
|
51
72
|
# Checks attribute :hide_nil option
|
@@ -66,6 +87,11 @@ class Serega
|
|
66
87
|
end
|
67
88
|
end
|
68
89
|
|
90
|
+
#
|
91
|
+
# SeregaObjectSerializer additional/patched class methods
|
92
|
+
#
|
93
|
+
# @see Serega::SeregaObjectSerializer
|
94
|
+
#
|
69
95
|
module SeregaObjectSerializerInstanceMethods
|
70
96
|
private
|
71
97
|
|
@@ -2,25 +2,65 @@
|
|
2
2
|
|
3
3
|
class Serega
|
4
4
|
module SeregaPlugins
|
5
|
+
#
|
6
|
+
# Plugin `:metadata`
|
7
|
+
#
|
8
|
+
# Depends on: `:root` plugin, that must be loaded first
|
9
|
+
#
|
10
|
+
# Adds ability to describe metadata that must be added to serialized response
|
11
|
+
#
|
12
|
+
# Added class-level method `:meta_attribute`, to define metadata, it accepts:
|
13
|
+
# - *path [Array<Symbol>] - nested hash keys beginning from the root object.
|
14
|
+
# - **options [Hash] - defaults are `hide_nil: false, hide_empty: false`
|
15
|
+
# - &block [Proc] - describes value for current meta attribute
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# class AppSerializer < Serega
|
19
|
+
# plugin :root
|
20
|
+
# plugin :metadata
|
21
|
+
#
|
22
|
+
# meta_attribute(:version) { '1.2.3' }
|
23
|
+
# meta_attribute(:ab_tests, :names) { %i[foo bar] }
|
24
|
+
# meta_attribute(:meta, :paging, hide_nil: true) do |records, ctx|
|
25
|
+
# next unless records.respond_to?(:total_count)
|
26
|
+
#
|
27
|
+
# { page: records.page, per_page: records.per_page, total_count: records.total_count }
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# AppSerializer.to_h(nil) # => {:data=>nil, :version=>"1.2.3", :ab_tests=>{:names=>[:foo, :bar]}}
|
32
|
+
#
|
5
33
|
module Metadata
|
34
|
+
# @return [Symbol] Plugin name
|
6
35
|
def self.plugin_name
|
7
36
|
:metadata
|
8
37
|
end
|
9
38
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
39
|
+
# Checks requirements and loads additional plugins
|
40
|
+
#
|
41
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
42
|
+
# @param _opts [Hash] loaded plugins opts
|
43
|
+
#
|
44
|
+
# @return [void]
|
45
|
+
#
|
46
|
+
def self.before_load_plugin(serializer_class, **_opts)
|
47
|
+
unless serializer_class.plugin_used?(:root)
|
48
|
+
raise SeregaError, "Please load :root plugin first so we can wrap serialization response into top-level hash to add metadata there"
|
17
49
|
end
|
18
50
|
end
|
19
51
|
|
52
|
+
#
|
53
|
+
# Applies plugin code to specific serializer
|
54
|
+
#
|
55
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
56
|
+
# @param _opts [Hash] Loaded plugins options
|
57
|
+
#
|
58
|
+
# @return [void]
|
59
|
+
#
|
20
60
|
def self.load_plugin(serializer_class, **_opts)
|
21
61
|
serializer_class.extend(ClassMethods)
|
62
|
+
serializer_class.include(InstanceMethods)
|
22
63
|
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
|
23
|
-
serializer_class::SeregaSerializer.include(SeregaSerializerInstanceMethods)
|
24
64
|
|
25
65
|
require_relative "./meta_attribute"
|
26
66
|
require_relative "./validations/check_block"
|
@@ -34,28 +74,61 @@ class Serega
|
|
34
74
|
serializer_class.const_set(:MetaAttribute, meta_attribute_class)
|
35
75
|
end
|
36
76
|
|
77
|
+
#
|
78
|
+
# Adds config options and runs other callbacks after plugin was loaded
|
79
|
+
#
|
80
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
81
|
+
# @param _opts [Hash] loaded plugins opts
|
82
|
+
#
|
83
|
+
# @return [void]
|
84
|
+
#
|
37
85
|
def self.after_load_plugin(serializer_class, **_opts)
|
38
86
|
serializer_class.config.opts[:metadata] = {attribute_keys: %i[path hide_nil hide_empty]}
|
39
87
|
end
|
40
88
|
|
89
|
+
#
|
90
|
+
# Config for `metadata` plugin
|
91
|
+
#
|
41
92
|
class MetadataConfig
|
93
|
+
# @return [Hash] metadata options
|
42
94
|
attr_reader :opts
|
43
95
|
|
96
|
+
#
|
97
|
+
# Initializes context_metadata config object
|
98
|
+
#
|
99
|
+
# @param opts [Hash] options
|
100
|
+
#
|
101
|
+
# @return [Serega::SeregaPlugins::Metadata::MetadataConfig]
|
102
|
+
#
|
44
103
|
def initialize(opts)
|
45
104
|
@opts = opts
|
46
105
|
end
|
47
106
|
|
107
|
+
#
|
108
|
+
# Returns allowed metadata attribute keys
|
109
|
+
#
|
48
110
|
def attribute_keys
|
49
111
|
opts.fetch(:attribute_keys)
|
50
112
|
end
|
51
113
|
end
|
52
114
|
|
115
|
+
#
|
116
|
+
# Config class additional/patched instance methods
|
117
|
+
#
|
118
|
+
# @see Serega::SeregaConfig
|
119
|
+
#
|
53
120
|
module ConfigInstanceMethods
|
121
|
+
# @return [Serega::SeregaPlugins::Metadata::MetadataConfig] metadata config
|
54
122
|
def metadata
|
55
123
|
@metadata ||= MetadataConfig.new(opts.fetch(:metadata))
|
56
124
|
end
|
57
125
|
end
|
58
126
|
|
127
|
+
#
|
128
|
+
# Serega class additional/patched class methods
|
129
|
+
#
|
130
|
+
# @see Serega::SeregaConfig
|
131
|
+
#
|
59
132
|
module ClassMethods
|
60
133
|
private def inherited(subclass)
|
61
134
|
super
|
@@ -73,30 +146,20 @@ class Serega
|
|
73
146
|
#
|
74
147
|
# List of added metadata attributes
|
75
148
|
#
|
76
|
-
# @return [
|
149
|
+
# @return [Hash<Symbol => Serega::SeregaPlugins::Metadata::MetaAttribute>] Added metadata attributes
|
77
150
|
#
|
78
151
|
def meta_attributes
|
79
152
|
@meta_attributes ||= {}
|
80
153
|
end
|
81
154
|
|
82
155
|
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
# @example
|
86
|
-
# class AppSerializer < Serega
|
87
|
-
#
|
88
|
-
# meta_attribute(:version) { '1.2.3' }
|
156
|
+
# Define metadata attribute
|
89
157
|
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
# end
|
158
|
+
# @param path [String, Symbol] Metadata attribute path keys
|
159
|
+
# @param opts [Hash] Metadata attribute options
|
160
|
+
# @param block [Proc] Block to fetch metadata attribute value
|
94
161
|
#
|
95
|
-
# @
|
96
|
-
# @param **opts [Hash] Metadata attribute options
|
97
|
-
# @param &block [Proc] Metadata attribute value
|
98
|
-
#
|
99
|
-
# @return [MetadataAttribute] Added metadata attribute
|
162
|
+
# @return [Serega::SeregaPlugins::Metadata::MetaAttribute] Added metadata attribute
|
100
163
|
#
|
101
164
|
def meta_attribute(*path, **opts, &block)
|
102
165
|
attribute = self::MetaAttribute.new(path: path, opts: opts, block: block)
|
@@ -104,25 +167,35 @@ class Serega
|
|
104
167
|
end
|
105
168
|
end
|
106
169
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
170
|
+
#
|
171
|
+
# Serega additional/patched instance methods
|
172
|
+
#
|
173
|
+
# @see Serega
|
174
|
+
#
|
175
|
+
module InstanceMethods
|
114
176
|
private
|
115
177
|
|
116
|
-
def
|
117
|
-
|
118
|
-
|
178
|
+
def serialize(object, opts)
|
179
|
+
result = super
|
180
|
+
return result unless result.is_a?(Hash) # return earlier if not a hash, so no root was added
|
181
|
+
|
182
|
+
root = build_root(object, opts)
|
183
|
+
return result unless root # return earlier when no root
|
184
|
+
|
185
|
+
add_metadata(object, opts[:context], result)
|
186
|
+
result
|
187
|
+
end
|
188
|
+
|
189
|
+
def add_metadata(object, context, hash)
|
190
|
+
self.class.meta_attributes.each_value do |meta_attribute|
|
191
|
+
metadata = meta_attribute_value(object, context, meta_attribute)
|
119
192
|
next unless metadata
|
120
193
|
|
121
194
|
deep_merge_metadata(hash, metadata)
|
122
195
|
end
|
123
196
|
end
|
124
197
|
|
125
|
-
def meta_attribute_value(object, meta_attribute)
|
198
|
+
def meta_attribute_value(object, context, meta_attribute)
|
126
199
|
value = meta_attribute.value(object, context)
|
127
200
|
return if meta_attribute.hide?(value)
|
128
201
|
|
@@ -4,10 +4,13 @@ class Serega
|
|
4
4
|
module SeregaPlugins
|
5
5
|
module Metadata
|
6
6
|
class MetaAttribute
|
7
|
+
#
|
8
|
+
# Validator for meta_attribute :hide_empty option
|
9
|
+
#
|
7
10
|
class CheckOptHideEmpty
|
8
11
|
class << self
|
9
12
|
#
|
10
|
-
# Checks attribute :
|
13
|
+
# Checks attribute :hide_empty option
|
11
14
|
#
|
12
15
|
# @param opts [Hash] Attribute options
|
13
16
|
#
|
@@ -4,10 +4,13 @@ class Serega
|
|
4
4
|
module SeregaPlugins
|
5
5
|
module Metadata
|
6
6
|
class MetaAttribute
|
7
|
+
#
|
8
|
+
# Validator for meta_attribute :hide_nil option
|
9
|
+
#
|
7
10
|
class CheckOptHideNil
|
8
11
|
class << self
|
9
12
|
#
|
10
|
-
# Checks attribute :
|
13
|
+
# Checks attribute :hide_nil option
|
11
14
|
#
|
12
15
|
# @param opts [Hash] Attribute options
|
13
16
|
#
|
@@ -4,6 +4,9 @@ class Serega
|
|
4
4
|
module SeregaPlugins
|
5
5
|
module Metadata
|
6
6
|
class MetaAttribute
|
7
|
+
#
|
8
|
+
# Validator for meta_attribute :path parameter
|
9
|
+
#
|
7
10
|
class CheckPath
|
8
11
|
FORMAT_ONE_CHAR = /\A[a-zA-Z0-9]\z/
|
9
12
|
FORMAT_MANY_CHARS = /\A[a-zA-Z0-9][a-zA-Z0-9_-]*?[a-zA-Z0-9]\z/ # allow '-' and '_' in the middle
|