serega 0.10.0 → 0.11.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +319 -141
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +37 -105
  5. data/lib/serega/attribute_normalizer.rb +176 -0
  6. data/lib/serega/config.rb +26 -9
  7. data/lib/serega/object_serializer.rb +11 -10
  8. data/lib/serega/plan.rb +128 -0
  9. data/lib/serega/plan_point.rb +94 -0
  10. data/lib/serega/plugins/batch/batch.rb +187 -41
  11. data/lib/serega/plugins/batch/lib/loader.rb +4 -4
  12. data/lib/serega/plugins/batch/lib/loaders.rb +3 -3
  13. data/lib/serega/plugins/batch/lib/plugins_extensions/activerecord_preloads.rb +2 -2
  14. data/lib/serega/plugins/batch/lib/plugins_extensions/formatters.rb +19 -1
  15. data/lib/serega/plugins/batch/lib/plugins_extensions/preloads.rb +6 -4
  16. data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +9 -5
  17. data/lib/serega/plugins/formatters/formatters.rb +28 -31
  18. data/lib/serega/plugins/if/if.rb +24 -6
  19. data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +11 -13
  20. data/lib/serega/plugins/preloads/lib/main_preload_path.rb +2 -2
  21. data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +21 -15
  22. data/lib/serega/plugins/preloads/preloads.rb +72 -40
  23. data/lib/serega/plugins/presenter/presenter.rb +0 -9
  24. data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +11 -1
  25. data/lib/serega/plugins.rb +3 -3
  26. data/lib/serega/utils/enum_deep_dup.rb +18 -18
  27. data/lib/serega/utils/enum_deep_freeze.rb +35 -0
  28. data/lib/serega/utils/to_hash.rb +17 -9
  29. data/lib/serega.rb +22 -15
  30. metadata +6 -6
  31. data/lib/serega/map.rb +0 -91
  32. data/lib/serega/map_point.rb +0 -66
  33. data/lib/serega/plugins/batch/lib/batch_option_model.rb +0 -73
  34. data/lib/serega/plugins/preloads/lib/enum_deep_freeze.rb +0 -26
@@ -3,33 +3,62 @@
3
3
  class Serega
4
4
  module SeregaPlugins
5
5
  #
6
- # Plugin that can be used to load attributes values in batches.
7
- # Each batch loader accepts list of selected keys, context and current attribute point.
8
- # Each batch loader must return Hash with values grouped by provided keys.
9
- # There are specific `:default` option that can be used to add default value for missing key.
6
+ # Plugin `:batch`
7
+ #
8
+ # Adds ability to load nested attributes values in batches.
9
+ #
10
+ # It can be used to find value for attributes in optimal way:
11
+ # - load associations for multiple objects
12
+ # - load counters for multiple objects
13
+ # - make any heavy calculations for multiple objects only once
14
+ #
15
+ # After including plugin, attributes gain new `:batch` option.
16
+ #
17
+ # `:batch` option must be a hash with this keys:
18
+ # - `key` (required) [Symbol, Proc, callable] - Defines current object identifier.
19
+ # Later `loader` will accept array of `keys` to find `values`.
20
+ # - `loader` (required) [Symbol, Proc, callable] - Defines how to fetch values for
21
+ # batch of keys. Receives 3 parameters: keys, context, plan_point.
22
+ # - `default` (optional) - Default value for attribute.
23
+ # By default it is `nil` or `[]` when attribute has option `many: true`
24
+ #
25
+ # If `:loader` was defined using name (as Symbol) then batch loader must be
26
+ # defined in serializer config: `config.batch.define(:loader_name) { ... }` method.
27
+ #
28
+ # *Result of this `:loader` callable must be a **Hash** where*:
29
+ # - keys - provided keys
30
+ # - values - values for according keys
31
+ #
32
+ # `Batch` plugin can be defined with two specific attributes:
33
+ # - `auto_hide: true` - Marks attributes with defined :batch as hidden, so it
34
+ # will not be serialized by default
35
+ # - `default_key: :id` - Set default object key (in this case :id) that will be used for all attributes with :batch option specified.
36
+ #
37
+ # This options (`auto_hide`, `default_key`) also can be set as config options in
38
+ # any nested serializer.
10
39
  #
11
40
  # @example
