serega 0.17.0 → 0.19.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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.0
1
+ 0.19.0
@@ -21,6 +21,10 @@ class Serega
21
21
  # @return [Boolean, nil] Attribute :many option
22
22
  attr_reader :many
23
23
 
24
+ # Attribute :default option
25
+ # @return [Object, nil] Attribute :default option
26
+ attr_reader :default
27
+
24
28
  # Attribute :hide option
25
29
  # @return [Boolean, nil] Attribute :hide option
26
30
  attr_reader :hide
@@ -108,9 +112,10 @@ class Serega
108
112
 
109
113
  def set_normalized_vars(normalizer)
110
114
  @name = normalizer.name
115
+ @many = normalizer.many
116
+ @default = normalizer.default
111
117
  @value_block = normalizer.value_block
112
118
  @hide = normalizer.hide
113
- @many = normalizer.many
114
119
  @serializer = normalizer.serializer
115
120
  end
116
121
  end
@@ -86,6 +86,19 @@ class Serega
86
86
  @serializer = prepare_serializer
87
87
  end
88
88
 
89
+ #
90
+ # Shows the default attribute value. It is a value that replaces found nils.
91
+ #
92
+ # When custom :default is not specified, we set empty array as default when `many: true` specified
93
+ #
94
+ # @return [Object] Attribute default value
95
+ #
96
+ def default
97
+ return @default if instance_variable_defined?(:@default)
98
+
99
+ @default = prepare_default
100
+ end
101
+
89
102
  private
90
103
 
91
104
  def prepare_name
@@ -97,11 +110,14 @@ class Serega
97
110
  # - plugin :formatters (wraps resulted block in formatter block and formats :const values)
98
111
  #
99
112
  def prepare_value_block
100
- prepare_init_block ||
113
+ value_block =
114
+ prepare_init_block ||
101
115
  prepare_value_option_block ||
102
116
  prepare_const_block ||
103
117
  prepare_delegate_block ||
104
118
  prepare_keyword_block
119
+
120
+ prepare_value_block_with_default(value_block)
105
121
  end
106
122
 
107
123
  #
@@ -158,6 +174,20 @@ class Serega
158
174
  end
159
175
  end
160
176
 
177
+ def prepare_value_block_with_default(callable)
178
+ default_value = default
179
+ return callable if default_value.nil?
180
+
181
+ proc { |obj, ctx|
182
+ res = callable.call(obj, ctx)
183
+ res.nil? ? default_value : res
184
+ }
185
+ end
186
+
187
+ def prepare_default
188
+ init_opts.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
189
+ end
190
+
161
191
  def prepare_delegate_block
162
192
  delegate = init_opts[:delegate]
163
193
  return unless delegate
data/lib/serega/config.rb CHANGED
@@ -15,7 +15,7 @@ class Serega
15
15
  DEFAULTS = {
16
16
  plugins: [],
17
17
  initiate_keys: %i[only with except check_initiate_params].freeze,
18
- attribute_keys: %i[method value serializer many hide const delegate].freeze,
18
+ attribute_keys: %i[method value serializer many hide const delegate default].freeze,
19
19
  serialize_keys: %i[context many].freeze,
20
20
  check_attribute_name: true,
21
21
  check_initiate_params: true,
@@ -29,7 +29,7 @@ class Serega
29
29
  #
30
30
  # @param object [Object] Serialized object
31
31
  #
32
- # @return [Hash, Array<Hash>] Serialized object(s)
32
+ # @return [Hash, Array<Hash>, nil] Serialized object(s)
33
33
  def serialize(object)
34
34
  return if object.nil?
35
35
 
@@ -5,62 +5,17 @@ class Serega
5
5
  #
6
6
  # Plugin `:batch`
7
7
  #
8
- # Adds ability to load nested attributes values in batches.
8
+ # Must be used to omit N+1 when loading attributes values.
9
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
10
+ # @example Quick example
14
11
  #
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.
39
- #
40
- # @example
41
- # class PostSerializer < Serega
42
- # plugin :batch, auto_hide: true, default_key: :id
43
- #
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}
46
- #
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}
49
- #
50
- # # Define batch loader with serializer
51
- # attribute :comments, serializer: CommentSerializer, batch: { loader: :posts_comments, default: []}
52
- #
53
- # # Resulted block must return hash like { key => value(s) }
54
- # config.batch.define(:posts_comments_counter) do |keys|
55
- # Comment.group(:post_id).where(post_id: keys).count
56
- # end
12
+ # class AppSerializer
13
+ # plugin :batch, id_method: :id
14
+ # end
57
15
  #
