serega 0.8.0 → 0.8.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2ebcd147c1714ebb2b4e923ee179c788158b225ac76791817229a82975926ec
4
- data.tar.gz: eef2621b3d6b9ac6ee1da3de29d3006ff99a0c2c91082cdc7795e82665ee92e5
3
+ metadata.gz: 0d467016ac66fcd328919f3d7068dd556ac5a7768f657adb30948c2c316480a1
4
+ data.tar.gz: 3ae0c46dc6760cdf94299c0edc34b41a8b195cea92dff13b91a4017bb21653f7
5
5
  SHA512:
6
- metadata.gz: 7891e792121105b7ee566b926347a63d82fb9fae186b7cf97766d4450141538d81dbb3bf7ac571b2ff7ed12bf8567f35d3e769a68e8979e47aad5c1c99be0300
7
- data.tar.gz: 3dd972104c307eff809f5c41af005fb75fdd7caa6d6a494b90a8c63e89a0e25dc89de1547b762c6ad1f7ab3aa5e5f5811b470a6f20adaed55c7155c17fc00207
6
+ metadata.gz: 9f2a9f714be636a26f4ca2de8a5f692455080354f495dc5cc2d69dd874d207ddd0bc3286b47ea600ebf5a0ba2ddfd774d6876da67e82906d35319f4a54614caa
7
+ data.tar.gz: 766931dbb8aaa415dcc008a477258ba6f63ca35b664267278ee31ab0becc91f124e16eb1f57bd5968df03e333bb59c98a8396f796be84c254a76e674a472d6e1
data/README.md CHANGED
@@ -150,7 +150,7 @@ UserSerializer.as_json([user]) # => [{"username":"serega"}]
150
150
 
151
151
  Modifiers can be provided as Hash, Array, String, Symbol or their combinations.
152
152
 
153
- With plugin [string_modifiers][string_modifiers] we can provide modifiers as single `String` with attributes split by comma `,` and nested values inside brackets `()`, like: `username,enemies(username,email)`. This can be very useful to accept list of field in **GET** requests.
153
+ With plugin [string_modifiers][string_modifiers] we can provide modifiers as single `String` with attributes split by comma `,` and nested values inside brackets `()`, like: `username,enemies(username,email)`. This can be very useful to accept list of fields in **GET** requests.
154
154
 
155
155
  When provided non-existing attribute, `Serega::AttributeNotExist` error will be raised. This error can be muted with `check_initiate_params: false` parameter.
156
156
 
@@ -258,9 +258,9 @@ Allows to define `:preloads` to attributes and then allows to merge preloads
258
258
  from serialized attributes and return single associations hash.
259
259
 
260
260
  Plugin accepts options:
261
- - `auto_preload_attributes_with_delegate` - default false
262
- - `auto_preload_attributes_with_serializer` - default false
263
- - `auto_hide_attributes_with_preload` - default false
261
+ - `auto_preload_attributes_with_delegate` - default `false`
262
+ - `auto_preload_attributes_with_serializer` - default `false`
263
+ - `auto_hide_attributes_with_preload` - default `false`
264
264
 
265
265
  This options are very handy if you want to forget about finding preloads manually.
266
266
 
@@ -373,13 +373,12 @@ Added new `:batch` attribute option, example:
373
373
  attribute :name, batch: { key: :id, loader: :name_loader, default: '' }
