serega 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/serega/attribute.rb +2 -2
  4. data/lib/serega/config.rb +23 -1
  5. data/lib/serega/errors.rb +8 -5
  6. data/lib/serega/helpers/serializer_class_helper.rb +5 -0
  7. data/lib/serega/json/adapter.rb +6 -0
  8. data/lib/serega/json/json.rb +20 -0
  9. data/lib/serega/json/oj.rb +20 -0
  10. data/lib/serega/map.rb +17 -0
  11. data/lib/serega/map_point.rb +36 -1
  12. data/lib/serega/object_serializer.rb +17 -4
  13. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +66 -18
  14. data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +100 -40
  15. data/lib/serega/plugins/batch/batch.rb +136 -10
  16. data/lib/serega/plugins/batch/lib/loader.rb +33 -1
  17. data/lib/serega/plugins/batch/lib/loaders.rb +15 -2
  18. data/lib/serega/plugins/batch/lib/plugins_extensions.rb +23 -1
  19. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_key.rb +12 -0
  20. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +12 -0
  21. data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +12 -0
  22. data/lib/serega/plugins/context_metadata/context_metadata.rb +95 -13
  23. data/lib/serega/plugins/formatters/formatters.rb +94 -8
  24. data/lib/serega/plugins/hide_nil/hide_nil.rb +33 -7
  25. data/lib/serega/plugins/metadata/metadata.rb +108 -35
  26. data/lib/serega/plugins/metadata/validations/check_block.rb +3 -0
  27. data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +4 -1
  28. data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +4 -1
  29. data/lib/serega/plugins/metadata/validations/check_opts.rb +3 -0
  30. data/lib/serega/plugins/metadata/validations/check_path.rb +3 -0
  31. data/lib/serega/plugins/preloads/lib/enum_deep_freeze.rb +10 -1
  32. data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +13 -4
  33. data/lib/serega/plugins/preloads/lib/main_preload_path.rb +16 -3
  34. data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +4 -6
  35. data/lib/serega/plugins/preloads/preloads.rb +145 -12
  36. data/lib/serega/plugins/preloads/validations/check_opt_preload.rb +11 -0
  37. data/lib/serega/plugins/preloads/validations/check_opt_preload_path.rb +12 -0
  38. data/lib/serega/plugins/presenter/presenter.rb +20 -11
  39. data/lib/serega/plugins/root/root.rb +131 -19
  40. data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +42 -12
  41. data/lib/serega/plugins/string_modifiers/string_modifiers.rb +14 -0
  42. data/lib/serega/plugins.rb +7 -1
  43. data/lib/serega/utils/enum_deep_dup.rb +5 -0
  44. data/lib/serega/utils/to_hash.rb +21 -3
  45. data/lib/serega/validations/attribute/check_block.rb +7 -2
  46. data/lib/serega/validations/attribute/check_name.rb +6 -0
  47. data/lib/serega/validations/attribute/check_opt_const.rb +12 -9
  48. data/lib/serega/validations/attribute/check_opt_delegate.rb +13 -10
  49. data/lib/serega/validations/attribute/check_opt_hide.rb +3 -0
  50. data/lib/serega/validations/attribute/check_opt_key.rb +12 -9
  51. data/lib/serega/validations/attribute/check_opt_many.rb +3 -0
  52. data/lib/serega/validations/attribute/check_opt_serializer.rb +3 -0
  53. data/lib/serega/validations/attribute/check_opt_value.rb +12 -9
  54. data/lib/serega/validations/check_attribute_params.rb +29 -1
  55. data/lib/serega/validations/check_initiate_params.rb +17 -0
  56. data/lib/serega/validations/check_serialize_params.rb +16 -0
  57. data/lib/serega/validations/initiate/check_modifiers.rb +15 -0
  58. data/lib/serega/validations/utils/check_allowed_keys.rb +14 -0
  59. data/lib/serega/validations/utils/check_opt_is_bool.rb +11 -0
  60. data/lib/serega/validations/utils/check_opt_is_hash.rb +11 -0
  61. data/lib/serega/validations/utils/check_opt_is_string_or_symbol.rb +11 -0
  62. data/lib/serega/version.rb +4 -0
  63. data/lib/serega.rb +83 -24
  64. metadata +2 -3
  65. data/lib/serega/serializer.rb +0 -32
@@ -3,9 +3,18 @@
3
3
  class Serega
4
4
  module SeregaPlugins