58
- # # We can return objects that will be automatically serialized if attribute defined with :serializer
59
- # # Parameter `context` can be used when loading batch
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|
62
- # Comment.where(post_id: keys).where(is_spam: false).group_by(&:post_id)
63
- # end
16
+ # class UserSerializer < AppSerializer
17
+ # attribute :comments_count, batch: { loader: CommentsCountBatchLoader }, default: 0
18
+ # attribute :company, serializer: CompanySerializer, batch: { loader: UserCompanyBatchLoader }
64
19
  # end
65
20
  #
66
21
  module Batch
@@ -88,7 +43,7 @@ class Serega
88
43
  require_relative "lib/modules/config"
89
44
  require_relative "lib/modules/object_serializer"
90
45
  require_relative "lib/modules/plan_point"
91
- require_relative "lib/validations/check_batch_opt_key"
46
+ require_relative "lib/validations/check_batch_opt_id_method"
92
47
  require_relative "lib/validations/check_batch_opt_loader"
93
48
  require_relative "lib/validations/check_opt_batch"
94
49
 
@@ -138,9 +93,9 @@ class Serega
138
93
 
139
94
  config = serializer_class.config
140
95
  config.attribute_keys << :batch
141
- config.opts[:batch] = {loaders: {}, default_key: nil, auto_hide: false}
96
+ config.opts[:batch] = {loaders: {}, id_method: nil, auto_hide: false}
142
97
  config.batch.auto_hide = opts[:auto_hide] if opts.key?(:auto_hide)
143
- config.batch.default_key = opts[:default_key] if opts.key?(:default_key)
98
+ config.batch.id_method = opts[:id_method] if opts.key?(:id_method)
144
99
  end
145
100
 
146
101
  #
@@ -17,8 +17,8 @@ class Serega
17
17
  # Defines batch loader
18
18
  #
19
19
  # @param loader_name [Symbol] Batch loader name, that is used when defining attribute with batch loader.
20
- # @param block [Proc] Block that can accept 3 parameters - keys, context, plan_point
21
- # and returns hash where ids are keys and values are batch loaded objects/
20
+ # @param block [Proc] Block that can accept 3 parameters - ids, context, plan_point
21
+ # and returns hash with ids as keys and values are batch loaded objects
22
22
  #
23
23
  # @return [void]
24
24
  #
@@ -32,7 +32,7 @@ class Serega
32
32
  params_count = SeregaUtils::ParamsCount.call(callable, max_count: 3)
33
33
 
34
34
  if params_count > 3
35
- raise SeregaError, "Batch loader can have maximum 3 parameters (keys, context, plan)"
35
+ raise SeregaError, "Batch loader can have maximum 3 parameters (ids, context, plan)"
36
36
  end
37
37
 
38
38
  loaders[loader_name] = callable
@@ -44,16 +44,6 @@ class Serega
44
44
  opts[:loaders]
45
45
  end
46
46
 
47
- #
48
- # Finds previously defined batch loader by name
49
- #
50
- # @param loader_name [Symbol]
51
- #
52
- # @return [Proc] batch loader block
53
- def fetch_loader(loader_name)
54
- loaders[loader_name] || (raise SeregaError, "Batch loader with name `#{loader_name.inspect}` was not defined. Define example: config.batch.define(:#{loader_name}) { |keys| ... }")
55
- end
56
-
57
47
  # Shows option to auto hide attributes with :batch specified
58
48
  # @return [Boolean, nil] option value
59
49
  def auto_hide
@@ -67,17 +57,19 @@ class Serega
67
57
  opts[:auto_hide] = value
68
58
  end
69
59
 
70
- # Shows default key for :batch option
71
- # @return [Symbol, nil] default key for :batch option
72
- def default_key
73
- opts[:default_key]
60
+ # Shows method name or callable object needed to get object identifier for batch load
61
+ # @return [Symbol, #call, nil] Default method name or callable object to get identifier
62
+ def id_method
63
+ opts[:id_method]
74
64
  end
75
65
 
76
- # @param value [Symbol] New :default_key option value
66
+ # Sets new identifier method name or callable value needed for batch loading
67
+ #
68
+ # @param value [Symbol, #call] New :id_method value
77
69
  # @return [Boolean] New option value
78
- def default_key=(value)
79
- raise SeregaError, "Must be a Symbol, #{value.inspect} provided" unless value.is_a?(Symbol)
80
- opts[:default_key] = value
70
+ def id_method=(value)
71
+ CheckBatchOptIdMethod.call(value)
72
+ opts[:id_method] = value
81
73
  end
82
74
  end
83
75
  end
@@ -44,27 +44,25 @@ class Serega
44
44
 
45
45
  loader = prepare_batch_loader(batch[:loader])
46
46
 
47
- key = batch[:key] || self.class.serializer_class.config.batch.default_key
48
- key = prepare_batch_key(key)
47
+ id_method = batch[:id_method] || self.class.serializer_class.config.batch.id_method
48
+ id_method = prepare_batch_id_method(id_method)
49
49
 