12
41
  # class PostSerializer < Serega
13
- # plugin :batch
42
+ # plugin :batch, auto_hide: true, default_key: :id
14
43
  #
15
- # # Define batch loader via callable class, it must accept three args (keys, context, map_point)
16
- # attribute :comments_count, batch: { key: :id, loader: PostCommentsCountBatchLoader, default: 0}
44
+ # # Define batch loader via callable class, it must accept three args (keys, context, plan_point)
45
+ # attribute :comments_count, batch: { loader: PostCommentsCountBatchLoader, default: 0}
17
46
  #
18
- # # Define batch loader via Symbol, later we should define this loader via config.batch_loaders.define(:posts_comments_counter) { ... }
19
- # attribute :comments_count, batch: { key: :id, loader: :posts_comments_counter, default: 0}
47
+ # # Define batch loader via Symbol, later we should define this loader via config.batch.define(:posts_comments_counter) { ... }
48
+ # attribute :comments_count, batch: { loader: :posts_comments_counter, default: 0}
20
49
  #
21
50
  # # Define batch loader with serializer
22
- # attribute :comments, serializer: CommentSerializer, batch: { key: :id, loader: :posts_comments, default: []}
51
+ # attribute :comments, serializer: CommentSerializer, batch: { loader: :posts_comments, default: []}
23
52
  #
24
53
  # # Resulted block must return hash like { key => value(s) }
25
- # config.batch_loaders.define(:posts_comments_counter) do |keys|
54
+ # config.batch.define(:posts_comments_counter) do |keys|
26
55
  # Comment.group(:post_id).where(post_id: keys).count
27
56
  # end
28
57
  #
29
58
  # # We can return objects that will be automatically serialized if attribute defined with :serializer
30
59
  # # Parameter `context` can be used when loading batch
31
- # # Parameter `map_point` can be used to find nested attributes that will be serialized (`map_point.preloads`)
32
- # config.batch_loaders.define(:posts_comments) do |keys, context, map_point|
60
+ # # Parameter `plan_point` can be used to find nested attributes that will be serialized (`plan_point.preloads`)
61
+ # config.batch.define(:posts_comments) do |keys, context, plan_point|
33
62
  # Comment.where(post_id: keys).where(is_spam: false).group_by(&:post_id)
34
63
  # end
35
64
  # end
@@ -50,7 +79,6 @@ class Serega
50
79
  # @return [void]
51
80
  #
52
81
  def self.load_plugin(serializer_class, **_opts)
53
- require_relative "./lib/batch_option_model"
54
82
  require_relative "./lib/loader"
55
83
  require_relative "./lib/loaders"
56
84
  require_relative "./lib/validations/check_batch_opt_key"
@@ -61,7 +89,8 @@ class Serega
61
89
  serializer_class.include(InstanceMethods)
62
90
  serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
63
91
  serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
64
- serializer_class::SeregaMapPoint.include(MapPointInstanceMethods)
92
+ serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
93
+ serializer_class::SeregaPlanPoint.include(PlanPointInstanceMethods)
65
94
  serializer_class::SeregaObjectSerializer.include(SeregaObjectSerializerInstanceMethods)
66
95
  end
67
96
 
@@ -74,9 +103,6 @@ class Serega
74
103
  # @return [void]
75
104
  #
76
105
  def self.after_load_plugin(serializer_class, **opts)
77
- config = serializer_class.config
78
- config.attribute_keys << :batch
79
- config.opts[:batch] = {loaders: {}}
80
106
  serializer_class::SeregaConfig.include(ConfigInstanceMethods)
81
107
 
82
108
  batch_loaders_class = Class.new(SeregaBatchLoaders)
@@ -95,18 +121,24 @@ class Serega
95
121
  if serializer_class.plugin_used?(:formatters)
96
122
  require_relative "./lib/plugins_extensions/formatters"
97
123
  serializer_class::SeregaBatchLoader.include(PluginsExtensions::Formatters::BatchLoaderInstanceMethods)
124
+ serializer_class::SeregaAttribute.include(PluginsExtensions::Formatters::SeregaAttributeInstanceMethods)
98
125
  end
99
126
 
100
127
  if serializer_class.plugin_used?(:preloads)
101
128
  require_relative "./lib/plugins_extensions/preloads"