5
5
  module Preloads
6
- # Freezes nested enumerable data
6
+ #
7
+ # Utility to freeze nested hashes and arrays
8
+ #
7
9
  class EnumDeepFreeze
8
10
  class << self
11
+ #
12
+ # Freezes nested enumerable data
13
+ #
14
+ # @param data[Hash, Array] data to freeze
15
+ #
16
+ # @return [Hash, Array] same deeply frozen data
17
+ #
9
18
  def call(data)
10
19
  data.each_entry { |entry| call(entry) } if data.is_a?(Enumerable)
11
20
  data.freeze
@@ -3,7 +3,9 @@
3
3
  class Serega
4
4
  module SeregaPlugins
5
5
  module Preloads
6
- # Transforms user provided preloads to hash
6
+ #
7
+ # Utility that helps to transform user provided preloads to hash
8
+ #
7
9
  class FormatUserPreloads
8
10
  METHODS = {
9
11
  Array => :array_to_hash,
@@ -14,7 +16,16 @@ class Serega
14
16
  Symbol => :symbol_to_hash
15
17
  }.freeze
16
18
 
17
- module ClassMethods
19
+ private_constant :METHODS
20
+
21
+ class << self
22
+ #
23
+ # Transforms user provided preloads to hash
24
+ #
25
+ # @param value [Array,Hash,String,Symbol,nil,false] preloads
26
+ #
27
+ # @return [Hash] preloads transformed to hash
28
+ #
18
29
  def call(value)
19
30
  send(METHODS.fetch(value.class), value)
20
31
  end
@@ -45,8 +56,6 @@ class Serega
45
56
  {value => {}}
46
57
  end
47
58
  end
48
-
49
- extend ClassMethods
50
59
  end
51
60
  end
52
61
  end
@@ -3,9 +3,24 @@
3
3
  class Serega
4
4
  module SeregaPlugins
5
5
  module Preloads
6
+ #
7
+ # Class that constructs main preloads path.
8
+ #
9
+ # When we have nested preloads we will use this path to dig to `main` element and
10
+ # assign nested preloads to it.
11
+ #
12
+ # By default its a path to latest provided preload
13
+ #
14
+ # @example
15
+ # MainPreloadPath.(a: { b: { c: {} }, d: {} }) # => [:a, :d]
16
+ #
6
17
  class MainPreloadPath
7
- module ClassMethods
18
+ class << self
19
+ # Finds default preload path
20
+ #
8
21
  # @param preloads [Hash] Formatted user provided preloads hash
22
+ #
23
+ # @return [Array<Symbol>] Preloads path to `main` element
9
24
  def call(preloads)
10
25
  return FROZEN_EMPTY_ARRAY if preloads.empty?
11
26
 
@@ -32,8 +47,6 @@ class Serega
32
47
  path
33
48
  end
34
49
  end
35
-
36
- extend ClassMethods
37
50
  end
38
51
  end
39
52
  end
@@ -4,14 +4,14 @@ class Serega
4
4
  module SeregaPlugins
5
5
  module Preloads
6
6
  #
7
- # Finds relations to preload for provided serializer
7
+ # Finds preloads for provided attributes map
8
8
  #
9
9
  class PreloadsConstructor
10
- module ClassMethods
10
+ class << self
11
11
  #
12
- # Constructs preloads hash for given serializer
12
+ # Constructs preloads hash for given attributes map
13
13
  #
14
- # @param serializer [Serega] Instance of Serega serializer
14
+ # @param map [Array<Serega::MapPoint>] Serialization map
15
15
  #
16
16
  # @return [Hash]
17
17
  #
@@ -54,8 +54,6 @@ class Serega
54
54
  (!path || path.empty?) ? preloads : preloads.dig(*path)
55
55
  end
56
56
  end
57
-
58
- extend ClassMethods
59
57
  end
60
58
  end
61
59
  end
@@ -3,8 +3,59 @@
3
3
  class Serega
4
4
  module SeregaPlugins
5
5
  #
6
- # Plugin adds `.preloads` method to find relations that must be preloaded
6
+ # Plugin `:preloads`
7
7
  #