50
- default = batch.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
51
-
52
- {loader: loader, key: key, default: default}
50
+ {loader: loader, id_method: id_method, default: default}
53
51
  end
54
52
 
55
- def prepare_batch_key(key)
56
- return proc { |object| object.public_send(key) } if key.is_a?(Symbol)
53
+ def prepare_batch_id_method(id_method)
54
+ return proc { |object| object.public_send(id_method) } if id_method.is_a?(Symbol)
57
55
 
58
- params_count = SeregaUtils::ParamsCount.call(key, max_count: 2)
56
+ params_count = SeregaUtils::ParamsCount.call(id_method, max_count: 2)
59
57
  case params_count
60
- when 0 then proc { key.call }
61
- when 1 then proc { |object| key.call(object) }
62
- else key
58
+ when 0 then proc { id_method.call }
59
+ when 1 then proc { |object| id_method.call(object) }
60
+ else id_method
63
61
  end
64
62
  end
65
63
 
66
64
  def prepare_batch_loader(loader)
67
- return loader if loader.is_a?(Symbol)
65
+ loader = self.class.serializer_class.config.batch.loaders.fetch(loader) if loader.is_a?(Symbol)
68
66
 
69
67
  params_count = SeregaUtils::ParamsCount.call(loader, max_count: 3)
70
68
  case params_count
@@ -19,8 +19,8 @@ class Serega
19
19
  end
20
20
 
21
21
  def remember_key_for_batch_loading(batch, object, point, container)
22
- key = batch[:key].call(object, context)
23
- batch_loader(point).remember(key, container)
22
+ id = batch[:id_method].call(object, context)
23
+ batch_loader(point).remember(id, container)
24
24
  container[point.name] = nil # Reserve attribute place in resulted hash. We will set correct value later
25
25
  end
26
26
 
@@ -13,25 +13,8 @@ class Serega
13
13
  # Returns attribute :batch option with prepared loader
14
14
  # @return [Hash] attribute :batch option
15
15
  #
16
- attr_reader :batch
17
-
18
- private
19
-
20
- def set_normalized_vars
21
- super
22
- @batch = prepare_batch
23
- end
24
-
25
- def prepare_batch
26
- batch = attribute.batch
27
- if batch
28
- loader = batch[:loader]
29
- if loader.is_a?(Symbol)
30
- batch_config = attribute.class.serializer_class.config.batch
31
- batch[:loader] = batch_config.fetch_loader(loader)
32
- end
33
- end
34
- batch
16
+ def batch
17
+ attribute.batch
35
18
  end
36
19
  end
37
20
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Batch
6
+ #
7
+ # Validator for option :id_method in attribute :batch option
8
+ #
9
+ class CheckBatchOptIdMethod
10
+ class << self
11
+ #
12
+ # Checks option :id_method of attribute :batch option
13
+ #
14
+ # @param id [nil, #call] Attribute :batch option :id_method
15
+ #
16
+ # @raise [SeregaError] validation error
17
+ #
18
+ # @return [void]
19
+ #
20
+ def call(id)
21
+ return if id.is_a?(Symbol)
22
+
23
+ raise SeregaError, must_be_callable unless id.respond_to?(:call)
24
+
25
+ SeregaValidations::Utils::CheckExtraKeywordArg.call(id, "batch option :id_method")
26
+ params_count = SeregaUtils::ParamsCount.call(id, max_count: 2)
27
+ raise SeregaError, params_count_error if params_count > 2
28
+ end
29
+
30
+ private
31
+
32
+ def params_count_error
33
+ "Invalid :batch option :id_method. It can accept maximum 2 parameters (object, context)"
34
+ end
35
+
36
+ def must_be_callable
37
+ "Invalid :batch option :id_method. It must be a Symbol, a Proc or respond to #call"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -17,9 +17,27 @@ class Serega
17
17
  #
18
18
  # @return [void]
19
19
  #
20
- def call(loader)
21
- return if loader.is_a?(Symbol)
20
+ def call(loader, serializer_class)
21
+ if loader.is_a?(Symbol)
22
+ check_symbol(loader, serializer_class)
23
+ else
24
+ check_callable(loader)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def check_symbol(loader_name, serializer_class)
31
+ defined_loaders = serializer_class.config.batch.loaders
32
+ return if defined_loaders[loader_name]
22
33
 
34
+ raise SeregaError, <<~ERR.strip
35
+ Please define loader before adding it to attribute.
36
+ Example: `config.batch.define(:#{loader_name}) { |ids| ... }`
37
+ ERR
38
+ end
39
+
40
+ def check_callable(loader)
23
41
  raise SeregaError, must_be_callable unless loader.respond_to?(:call)
24
42
 
25
43
  SeregaValidations::Utils::CheckExtraKeywordArg.call(loader, ":batch option :loader")