102
- serializer_class::SeregaAttribute.include(PluginsExtensions::Preloads::AttributeInstanceMethods)
129
+ serializer_class::SeregaAttributeNormalizer.include(PluginsExtensions::Preloads::AttributeNormalizerInstanceMethods)
103
130
  end
131
+
132
+ config = serializer_class.config
133
+ config.attribute_keys << :batch
134
+ config.opts[:batch] = {loaders: {}, default_key: nil, auto_hide: false}
135
+ config.batch.auto_hide = opts[:auto_hide] if opts.key?(:auto_hide)
104
136
  end
105
137
 
106
138
  #
107
139
  # Batch loader config
108
140
  #
109
- class BatchLoadersConfig
141
+ class BatchConfig
110
142
  attr_reader :opts
111
143
 
112
144
  def initialize(opts)
@@ -117,22 +149,28 @@ class Serega
117
149
  # Defines batch loader
118
150
  #
119
151
  # @param loader_name [Symbol] Batch loader name, that is used when defining attribute with batch loader.
120
- # @param block [Proc] Block that can accept 3 parameters - keys, context, map_point
152
+ # @param block [Proc] Block that can accept 3 parameters - keys, context, plan_point
121
153
  # and returns hash where ids are keys and values are batch loaded objects/
122
154
  #
123
155
  # @return [void]
124
156
  #
125
157
  def define(loader_name, &block)
126
158
  unless block
127
- raise SeregaError, "Block must be given to batch_loaders.define method"
159
+ raise SeregaError, "Block must be given to #define method"
128
160
  end
129
161
 
130
162
  params = block.parameters
131
- if params.count > 3 || !params.map!(&:first).all? { |type| (type == :req) || (type == :opt) }
163
+ if params.count > 3 || !params.all? { |param| (param[0] == :req) || (param[0] == :opt) }
132
164
  raise SeregaError, "Block can have maximum 3 regular parameters"
133
165
  end
134
166
 
135
- opts[loader_name] = block
167
+ loaders[loader_name] = block
168
+ end
169
+
170
+ # Shows defined loaders
171
+ # @return [Hash] defined loaders
172
+ def loaders
173
+ opts[:loaders]
136
174
  end
137
175
 
138
176
  #
@@ -141,8 +179,34 @@ class Serega
141
179
  # @param loader_name [Symbol]
142
180
  #
143
181
  # @return [Proc] batch loader block
144
- def fetch(loader_name)
145
- opts[loader_name] || (raise SeregaError, "Batch loader with name `#{loader_name.inspect}` was not defined. Define example: config.batch_loaders.define(:#{loader_name}) { |keys, ctx, points| ... }")
182
+ def fetch_loader(loader_name)
183
+ loaders[loader_name] || (raise SeregaError, "Batch loader with name `#{loader_name.inspect}` was not defined. Define example: config.batch.define(:#{loader_name}) { |keys, ctx, points| ... }")
184
+ end
185
+
186
+ # Shows option to auto hide attributes with :batch specified
187
+ # @return [Boolean, nil] option value
188
+ def auto_hide
189
+ opts[:auto_hide]
190
+ end
191
+
192
+ # @param value [Boolean] New :auto_hide option value
193
+ # @return [Boolean] New option value
194
+ def auto_hide=(value)
195
+ raise SeregaError, "Must have boolean value, #{value.inspect} provided" if (value != true) && (value != false)
196
+ opts[:auto_hide] = value
197
+ end
198
+
199
+ # Shows default key for :batch option
200
+ # @return [Symbol, nil] default key for :batch option
201
+ def default_key
202
+ opts[:default_key]
203
+ end
204
+
205
+ # @param value [Symbol] New :default_key option value
206
+ # @return [Boolean] New option value
207
+ def default_key=(value)
208
+ raise SeregaError, "Must be a Symbol, #{value.inspect} provided" unless value.is_a?(Symbol)
209
+ opts[:default_key] = value
146
210
  end
147
211
  end
148
212
 
@@ -155,10 +219,10 @@ class Serega
155
219
  #
156
220
  # Returns all batch loaders registered for current serializer
157
221
  #
158
- # @return [Serega::SeregaPlugins::Batch::BatchLoadersConfig] configuration for batch loaders
222
+ # @return [Serega::SeregaPlugins::Batch::BatchConfig] configuration for batch loaded attributes
159
223
  #
