serega 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|