8
+ # Allows to define `:preloads` to attributes and then allows to merge preloads
9
+ # from serialized attributes and return single associations hash.
10
+ #
11
+ # Plugin accepts options:
12
+ # - `auto_preload_attributes_with_delegate` - default false
13
+ # - `auto_preload_attributes_with_serializer` - default false
14
+ # - `auto_hide_attributes_with_preload` - default false
15
+ #
16
+ # This options are very handy if you want to forget about finding preloads manually.
17
+ #
18
+ # Preloads can be disabled with `preload: false` attribute option option.
19
+ # Also automatically added preloads can be overwritten with manually specified `preload: :another_value`.
20
+ #
21
+ # Some examples, **please read comments in the code below**
22
+ #
23
+ # @example
24
+ # class AppSerializer < Serega
25
+ # plugin :preloads,
26
+ # auto_preload_attributes_with_delegate: true,
27
+ # auto_preload_attributes_with_serializer: true,
28
+ # auto_hide_attributes_with_preload: true
29
+ # end
30
+ #
31
+ # class UserSerializer < AppSerializer
32
+ # # No preloads
33
+ # attribute :username
34
+ #
35
+ # # Specify `preload: :user_stats` manually
36
+ # attribute :followers_count, preload: :user_stats, value: proc { |user| user.user_stats.followers_count }
37
+ #
38
+ # # Automatically `preloads: :user_stats` as `auto_preload_attributes_with_delegate` option is true
39
+ # attribute :comments_count, delegate: { to: :user_stats }
40
+ #
41
+ # # Automatically `preloads: :albums` as `auto_preload_attributes_with_serializer` option is true
42
+ # attribute :albums, serializer: 'AlbumSerializer'
43
+ # end
44
+ #
45
+ # class AlbumSerializer < AppSerializer
46
+ # attribute :images_count, delegate: { to: :album_stats }
47
+ # end
48
+ #
49
+ # # By default preloads are empty, as we specify `auto_hide_attributes_with_preload = true`,
50
+ # # and attributes with preloads will be not serialized
51
+ # UserSerializer.new.preloads # => {}
52
+ # UserSerializer.new.to_h(OpenStruct.new(username: 'foo')) # => {:username=>"foo"}
53
+ #
54
+ # UserSerializer.new(with: :followers_count).preloads # => {:user_stats=>{}}
55
+ # UserSerializer.new(with: %i[followers_count comments_count]).preloads # => {:user_stats=>{}}
56
+ # UserSerializer.new(with: [:followers_count, :comments_count, { albums: :images_count }]).preloads # => {:user_stats=>{}, :albums=>{:album_stats=>{}}}
57
+ #
58
+
8
59
  module Preloads
9
60
  DEFAULT_CONFIG = {
10
61
  auto_preload_attributes_with_delegate: false,
@@ -12,24 +63,26 @@ class Serega
12
63
  auto_hide_attributes_with_preload: false
13
64
  }.freeze
14
65
 
15
- # @return [Symbol] plugin name
66
+ private_constant :DEFAULT_CONFIG
67
+
68
+ # @return [Symbol] Plugin name
16
69
  def self.plugin_name
17
70
  :preloads
18
71
  end
19
72
 
20
73
  #
21
- # Includes plugin modules to current serializer
74
+ # Applies plugin code to specific serializer
22
75
  #
23
- # @param serializer_class [Class] current serializer class
24
- # @param _opts [Hash] plugin opts
76
+ # @param serializer_class [Class<Serega>] Current serializer class
77
+ # @param _opts [Hash] Loaded plugins options
25
78
  #
26
79
  # @return [void]
27
80
  #
28
81
  def self.load_plugin(serializer_class, **_opts)
29
82
  serializer_class.include(InstanceMethods)
30
- serializer_class::SeregaAttribute.include(AttributeMethods)
83
+ serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
31
84
  serializer_class::SeregaConfig.include(ConfigInstanceMethods)
32
- serializer_class::SeregaMapPoint.include(MapPointMethods)
85
+ serializer_class::SeregaMapPoint.include(MapPointInstanceMethods)
33
86
 
34
87
  serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
35
88
 
@@ -41,6 +94,14 @@ class Serega
41
94
  require_relative "./validations/check_opt_preload_path"
42
95
  end
43
96
 
97
+ #
98
+ # Adds config options and runs other callbacks after plugin was loaded
99
+ #
100
+ # @param serializer_class [Class<Serega>] Current serializer class
101
+ # @param opts [Hash] loaded plugins opts
102
+ #
103
+ # @return [void]
104
+ #
44
105
  def self.after_load_plugin(serializer_class, **opts)
45
106
  config = serializer_class.config
46
107
  config.attribute_keys << :preload << :preload_path