160
- def batch_loaders
161
- @batch_loaders ||= BatchLoadersConfig.new(opts.fetch(:batch).fetch(:loaders))
224
+ def batch
225
+ @batch ||= BatchConfig.new(opts.fetch(:batch))
162
226
  end
163
227
  end
164
228
 
@@ -194,7 +258,7 @@ class Serega
194
258
  def check_opts
195
259
  super
196
260
 
197
- CheckOptBatch.call(opts, block)
261
+ CheckOptBatch.call(opts, block, self.class.serializer_class)
198
262
  end
199
263
  end
200
264
 
@@ -207,29 +271,111 @@ class Serega
207
271
  #
208
272
  # @return [nil, Hash] :batch option
209
273
  #
210
- def batch
211
- opts[:batch]
274
+ attr_reader :batch
275
+
276
+ private
277
+
278
+ def set_normalized_vars(normalizer)
279
+ super
280
+ @batch = normalizer.batch
212
281
  end
213
282
  end
214
283
 
215
284
  #
216
- # Serega::SeregaMapPoint additional/patched class methods
285
+ # SeregaAttributeNormalizer additional/patched instance methods
217
286
  #
218
- # @see SeregaAttribute
287
+ # @see SeregaAttributeNormalizer::AttributeInstanceMethods
219
288
  #
220
- module MapPointInstanceMethods
289
+ module AttributeNormalizerInstanceMethods
221
290
  #
222
- # Returns BatchOptionModel, an object that combines options and methods needed to load batch
291
+ # Returns normalized attribute :batch option with prepared :key and
292
+ # :default options. Option :loader will be prepared at serialization
293
+ # time as loaders are usually defined after attributes.
223
294
  #
224
- # @return [BatchOptionModel] Class that combines options and methods needed to load batch.
295
+ # @return [Hash] attribute :batch normalized options
225
296
  #
226
297
  def batch
227
298
  return @batch if instance_variable_defined?(:@batch)
228
299
 
229
- @batch = begin
230
- opts = attribute.batch
231
- BatchOptionModel.new(attribute) if opts
300
+ @batch = prepare_batch
301
+ end
302
+
303
+ private
304
+
305
+ #
306
+ # Patch for original `prepare_hide` method
307
+ #
308
+ # Marks attribute hidden if auto_hide option was set and attribute has batch loader
309
+ #
310
+ def prepare_hide
311
+ res = super
312
+ return res unless res.nil?
313
+
314
+ if batch
315
+ self.class.serializer_class.config.batch.auto_hide || nil
316
+ end
317
+ end
318
+
319
+ def prepare_batch
320
+ batch = init_opts[:batch]
321
+ return unless batch
322
+
323
+ # take loader
324
+ loader = batch[:loader]
325
+
326
+ # take key
327
+ key = batch[:key] || self.class.serializer_class.config.batch.default_key
328
+ proc_key =
329
+ if key.is_a?(Symbol)
330
+ proc do |object|
331
+ handle_no_method_error { object.public_send(key) }
332
+ end
333
+ else
334
+ key
335
+ end
336
+
337
+ # take default value
338
+ default = batch.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
339
+
340
+ {loader: loader, key: proc_key, default: default}
341
+ end
342
+
343
+ def handle_no_method_error
344
+ yield
345
+ rescue NoMethodError => error
346
+ raise error, "NoMethodError when serializing '#{name}' attribute in #{self.class.serializer_class}\n\n#{error.message}", error.backtrace
347
+ end
348
+ end
349
+
350
+ #
351
+ # Serega::SeregaPlanPoint additional/patched class methods
352
+ #
353
+ # @see SeregaAttribute
354
+ #
355
+ module PlanPointInstanceMethods
356
+ #
357
+ # Returns attribute :batch option with prepared loader
358
+ # @return [Hash] attribute :batch option
359
+ #
360
+ attr_reader :batch
361
+
362
+ private
363
+
364
+ def set_normalized_vars
365
+ super
366
+ @batch = prepare_batch
367
+ end
368
+
369
+ def prepare_batch
370
+ batch = attribute.batch
371
+ if batch
372
+ loader = batch[:loader]
373
+ if loader.is_a?(Symbol)
374
+ batch_config = attribute.class.serializer_class.config.batch
375
+ batch[:loader] = batch_config.fetch_loader(loader)
376
+ end
232
377
  end