374
374
  ```
375
375
 
376
- `:batch` option must be a hash with this keys:
377
- - :key (required) [Symbol, Proc, callable] - Defines identifier of current object
378
- - :loader (required) [Symbol, Proc, callable] - Defines how to fetch values for batch of keys. Accepts 3 parameters: keys, context, point.
379
- - :default (optional) - Default value used when loader does not return value for current key. By default it is `nil` or `[]` when attribute has additional option `many: true` (`attribute :name, many: true, batch: { ... }`).
376
+ Attribute `:batch` option must be a hash with this keys:
377
+ - `key` (required) [Symbol, Proc, callable] - Defines identifier of current object
378
+ - `loader` (required) [Symbol, Proc, callable] - Defines how to fetch values for batch of keys. Accepts 3 parameters: keys, context, point.
379
+ - `default` (optional) - Default value used when loader does not return value for current key. By default it is `nil` or `[]` when attribute has additional option `many: true` (ex: `attribute :name, many: true, batch: { ... }`).
380
380
 
381
- If `:loader` was defined via Symbol then batch loader must be defined using `config.batch_loaders.define(:loader_name) { ... }` method.
382
- Result of this block must be a Hash where keys are - provided keys, and values are - batch loaded values for according keys.
381
+ If `:loader` was defined using name (as Symbol) then batch loader must be defined using `config.batch_loaders.define(:loader_name) { ... }` method. Result of this block must be a Hash where keys are - provided keys, and values are - batch loaded values for according keys.
383
382
 
384
383
  Batch loader works well with [`activerecord_preloads`][activerecord_preloads] plugin.
385
384
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.0
1
+ 0.8.2
@@ -136,7 +136,9 @@ class Serega
136
136
 
137
137
  def keyword_block
138
138
  key_method_name = key
139
- proc { |object| object.public_send(key_method_name) }
139
+ proc do |object|
140
+ handle_no_method_error { object.public_send(key_method_name) }
141
+ end
140
142
  end
141
143
 
142
144
  def delegate_block
@@ -147,11 +149,25 @@ class Serega
147
149
  delegate_to = delegate[:to]
148
150
 
149
151
  if delegate[:allow_nil]
150
- proc { |object| object.public_send(delegate_to)&.public_send(key_method_name) }
152
+ proc do |object|
153
+ handle_no_method_error do
154
+ object.public_send(delegate_to)&.public_send(key_method_name)
155
+ end
156
+ end
151
157
  else
152
- proc { |object| object.public_send(delegate_to).public_send(key_method_name) }
158
+ proc do |object|
159
+ handle_no_method_error do
160
+ object.public_send(delegate_to).public_send(key_method_name)
161
+ end
162
+ end
153
163
  end
154
164
  end
165
+
166
+ def handle_no_method_error
167
+ yield
168
+ rescue NoMethodError => error
169
+ raise error, "NoMethodError when serializing '#{name}' attribute in #{self.class.serializer_class}\n\n#{error.message}", error.backtrace
170
+ end
155
171
  end
156
172
 
157
173
  extend Serega::SeregaHelpers::SerializerClassHelper
@@ -50,6 +50,7 @@ class Serega
50
50
  # @return [void]
51
51
  #
52
52
  def self.load_plugin(serializer_class, **_opts)
53
+ require_relative "./lib/batch_option_model"
53
54
  require_relative "./lib/loader"
54
55
  require_relative "./lib/loaders"
55
56
  require_relative "./lib/validations/check_batch_opt_key"
@@ -87,14 +88,19 @@ class Serega
87
88
  serializer_class.const_set(:SeregaBatchLoader, batch_loader_class)
88
89
 
89
90
  if serializer_class.plugin_used?(:activerecord_preloads)
90
- require_relative "./lib/plugins_extensions"
91
+ require_relative "./lib/plugins_extensions/activerecord_preloads"
91
92
  serializer_class::SeregaBatchLoader.include(PluginsExtensions::ActiveRecordPreloads::BatchLoaderInstanceMethods)
92
93
  end
93
94
 
94
95
  if serializer_class.plugin_used?(:formatters)
95
- require_relative "./lib/plugins_extensions"
96
+ require_relative "./lib/plugins_extensions/formatters"
96
97
  serializer_class::SeregaBatchLoader.include(PluginsExtensions::Formatters::BatchLoaderInstanceMethods)
97
98
  end
99
+
100
+ if serializer_class.plugin_used?(:preloads)
101
+ require_relative "./lib/plugins_extensions/preloads"
102
+ serializer_class::SeregaAttribute.include(PluginsExtensions::Preloads::AttributeInstanceMethods)
103
+ end
98
104
  end
99
105
 
100
106
  #
@@ -140,56 +146,6 @@ class Serega
140
146
  end
141
147
  end
142
148
 
143
- #
144
- # Stores batch config for specific attribute
145
- #
146
- class BatchModel
147
- attr_reader :opts, :loaders, :many
148
-
149
- #
150
- # Initializes batch model
151
- #
152
- # @param opts [Hash] Attribute :batch option
153
- # @param loaders [Array] Array of all loaders defined in serialize class
154
- # @param many [Boolean] Option :many, defined on attribute
155
- #
156
- # @return [void]
157
- def initialize(opts, loaders, many)
158
- @opts = opts
159
- @loaders = loaders
160
- @many = many
161
- end
162
-
163
- # Returns proc that will be used to batch load registered keys values
164
- # @return [#call] batch loader
165
- def loader
166
- @batch_loader ||= begin
167
- loader = opts[:loader]
168
- loader = loaders.fetch(loader) if loader.is_a?(Symbol)
169
- loader
170
- end
171
- end
172
-
173
- # Returns proc that will be used to find batch_key for current attribute.
174
- # @return [Object] key (uid) of batch loaded object
175
- def key
176
- @batch_key ||= begin
177
- key = opts[:key]
178
- key.is_a?(Symbol) ? proc { |object| object.public_send(key) } : key
179
- end
180
- end
181
-
182
- # Returns default value to use if batch loader does not return value for some key
183
- # @return [Object] default value for missing key
184
- def default_value
185
- if opts.key?(:default)
186
- opts[:default]
187
- elsif many
188
- FROZEN_EMPTY_ARRAY
189
- end
190
- end
191
- end
192
-
193
149
  #
194
150
  # Config class additional/patched instance methods
195
151
  #
@@ -263,16 +219,16 @@ class Serega
263
219
  #
264
220
  module MapPointInstanceMethods
265
221
  #
266
- # Returns BatchModel, an object that encapsulates all batch_loader methods for current point
222
+ # Returns BatchOptionModel, an object that combines options and methods needed to load batch
267
223
  #
268
- # @return [BatchModel] batch model that encapsulates everything needed to load current batch
224
+ # @return [BatchOptionModel] Class that combines options and methods needed to load batch.
269
225
  #
270
226
  def batch
271
227
  return @batch if instance_variable_defined?(:@batch)
272
228
 
273
229
  @batch = begin
274
230
  opts = attribute.batch
275
- BatchModel.new(opts, self.class.serializer_class.config.batch_loaders, many) if opts
231
+ BatchOptionModel.new(attribute) if opts
276
232
  end
277
233
  end
278
234
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Batch
6
+ #
7
+ # Combines options and methods needed to load batch for specific attribute
8
+ #
9
+ class BatchOptionModel
10
+ attr_reader :attribute, :opts, :loaders, :many
11
+
12
+ #
13
+ # Initializes BatchOptionModel
14
+ #
15
+ # @param map_point [Serega::SeregaMapPoint] Map point for attribute with :batch option
16
+ # @param loaders [Array] Array of all loaders defined in serialize class
17
+ # @param many [Boolean] Option :many, defined on attribute
18
+ #
19
+ # @return [void]
20
+ def initialize(attribute)
21
+ @attribute = attribute
22
+ @opts = attribute.batch
23
+ @loaders = attribute.class.serializer_class.config.batch_loaders
24
+ @many = attribute.many
25
+ end
26
+
27
+ # Returns proc that will be used to batch load registered keys values
28
+ # @return [#call] batch loader
29
+ def loader
30
+ @batch_loader ||= begin
31
+ loader = opts[:loader]
32
+ loader = loaders.fetch(loader) if loader.is_a?(Symbol)
33
+ loader
34
+ end
35
+ end
36
+
37
+ # Returns proc that will be used to find batch_key for current attribute.
38
+ # @return [Object] key (uid) of batch loaded object
39
+ def key
40
+ @batch_key ||= begin
41
+ key = opts[:key]
42
+
43
+ if key.is_a?(Symbol)
44
+ proc do |object|
45
+ handle_no_method_error { object.public_send(key) }
46
+ end
47
+ else
48
+ key
49
+ end
50
+ end
51
+ end
52
+
53
+ # Returns default value to use if batch loader does not return value for some key
54
+ # @return [Object] default value for missing key
55
+ def default_value
56
+ if opts.key?(:default)
57
+ opts[:default]
58
+ elsif many
59
+ FROZEN_EMPTY_ARRAY
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def handle_no_method_error
66
+ yield
67
+ rescue NoMethodError => error
68
+ raise error, "NoMethodError when serializing '#{attribute.name}' attribute in #{attribute.class.serializer_class}\n\n#{error.message}", error.backtrace
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Batch
6
+ #
7
+ # Extensions (mini-plugins) that are enabled when :batch plugin used with other plugins
8
+ #
9
+ module PluginsExtensions
10
+ #
11
+ # Extension that is used when :batch plugin used with :active_record_preloads plugin
12
+ #
13
+ module ActiveRecordPreloads
14
+ #
15
+ # BatchLoader additional/patched instance methods
16
+ #
17
+ # @see Serega::SeregaPlugins::Batch::SeregaBatchLoader
18
+ #
19
+ module BatchLoaderInstanceMethods
20
+ private
21
+
22
+ # Preloads required associations to batch-loaded records
23
+ def keys_values(*)
24
+ data = super
25
+
26
+ if point.has_nested_points?
27
+ associations = point.preloads
28
+ return data if associations.empty?
29
+
30
+ records = data.values
31
+ records.flatten!(1)
32
+
33
+ ActiverecordPreloads::Preloader.preload(records, associations)
34
+ end
35
+
36
+ data
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Batch
6
+ #
7
+ # Extensions (mini-plugins) that are enabled when :batch plugin used with other plugins
8
+ #
9
+ module PluginsExtensions
10
+ #
11
+ # Extension that is used when batch plugin used with :formatters plugin
12
+ #
13
+ module Formatters
14
+ #
15
+ # BatchLoader additional/patched instance methods
16
+ #
17
+ # @see Serega::SeregaPlugins::Batch::SeregaBatchLoader
18
+ #
19
+ module BatchLoaderInstanceMethods
20
+ private
21
+
22
+ # Format values after they are prepared
23
+ def keys_values(*)
24
+ data = super
25
+
26
+ formatter = point.attribute.formatter
27
+ data.transform_values! { |value| formatter.call(value) } if formatter
28
+
29
+ data
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Batch
6
+ #
7
+ # Extensions (mini-plugins) that are enabled when :batch plugin used with other plugins
8
+ #
9
+ module PluginsExtensions
10
+ #
11
+ # Extension that is used when :preloads plugin is loaded
12
+ #
13
+ module Preloads
14
+ #
15
+ # Attribute additional/patched instance methods
16
+ #
17
+ # @see Serega::SeregaPlugins::Preloads::AttributeInstanceMethods
18
+ #
19
+ module AttributeInstanceMethods
20
+ private
21
+
22
+ # Do not add any preloads automatically when batch option provided
23
+ def get_preloads
24
+ return if batch && !opts.key?(:preload)
25
+ super
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -39,9 +39,7 @@ class Serega
39
39
  end
40
40
 
41
41
  def check_modifiers
42
- Initiate::CheckModifiers.call(serializer_class, opts[:only])
43
- Initiate::CheckModifiers.call(serializer_class, opts[:except])
44
- Initiate::CheckModifiers.call(serializer_class, opts[:with])
42
+ Initiate::CheckModifiers.new.call(serializer_class, opts[:only], opts[:with], opts[:except])
45
43
  end
46
44
 
47
45
  def serializer_class
@@ -10,55 +10,72 @@ class Serega
10
10
  # Modifiers validation
11
11
  #
12
12
  class CheckModifiers
13
- class << self
14
- # Validates provided fields names are existing attributes
15
- #
16
- # @param serializer_class [Serega]
17
- # @param fields [Hash] validated fields
18
- #
19
- # @raise [Serega::AttributeNotExist] when modifier not exist as attribute
20
- #
21
- # @return [void]
22
- #
23
- def call(serializer_class, fields)
24
- return unless fields
13
+ #
14
+ # Validates provided fields names are existing attributes
15
+ #
16
+ # @param serializer_class [Serega]
17
+ # @param only [Hash, nil] `only` modifier
18
+ # @param with [Hash, nil] `with` modifier
19
+ # @param except [Hash, nil] `except` modifier
20
+ #
21
+ # @raise [Serega::AttributeNotExist] when some checked modifier has not existing attribute
22
+ #
23
+ # @return [void]
24
+ #
25
+ def call(serializer_class, only, with, except)
26
+ validate(serializer_class, only) if only
27
+ validate(serializer_class, with) if with
28
+ validate(serializer_class, except) if except
25
29
 
26
- validate(serializer_class, fields, [])
27
- end
30
+ raise_errors if any_error?
31
+ end
28
32
 
29
- private
33
+ private
30
34
 
31
- def validate(serializer_class, fields, prev_names)
32
- fields.each do |name, nested_fields|
33
- attribute = serializer_class.attributes[name]
35
+ def validate(serializer_class, fields)
36
+ fields.each do |name, nested_fields|
37
+ attribute = serializer_class && serializer_class.attributes[name]
38
+
39
+ # Save error when no attribute with checked name exists
40
+ unless attribute
41
+ save_error(name)
42
+ next
43
+ end
34
44
 
35
- raise_error(name, prev_names) unless attribute
36
- next if nested_fields.empty?
45
+ # Return when attribute has no nested fields
46
+ next if nested_fields.empty?
37
47
 
38
- raise_nested_error(name, prev_names, nested_fields) unless attribute.relation?
39
- nested_serializer = attribute.serializer
40
- validate(nested_serializer, nested_fields, prev_names + [name])
48
+ with_parent_name(name) do
49
+ validate(attribute.serializer, nested_fields)
41
50
  end
42
51
  end
52
+ end
43
53
 
44
- def raise_error(name, prev_names)
45
- field_name = field_name(name, prev_names)
54
+ def parents_names
55
+ @parents_names ||= []
56
+ end
46
57
 
47
- raise Serega::AttributeNotExist, "Attribute #{field_name} not exists"
48
- end
58
+ def with_parent_name(name)
59
+ parents_names << name
60
+ yield
61
+ parents_names.pop
62
+ end
49
63
 
50
- def raise_nested_error(name, prev_names, nested_fields)
51
- field_name = field_name(name, prev_names)
52
- first_nested = nested_fields.keys.first
64
+ def error_attributes
65
+ @error_attributes ||= []
66
+ end
53
67
 
54
- raise Serega::AttributeNotExist, "Attribute #{field_name} has no :serializer option specified to add nested '#{first_nested}' attribute"
55
- end
68
+ def save_error(name)
69
+ full_attribute_name = [*parents_names, name].join(".")
70
+ error_attributes << full_attribute_name
71
+ end
56
72
 
57
- def field_name(name, prev_names)
58
- res = "'#{name}'"
59
- res += " ('#{prev_names.join(".")}.#{name}')" if prev_names.any?
60
- res
61
- end
73
+ def raise_errors
74
+ raise Serega::AttributeNotExist, "Not existing attributes: #{error_attributes.join(", ")}"
75
+ end
76
+
77
+ def any_error?
78
+ defined?(@error_attributes)
62
79
  end
63
80
  end
64
81
  end
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.8.0
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Glushkov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-08 00:00:00.000000000 Z
11
+ date: 2022-12-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  JSON Serializer
@@ -42,9 +42,12 @@ files:
42
42
  - lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb
43
43
  - lib/serega/plugins/activerecord_preloads/lib/preloader.rb
44
44
  - lib/serega/plugins/batch/batch.rb
45
+ - lib/serega/plugins/batch/lib/batch_option_model.rb
45
46
  - lib/serega/plugins/batch/lib/loader.rb
46
47
  - lib/serega/plugins/batch/lib/loaders.rb
47
- - lib/serega/plugins/batch/lib/plugins_extensions.rb
48
+ - lib/serega/plugins/batch/lib/plugins_extensions/activerecord_preloads.rb
49
+ - lib/serega/plugins/batch/lib/plugins_extensions/formatters.rb
50
+ - lib/serega/plugins/batch/lib/plugins_extensions/preloads.rb
48
51
  - lib/serega/plugins/batch/lib/validations/check_batch_opt_key.rb
49
52
  - lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb
50
53
  - lib/serega/plugins/batch/lib/validations/check_opt_batch.rb
@@ -93,8 +96,8 @@ homepage: https://github.com/aglushkov/serega
93
96
  licenses:
94
97
  - MIT
95
98
  metadata:
96
- homepage_uri: https://github.com/aglushkov/serega
97
99
  source_code_uri: https://github.com/aglushkov/serega
100
+ documentation_uri: https://www.rubydoc.info/gems/serega
98
101
  changelog_uri: https://github.com/aglushkov/serega/blob/master/CHANGELOG.md
99
102
  post_install_message:
100
103
  rdoc_options: []
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- module SeregaPlugins
5
- module Batch
6
- # Extensions (mini-plugins) that are enabled when Batch plugin used with other plugins
7
- module PluginsExtensions
8
- #
9
- # Extension that is used when batch plugin used with :active_record_preloads plugin
10
- #
11
- module ActiveRecordPreloads
12
- #
13
- # BatchLoader additional/patched instance methods
14
- #
15
- # @see Serega::SeregaPlugins::Batch::SeregaBatchLoader
16
- #
17
- module BatchLoaderInstanceMethods
18
- private
19
-
20
- # Preloads required associations to batch-loaded records
21
- def keys_values(*)
22
- data = super
23
-
24
- if point.has_nested_points?
25
- associations = point.preloads
26
- return data if associations.empty?
27
-
28
- ActiverecordPreloads::Preloader.preload(data.values.flatten(1), associations)
29
- end
30
-
31
- data
32
- end
33
- end
34
- end
35
-
36
- #
37
- # Extension that is used when batch plugin used with :formatters plugin
38
- #
39
- module Formatters
40
- #
41
- # BatchLoader additional/patched instance methods
42
- #
43
- # @see Serega::SeregaPlugins::Batch::SeregaBatchLoader
44
- #
45
- module BatchLoaderInstanceMethods
46
- private
47
-
48
- # Format values after they are prepared
49
- def keys_values(*)
50
- data = super
51
-
52
- formatter = point.attribute.formatter
53
- data.transform_values! { |value| formatter.call(value) } if formatter
54
-
55
- data
56
- end
57
- end
58
- end
59
- end
60
- end
61
- end
62
- end