@@ -53,21 +114,57 @@ class Serega
53
114
  preloads_config.auto_hide_attributes_with_preload = preloads_opts[:auto_hide_attributes_with_preload]
54
115
  end
55
116
 
56
- # Adds #preloads instance method
117
+ #
118
+ # Serega additional/patched instance methods
119
+ #
120
+ # @see Serega
121
+ #
57
122
  module InstanceMethods
58
- # @return [Hash] relations that can be preloaded to omit N+1
123
+ # @return [Hash] merged preloads of all serialized attributes
59
124
  def preloads
60
125
  @preloads ||= PreloadsConstructor.call(map)
61
126
  end
62
127
  end
63
128
 
129
+ #
130
+ # Config for `preloads` plugin
131
+ #
64
132
  class PreloadsConfig
133
+ # @return [Hash] preloads plugin options
65
134
  attr_reader :opts
66
135
 
136
+ #
137
+ # Initializes context_metadata config object
138
+ #
139
+ # @param opts [Hash] options
140
+ #
141
+ # @return [Serega::SeregaPlugins::Metadata::MetadataConfig]
142
+ #
67
143
  def initialize(opts)
68
144
  @opts = opts
69
145
  end
70
146
 
147
+ # @!method auto_preload_attributes_with_delegate
148
+ # @return [Boolean, nil] option value
149
+ #
150
+ # @!method auto_preload_attributes_with_delegate=(value)
151
+ # @param value [Boolean] New option value
152
+ # @return [Boolean] New option value
153
+ #
154
+ # @!method auto_preload_attributes_with_serializer
155
+ # @return [Boolean, nil] option value
156
+ #
157
+ # @!method auto_preload_attributes_with_serializer=(value)
158
+ # @param value [Boolean] New option value
159
+ # @return [Boolean] New option value
160
+ #
161
+ # @!method auto_hide_attributes_with_preload
162
+ # @return [Boolean, nil] option value
163
+ #
164
+ # @!method auto_hide_attributes_with_preload=(value)
165
+ # @param value [Boolean] New option value
166
+ # @return [Boolean] New option value
167
+ #
71
168
  %i[
72
169
  auto_preload_attributes_with_delegate
73
170
  auto_preload_attributes_with_serializer
@@ -84,26 +181,46 @@ class Serega
84
181
  end
85
182
  end
86
183
 
184
+ #
185
+ # Config class additional/patched instance methods
186
+ #
187
+ # @see Serega::SeregaConfig
188
+ #
87
189
  module ConfigInstanceMethods
190
+ # @return [Serega::SeregaPlugins::Preloads::PreloadsConfig] `preloads` plugin config
88
191
  def preloads
89
192
  @preloads ||= PreloadsConfig.new(opts.fetch(:preloads))
90
193
  end
91
194
  end
92
195
 
93
- # Adds #preloads and #preloads_path Attribute instance method
94
- module AttributeMethods
196
+ #
197
+ # Serega::SeregaAttribute additional/patched instance methods
198
+ #
199
+ # @see Serega::SeregaAttribute::AttributeInstanceMethods
200
+ #
201
+ module AttributeInstanceMethods
202
+ # @return [Hash,nil] formatted preloads of current attribute
95
203
  def preloads
96
204
  return @preloads if defined?(@preloads)
97
205
 
98
206
  @preloads = get_preloads
99
207
  end
100
208
 
209
+ # @return [Array] formatted preloads_path of current attribute
101
210
  def preloads_path
102
211
  return @preloads_path if defined?(@preloads_path)
103
212
 
104
213
  @preloads_path = get_preloads_path
105
214
  end
106
215
 
216
+ # Patch for original `hide` method
217
+ #
218
+ # Marks attribute hidden if auto_hide_attribute_with_preloads option was set and attribute has preloads
219
+ #
220
+ # @return [Boolean, nil] if attribute is hidden
221
+ #
222
+ # @see Serega::SeregaAttribute::AttributeInstanceMethods#hide
223
+ #
107
224
  def hide
108
225
  res = super
109
226
  return res unless res.nil?
@@ -143,16 +260,32 @@ class Serega
143
260
  end
144
261
  end
145
262
 
146
- module MapPointMethods
263
+ #
264
+ # Serega::SeregaMapPoint additional/patched instance methods
265
+ #
266
+ # @see Serega::SeregaMapPoint::InstanceMethods
267
+ #
268
+ module MapPointInstanceMethods
269
+ #
270
+ # @return [Hash] preloads for nested attributes
271
+ #
147
272
  def preloads
148
273
  @preloads ||= PreloadsConstructor.call(nested_points)
149
274
  end
150
275
 
276
+ #
277
+ # @return [Array<Symbol>] preloads path for current attribute
278
+ #
151
279
  def preloads_path
152
280
  attribute.preloads_path
153
281
  end
154
282
  end
155
283
 
284
+ #
285
+ # Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
286
+ #
287
+ # @see Serega::SeregaValidations::CheckAttributeParams
288
+ #
156
289
  module CheckAttributeParamsInstanceMethods
157
290
  private
158
291
 
@@ -3,8 +3,19 @@
3
3
  class Serega
4
4
  module SeregaPlugins
5
5
  module Preloads
6
+ #
7
+ # Validator for attribute :preload option
8
+ #
6
9
  class CheckOptPreload
7
10
  class << self
11
+ #
12
+ # Checks :preload option
13
+ #
14
+ # @param opts [Hash] Attribute options
15
+ #
16
+ # @raise [SeregaError] validation error
17
+ #
18
+ # @return [void]
8
19
  def call(opts)
9
20
  return unless opts.key?(:preload)
10
21
 
@@ -3,8 +3,20 @@
3
3
  class Serega
4
4
  module SeregaPlugins
5
5
  module Preloads
6
+ #
7
+ # Validator for attribute :preload_path option
8
+ #
6
9
  class CheckOptPreloadPath
7
10
  class << self
11
+ #
12
+ # Checks preload_path option
13
+ #
14
+ # @param opts [Hash] Attribute options
15
+ #
16
+ # @raise [SeregaError] validation error
17
+ #
18
+ # @return [void]
19
+ #
8
20
  def call(opts)
9
21
  return unless opts.key?(:preload_path)
10
22
 
@@ -20,13 +20,13 @@ class Serega
20
20
  # end
21
21
  # end
22
22
  module Presenter
23
- # @return [Symbol] plugin name
23
+ # @return [Symbol] Plugin name
24
24
  def self.plugin_name
25
25
  :presenter
26
26
  end
27
27
 
28
28
  #
29
- # Loads plugin
29
+ # Applies plugin code to specific serializer
30
30
  #
31
31
  # @param serializer_class [Class<Serega>] Current serializer class
32
32
  # @param _opts [Hash] Loaded plugins options
@@ -35,14 +35,14 @@ class Serega
35
35
  #
36
36
  def self.load_plugin(serializer_class, **_opts)
37
37
  serializer_class.extend(ClassMethods)
38
- serializer_class::SeregaSerializer.include(SeregaSerializerInstanceMethods)
38
+ serializer_class::SeregaObjectSerializer.include(SeregaObjectSerializerInstanceMethods)
39
39
  end
40
40
 
41
41
  #
42
- # Adds Presenter to current serializer
42
+ # Runs callbacks after plugin was attached
43
43
  #
44
44
  # @param serializer_class [Class<Serega>] Current serializer class
45
- # @param _opts [Hash] Loaded plugins options
45
+ # @param _opts [Hash] loaded plugins opts
46
46
  #
47
47
  # @return [void]
48
48
  #
@@ -74,7 +74,11 @@ class Serega
74
74
  include InstanceMethods
75
75
  end
76
76
 
77
- # Overrides class methods of included class
77
+ #
78
+ # Serega additional/patched class methods
79
+ #
80
+ # @see Serega
81
+ #
78
82
  module ClassMethods
79
83
  private def inherited(subclass)
80
84
  super
@@ -94,14 +98,19 @@ class Serega
94
98
  end
95
99
  end
96
100
 
97
- # Includes methods to override SeregaSerializer class
98
- module SeregaSerializerInstanceMethods
101
+ #
102
+ # SeregaObjectSerializer additional/patched class methods
103
+ #
104
+ # @see Serega::SeregaObjectSerializer
105
+ #
106
+ module SeregaObjectSerializerInstanceMethods
107
+ private
108
+
99
109
  #
100
110
  # Replaces serialized object with Presenter.new(object)
101
111
  #
102
- def serialize(object)
103
- presenter_class = points.first.class.serializer_class::Presenter
104
- object = presenter_class.new(object)
112
+ def serialize_object(object)
113
+ object = self.class.serializer_class::Presenter.new(object)
105
114
  super
106
115
  end
107
116
  end