378
+ batch
233
379
  end
234
380
  end
235
381
 
@@ -268,7 +414,7 @@ class Serega
268
414
  end
269
415
 
270
416
  def remember_key_for_batch_loading(batch, object, point, container)
271
- key = batch.key.call(object, context)
417
+ key = batch[:key].call(object, context)
272
418
  opts[:batch_loaders].get(point, self).remember(key, container)
273
419
  container[point.name] = nil # Reserve attribute place in resulted hash. We will set correct value later
274
420
  end
@@ -12,7 +12,7 @@ class Serega
12
12
  # Batch Loader instance methods
13
13
  #
14
14
  module InstanceMethods
15
- # @return [Serega::SeregaMapPoint]
15
+ # @return [Serega::SeregaPlanPoint]
16
16
  attr_reader :point
17
17
 
18
18
  # @return [Serega::SeregaObjectSerializer]
@@ -22,7 +22,7 @@ class Serega
22
22
  # Initializes new SeregaBatchLoader
23
23
  #
24
24
  # @param object_serializer [Serega::SeregaObjectSerializer]
25
- # @param point [Serega::SeregaMapPoint]
25
+ # @param point [Serega::SeregaPlanPoint]
26
26
  #
27
27
  # @return [Serega::SeregaPlugins::Batch::SeregaBatchLoader]
28
28
  #
@@ -52,7 +52,7 @@ class Serega
52
52
  keys_values = keys_values()
53
53
 
54
54
  each_key do |key, container|
55
- value = keys_values.fetch(key) { point.batch.default_value }
55
+ value = keys_values.fetch(key) { point.batch[:default] }
56
56
  final_value = object_serializer.__send__(:final_value, value, point)
57
57
  object_serializer.__send__(:attach_final_value, final_value, point, container)
58
58
  end
@@ -74,7 +74,7 @@ class Serega
74
74
  def keys_values
75
75
  ids = keys.keys
76
76
 
77
- point.batch.loader.call(ids, object_serializer.context, point).tap do |vals|
77
+ point.batch[:loader].call(ids, object_serializer.context, point).tap do |vals|
78
78
  next if vals.is_a?(Hash)
79
79
 
80
80
  attribute_name = "#{point.class.serializer_class}.#{point.name}"
@@ -10,13 +10,13 @@ class Serega
10
10
  #
11
11
  # Initializes or fetches already initialized batch loader
12
12
  #
13
- # @param map_point [Serega::SeregaMapPoint] current map point
13
+ # @param plan_point [Serega::SeregaPlanPoint] current plan point
14
14
  # @param object_serializer[Serega::SeregaObjectSerializer] current object serializer
15
15
  #
16
16
  # @return [Serega::SeregaPlugins::Batch::SeregaBatchLoader] Batch Loader
17
17
  #
18
- def get(map_point, object_serializer)
19
- batch_loaders[map_point] ||= self.class.serializer_class::SeregaBatchLoader.new(object_serializer, map_point)
18
+ def get(plan_point, object_serializer)
19
+ batch_loaders[plan_point] ||= self.class.serializer_class::SeregaBatchLoader.new(object_serializer, plan_point)
20
20
  end
21
21
 
22
22
  #
@@ -20,10 +20,10 @@ class Serega
20
20
  private
21
21
 
22
22
  # Preloads required associations to batch-loaded records
23
- def keys_values(*)
23
+ def keys_values
24
24
  data = super
25
25
 
26
- if point.has_nested_points?
26
+ if point.child_plan
27
27
  associations = point.preloads
28
28
  return data if associations.empty?
29
29
 
@@ -20,7 +20,7 @@ class Serega
20
20
  private
21
21
 
22
22
  # Format values after they are prepared
23
- def keys_values(*)
23
+ def keys_values
24
24
  data = super
25
25
 
26
26
  formatter = point.attribute.formatter
@@ -29,6 +29,24 @@ class Serega
29
29
  data
30
30
  end
31
31
  end
