serega 0.10.0 → 0.11.0

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