@@ -27,10 +45,8 @@ class Serega
27
45
  raise SeregaError, params_count_error if params_count > 3
28
46
  end
29
47
 
30
- private
31
-
32
48
  def params_count_error
33
- "Invalid :batch option :loader. It can accept maximum 3 parameters (keys, context, plan)"
49
+ "Invalid :batch option :loader. It can accept maximum 3 parameters (ids, context, plan)"
34
50
  end
35
51
 
36
52
  def must_be_callable
@@ -23,30 +23,29 @@ class Serega
23
23
  SeregaValidations::Utils::CheckOptIsHash.call(opts, :batch)
24
24
 
25
25
  batch = opts[:batch]
26
- SeregaValidations::Utils::CheckAllowedKeys.call(batch, %i[key loader default], :batch)
27
-
28
- check_batch_opt_key(batch, serializer_class)
29
- check_batch_opt_loader(batch)
26
+ SeregaValidations::Utils::CheckAllowedKeys.call(batch, %i[id_method loader], :batch)
30
27
 
28
+ check_batch_opt_id_method(batch, serializer_class)
29
+ check_batch_opt_loader(batch, serializer_class)
31
30
  check_usage_with_other_params(opts, block)
32
31
  end
33
32
 
34
33
  private
35
34
 
36
- def check_batch_opt_key(batch, serializer_class)
37
- return if !batch.key?(:key) && serializer_class.config.batch.default_key
35
+ def check_batch_opt_id_method(batch, serializer_class)
36
+ return if !batch.key?(:id_method) && serializer_class.config.batch.id_method
38
37
 
39
- key = batch[:key]
40
- raise SeregaError, "Option :key must present inside :batch option" unless key
38
+ id_method = batch[:id_method]
39
+ raise SeregaError, "Option :id_method must present inside :batch option" unless id_method
41
40
 
42
- CheckBatchOptKey.call(key)
41
+ CheckBatchOptIdMethod.call(id_method)
43
42
  end
44
43
 
45
- def check_batch_opt_loader(batch)
44
+ def check_batch_opt_loader(batch, serializer_class)
46
45
  loader = batch[:loader]
47
46
  raise SeregaError, "Option :loader must present inside :batch option" unless loader
48
47
 
49
- CheckBatchOptLoader.call(loader)
48
+ CheckBatchOptLoader.call(loader, serializer_class)
50
49
  end
51
50
 
52
51
  def check_usage_with_other_params(opts, block)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serega
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Glushkov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-03 00:00:00.000000000 Z
11
+ date: 2023-12-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  JSON Serializer
@@ -55,7 +55,7 @@ files:
55
55
  - lib/serega/plugins/batch/lib/plugins_extensions/activerecord_preloads.rb
56
56
  - lib/serega/plugins/batch/lib/plugins_extensions/formatters.rb
57
57
  - lib/serega/plugins/batch/lib/plugins_extensions/preloads.rb
58
- - lib/serega/plugins/batch/lib/validations/check_batch_opt_key.rb
58
+ - lib/serega/plugins/batch/lib/validations/check_batch_opt_id_method.rb
59
59
  - lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb
60
60
  - lib/serega/plugins/batch/lib/validations/check_opt_batch.rb
61
61
  - lib/serega/plugins/camel_case/camel_case.rb
@@ -140,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
140
  - !ruby/object:Gem::Version
141
141
  version: '0'
142
142
  requirements: []
143
- rubygems_version: 3.4.21
143
+ rubygems_version: 3.4.22
144
144
  signing_key:
145
145
  specification_version: 4
146
146
  summary: JSON Serializer
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- module SeregaPlugins
5
- module Batch
6
- #
7
- # Validator for option :key in attribute :batch option
8
- #
9
- class CheckBatchOptKey
10
- class << self
11
- #
12
- # Checks option :key of attribute :batch option
13
- #
14
- # @param key [nil, #call] Attribute :batch option :key
15
- #
16
- # @raise [SeregaError] validation error
17
- #
18
- # @return [void]
19
- #
20
- def call(key)
21
- return if key.is_a?(Symbol)
22
-
23
- raise SeregaError, must_be_callable unless key.respond_to?(:call)
24
-
25
- SeregaValidations::Utils::CheckExtraKeywordArg.call(key, "batch option :key")
26
- params_count = SeregaUtils::ParamsCount.call(key, max_count: 2)
27
- raise SeregaError, params_count_error if params_count > 2
28
- end
29
-
30
- private
31
-
32
- def params_count_error
33
- "Invalid :batch option :key. It can accept maximum 2 parameters (object, context)"
34
- end
35
-
36
- def must_be_callable
37
- "Invalid :batch option :key. It must be a Symbol, a Proc or respond to :call"
38
- end
39
- end
40
- end
41
- end
42
- end
43
- end