32
+
33
+ #
34
+ # SeregaAttribute additional/patched instance methods
35
+ #
36
+ # @see Serega::SeregaAttribute
37
+ #
38
+ module SeregaAttributeInstanceMethods
39
+ # Normalized formatter block or callable instance
40
+ # @return [#call, nil] Normalized formatter block or callable instance
41
+ attr_reader :formatter
42
+
43
+ private
44
+
45
+ def set_normalized_vars(normalizer)
46
+ super
47
+ @formatter = normalizer.formatter
48
+ end
49
+ end
32
50
  end
33
51
  end
34
52
  end
@@ -14,14 +14,16 @@ class Serega
14
14
  #
15
15
  # Attribute additional/patched instance methods
16
16
  #
17
- # @see Serega::SeregaPlugins::Preloads::AttributeInstanceMethods
17
+ # @see Serega::SeregaPlugins::Preloads::AttributeNormalizerInstanceMethods
18
18
  #
19
- module AttributeInstanceMethods
19
+ module AttributeNormalizerInstanceMethods
20
20
  private
21
21
 
22
22
  # Do not add any preloads automatically when batch option provided
23
- def get_preloads
24
- return if batch && !opts.key?(:preload)
23
+ def prepare_preloads
24
+ opts = init_opts
25
+ return if opts.key?(:batch) && !opts.key?(:preload)
26
+
25
27
  super
26
28
  end
27
29
  end
@@ -17,7 +17,7 @@ class Serega
17
17
  #
18
18
  # @return [void]
19
19
  #
20
- def call(opts, block)
20
+ def call(opts, block, serializer_class)
21
21
  return unless opts.key?(:batch)
22
22
 
23
23
  SeregaValidations::Utils::CheckOptIsHash.call(opts, :batch)
@@ -25,21 +25,25 @@ class Serega
25
25
  batch = opts[:batch]
26
26
  SeregaValidations::Utils::CheckAllowedKeys.call(batch, %i[key loader default])
27
27
 
28
- check_batch_opt_key(batch[:key])
29
- check_batch_opt_loader(batch[:loader])
28
+ check_batch_opt_key(batch, serializer_class)
29
+ check_batch_opt_loader(batch)
30
30
 
31
31
  check_usage_with_other_params(opts, block)
32
32
  end
33
33
 
34
34
  private
35
35
 
36
- def check_batch_opt_key(key)
36
+ def check_batch_opt_key(batch, serializer_class)
37
+ return if !batch.key?(:key) && serializer_class.config.batch.default_key
38
+
39
+ key = batch[:key]
37
40
  raise SeregaError, "Option :key must present inside :batch option" unless key
38
41
 
39
42
  CheckBatchOptKey.call(key)
40
43
  end
41
44
 
42
- def check_batch_opt_loader(loader)
45
+ def check_batch_opt_loader(batch)
46
+ loader = batch[:loader]
43
47
  raise SeregaError, "Option :loader must present inside :batch option" unless loader
44
48
 
45
49
  CheckBatchOptLoader.call(loader)
@@ -67,7 +67,7 @@ class Serega
67
67
  #
68
68
  def self.load_plugin(serializer_class, **_opts)
69
69
  serializer_class::SeregaConfig.include(ConfigInstanceMethods)
70
- serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
70
+ serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
71
71
  end
72
72
 
73
73
  #
@@ -115,10 +115,10 @@ class Serega
115
115
  #
116
116
  # Config class additional/patched instance methods
117
117
  #
118
- # @see Serega::SeregaConfig
118
+ # @see SeregaConfig
119
119
  #
120
120
  module ConfigInstanceMethods
121
- # @return [Serega::SeregaPlugins::Formatters::FormattersConfig] current formatters config
121
+ # @return [SeregaPlugins::Formatters::FormattersConfig] current formatters config
122
122
  def formatters
123
123
  @formatters ||= FormattersConfig.new(opts.fetch(:formatters))
124
124
  end
@@ -127,46 +127,43 @@ class Serega
127
127
  #
128
128
  # Attribute class additional/patched instance methods
129
129
  #
130
- # @see Serega::SeregaAttribute
130
+ # @see SeregaAttributeNormalizer
131
131
  #
132
- module AttributeInstanceMethods
133
- # @return [#call] callable formatter
132
+ module AttributeNormalizerInstanceMethods
133
+ # Block or callable instance that will format attribute values
134
+ # @return [Proc, #call, nil] Block or callable instance that will format attribute values
134
135
  def formatter
