serega 0.17.0 → 0.19.0

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