135
136
  return @formatter if instance_variable_defined?(:@formatter)
136
137
 
137
- @formatter = formatter_resolved
138
+ @formatter = prepare_formatter
138
139
  end
139
140
 
140
- # @return [#call] callable, that should be used to fetch attribute value
141
- def value_block
142
- return @value_block if instance_variable_defined?(:@value_block)
143
- return @value_block = super unless formatter
144
-
145
- new_value_block = formatted_block(formatter, super)
141
+ private
146
142
 
147
- # Format :const value in advance
148
- if opts.key?(:const)
149
- const_value = new_value_block.call
150
- new_value_block = proc { const_value }
143
+ def prepare_value_block
144
+ return super unless formatter
145
+
146
+ if init_opts.key?(:const)
147
+ # Format const value in advance
148
+ const_value = formatter.call(init_opts[:const])
149
+ proc { const_value }
150
+ else
151
+ # Wrap original block into formatter block
152
+ proc do |object, context|
153
+ value = super.call(object, context)
154
+ formatter.call(value)
155
+ end
151
156
  end
152
-
153
- @value_block = new_value_block
154
157
  end
155
158
 
156
- private
157
-
158
- def formatter_resolved
159
- formatter = opts[:format]
159
+ def prepare_formatter
160
+ formatter = init_opts[:format]
160
161
  return unless formatter
161
162
 
162
- formatter = self.class.serializer_class.config.formatters.opts.fetch(formatter) if formatter.is_a?(Symbol)
163
- formatter
164
- end
165
-
166
- def formatted_block(formatter, original_block)
167
- proc do |object, context|
168
- value = original_block.call(object, context)
169
- formatter.call(value)
163
+ if formatter.is_a?(Symbol)
164
+ self.class.serializer_class.config.formatters.opts.fetch(formatter)
165
+ else
166
+ formatter # already callable
170
167
  end
171
168
  end
172
169
  end
@@ -61,7 +61,8 @@ class Serega
61
61
  require_relative "./validations/check_opt_unless"
62
62
  require_relative "./validations/check_opt_unless_value"
63
63
 
64
- serializer_class::SeregaMapPoint.include(MapPointInstanceMethods)
64
+ serializer_class::SeregaAttribute.include(SeregaAttributeInstanceMethods)
65
+ serializer_class::SeregaPlanPoint.include(PlanPointInstanceMethods)
65
66
  serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
66
67
  serializer_class::SeregaObjectSerializer.include(SeregaObjectSerializerInstanceMethods)
67
68
  end
@@ -79,11 +80,28 @@ class Serega
79
80
  end
80
81
 
81
82
  #
82
- # Serega::SeregaMapPoint additional/patched instance methods
83
+ # SeregaAttribute additional/patched instance methods
83
84
  #
84
- # @see Serega::SeregaMapPoint::InstanceMethods
85
+ # @see Serega::SeregaAttribute
85
86
  #
86
- module MapPointInstanceMethods
87
+ module SeregaAttributeInstanceMethods
88
+ # @return provided :if options
89
+ attr_reader :opt_if
90
+
91
+ private
92
+
93
+ def set_normalized_vars(normalizer)
94
+ super
95
+ @opt_if = initials[:opts].slice(:if, :if_value, :unless, :unless_value).freeze
96
+ end
97
+ end
98
+
99
+ #
100
+ # Serega::SeregaPlanPoint additional/patched instance methods
101
+ #
102
+ # @see Serega::SeregaPlanPoint::InstanceMethods
103
+ #
104
+ module PlanPointInstanceMethods
87
105
  #
88
106
  # @return [Boolean] Should we show attribute or not
89
107
  # Conditions for this checks are specified by :if and :unless attribute options.
@@ -103,8 +121,8 @@ class Serega
103
121
  private
104
122
 
105
123
  def check_if_unless(obj, ctx, opt_if_name, opt_unless_name)
106
- opt_if = attribute.opts[opt_if_name]
107
- opt_unless = attribute.opts[opt_unless_name]
124
+ opt_if = attribute.opt_if[opt_if_name]
125
+ opt_unless = attribute.opt_if[opt_unless_name]
108
126
  return true if opt_if.nil? && opt_unless.nil?
109
127
 
110
128
  res_if =