serega 0.39.0 → 0.40.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.
- checksums.yaml +4 -4
- data/README.md +74 -2
- data/VERSION +1 -1
- data/lib/serega/attribute_normalizer.rb +59 -3
- data/lib/serega/attribute_value_resolvers/hash_access.rb +116 -0
- data/lib/serega/config.rb +60 -1
- data/lib/serega/plugins/presenter/presenter.rb +4 -0
- data/lib/serega/validations/attribute/check_opt_delegate.rb +42 -1
- data/lib/serega/validations/attribute/check_opt_hash_access.rb +78 -0
- data/lib/serega/validations/check_attribute_params.rb +1 -0
- data/lib/serega.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 42da0ff66dc5eeb282fbb72144f78bcfc8ded1aa8609122ded10093f6c83dd96
|
|
4
|
+
data.tar.gz: f9fb2a2628756540cd08a5449a21883b0cf28ec45db8f461ee0944fd0f3812e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1355573b7cb8d30ffbf844be46c03c4a5233fa9c190fa5e03fd39c0d1a396b9f7a19e7e2d4cf3afbd0445cd1d8dfc8ebbd1df6d77d91d338c0ede561c836fb99
|
|
7
|
+
data.tar.gz: 90ccd454b970598f9921ec1a6c9feab325a373d5628f884515141d34a3a72e7012273e37aa903046e064876f92bd956783ef3a9b93732ee23a8bcc8bec154ee3
|
data/README.md
CHANGED
|
@@ -26,6 +26,7 @@ It has some great features:
|
|
|
26
26
|
keeping the code dry
|
|
27
27
|
- Conditional attributes - ([if][if] plugin)
|
|
28
28
|
- Auto camelCase keys - [camel_case][camel_case] plugin
|
|
29
|
+
- Serializing Hash records - [hash_access][hash_access] attribute option
|
|
29
30
|
|
|
30
31
|
## Installation
|
|
31
32
|
|
|
@@ -74,8 +75,9 @@ class UserSerializer < Serega
|
|
|
74
75
|
# Regular attribute
|
|
75
76
|
attribute :first_name
|
|
76
77
|
|
|
77
|
-
# Option :method specifies the
|
|
78
|
-
# serialized object
|
|
78
|
+
# Option :method specifies the name used to read the attribute's value —
|
|
79
|
+
# a method called on the serialized object by default, or a Hash key when
|
|
80
|
+
# combined with :hash_access
|
|
79
81
|
attribute :first_name, method: :old_first_name
|
|
80
82
|
|
|
81
83
|
# Attribute blocks below require a base serializer for nested serializers.
|
|
@@ -494,6 +496,12 @@ class AppSerializer < Serega
|
|
|
494
496
|
# proc { |object, batches:| batches[:counter][object.id] }
|
|
495
497
|
config.batch_id_option = :id
|
|
496
498
|
|
|
499
|
+
# Defaults for the `hash_access:` attribute option — `default_mode` is
|
|
500
|
+
# what `hash_access: true` (and a Hash form omitting :mode) resolves to.
|
|
501
|
+
# See "Serializing Hash records".
|
|
502
|
+
config.hash_access.default_mode = :symbol # the default
|
|
503
|
+
config.hash_access.default_allow_missing_key = false # the default
|
|
504
|
+
|
|
497
505
|
# Parent class for nested serializers defined with attribute blocks.
|
|
498
506
|
# Usually a settings-only serializer, e.g. `config.base_serializer = self`
|
|
499
507
|
# in an application base serializer class. There is no default — an
|
|
@@ -664,6 +672,69 @@ end
|
|
|
664
672
|
|
|
665
673
|
---
|
|
666
674
|
|
|
675
|
+
## Serializing Hash records
|
|
676
|
+
|
|
677
|
+
(objects should implement `#[]`, `#fetch`, and `#key?` - Hash already has them)
|
|
678
|
+
|
|
679
|
+
The `hash_access: <mode>` attribute option makes an attribute read its value from
|
|
680
|
+
Hash keys instead of calling a method.
|
|
681
|
+
|
|
682
|
+
Allowed `<mode>`:
|
|
683
|
+
|
|
684
|
+
- `:symbol` reads record[:name]
|
|
685
|
+
- `:string` reads record["name"]
|
|
686
|
+
- `true` shorthand for `config.hash_access.default_mode` (`:symbol` by default)
|
|
687
|
+
|
|
688
|
+
By default missing keys will raise `KeyError`.
|
|
689
|
+
|
|
690
|
+
`config.hash_access.default_allow_missing_key = true` will change `KeyError`
|
|
691
|
+
to return `nil` value instead. `config.hash_access.default_mode = <mode>`
|
|
692
|
+
changes what `true` (and a Hash form omitting `:mode`) resolves to.
|
|
693
|
+
|
|
694
|
+
```ruby
|
|
695
|
+
class UserSerializer < Serega
|
|
696
|
+
config.hash_access.default_mode = :symbol # the default
|
|
697
|
+
config.hash_access.default_allow_missing_key = true # false by default
|
|
698
|
+
|
|
699
|
+
attribute :name, hash_access: true # reads record[:name] (config.hash_access.default_mode)
|
|
700
|
+
attribute :name, hash_access: :symbol # reads record[:name]
|
|
701
|
+
attribute :name, hash_access: :string # reads record["name"]
|
|
702
|
+
|
|
703
|
+
# Use long form if you need to override `allow_missing_key`
|
|
704
|
+
attribute :name, hash_access: { allow_missing_key: false }
|
|
705
|
+
attribute :name, hash_access: { mode: :symbol, allow_missing_key: false }
|
|
706
|
+
attribute :name, hash_access: { mode: :string, allow_missing_key: false }
|
|
707
|
+
end
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
Delegated attributes configure hash access **per step**:
|
|
711
|
+
|
|
712
|
+
- `to_hash_access` configures reading `<mode>` for intermediate object
|
|
713
|
+
- `hash_access` configures reading `<mode>` for final key
|
|
714
|
+
|
|
715
|
+
```ruby
|
|
716
|
+
# reads `record.address.city` (no hash access)
|
|
717
|
+
attribute :city, delegate: { to: :address } # record.address.city
|
|
718
|
+
|
|
719
|
+
# reads record.address[:city]
|
|
720
|
+
attribute :city, delegate: { to: :address, hash_access: :symbol }
|
|
721
|
+
|
|
722
|
+
# reads record[:address].city
|
|
723
|
+
attribute :city, delegate: { to: :address, to_hash_access: :symbol }
|
|
724
|
+
|
|
725
|
+
# reads record[:address][:city]
|
|
726
|
+
attribute :city, delegate: { to: :address, to_hash_access: :symbol, hash_access: :symbol }
|
|
727
|
+
|
|
728
|
+
# reads record["address"] and if exists returns record["address"][:city]
|
|
729
|
+
attribute :city,
|
|
730
|
+
delegate: {
|
|
731
|
+
to: :address,
|
|
732
|
+
allow_nil: true,
|
|
733
|
+
to_hash_access: { mode: :string, allow_missing_key: true },
|
|
734
|
+
hash_access: { mode: :symbol, allow_missing_key: false }
|
|
735
|
+
}
|
|
736
|
+
```
|
|
737
|
+
|
|
667
738
|
## Plugins
|
|
668
739
|
|
|
669
740
|
### Plugin :activerecord_preloads
|
|
@@ -1128,6 +1199,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
1128
1199
|
[context_metadata]: #plugin-context_metadata
|
|
1129
1200
|
[depth_limit]: #plugin-depth_limit
|
|
1130
1201
|
[formatters]: #plugin-formatters
|
|
1202
|
+
[hash_access]: #serializing-hash-records
|
|
1131
1203
|
[metadata]: #plugin-metadata
|
|
1132
1204
|
[preloads]: #preloads
|
|
1133
1205
|
[presenter]: #plugin-presenter
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.40.0
|
|
@@ -254,7 +254,10 @@ class Serega
|
|
|
254
254
|
end
|
|
255
255
|
|
|
256
256
|
def prepare_keyword_block
|
|
257
|
-
|
|
257
|
+
mode, allow_nil = hash_access
|
|
258
|
+
return AttributeValueResolvers::KeywordResolver.get(method_name) unless mode
|
|
259
|
+
|
|
260
|
+
AttributeValueResolvers::HashAccessResolver.get(method_name, mode, allow_nil)
|
|
258
261
|
end
|
|
259
262
|
|
|
260
263
|
def prepare_batch_loader_block
|
|
@@ -291,15 +294,68 @@ class Serega
|
|
|
291
294
|
init_opts.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
|
|
292
295
|
end
|
|
293
296
|
|
|
297
|
+
# `delegate: {to_hash_access: ...}` configures hash access for the
|
|
298
|
+
# intermediate (:to) read, `delegate: {hash_access: ...}` for the
|
|
299
|
+
# final (:method) read. A step without its sub-option keeps a plain
|
|
300
|
+
# method read.
|
|
294
301
|
def prepare_delegate_block
|
|
295
302
|
delegate = init_opts[:delegate]
|
|
296
303
|
return unless delegate
|
|
297
304
|
|
|
298
305
|
key_method_name = delegate[:method] || method_name
|
|
299
306
|
delegate_to = delegate[:to]
|
|
307
|
+
delegate_allow_nil = delegate.fetch(:allow_nil) { config.delegate_default_allow_nil }
|
|
308
|
+
|
|
309
|
+
to_access = delegate[:to_hash_access]
|
|
310
|
+
final_access = delegate[:hash_access]
|
|
311
|
+
unless to_access || final_access
|
|
312
|
+
return AttributeValueResolvers::DelegateResolver.get(delegate_to, key_method_name, delegate_allow_nil)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
to_step = delegate_to_step(delegate_to, to_access)
|
|
316
|
+
final_step = delegate_final_step(key_method_name, final_access)
|
|
317
|
+
AttributeValueResolvers::HashAccessDelegateResolver.get(to_step, final_step, delegate_allow_nil)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def delegate_to_step(delegate_to, access)
|
|
321
|
+
return AttributeValueResolvers::Keyword.new(delegate_to) unless access
|
|
322
|
+
|
|
323
|
+
mode, allow_missing_key = parse_hash_access(access)
|
|
324
|
+
AttributeValueResolvers::HashAccessKeyword.new(delegate_to, mode, allow_missing_key)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def delegate_final_step(key_method_name, access)
|
|
328
|
+
return AttributeValueResolvers::Keyword.new(key_method_name) unless access
|
|
300
329
|
|
|
301
|
-
|
|
302
|
-
AttributeValueResolvers::
|
|
330
|
+
mode, allow_missing_key = parse_hash_access(access)
|
|
331
|
+
AttributeValueResolvers::HashAccessKeyword.new(key_method_name, mode, allow_missing_key)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Resolves the :hash_access option of plain attributes
|
|
335
|
+
# @return [Array(Symbol, Boolean), nil] mode and allow_missing_key pair,
|
|
336
|
+
# or nil when hash access is not enabled for this attribute
|
|
337
|
+
def hash_access
|
|
338
|
+
option = init_opts[:hash_access]
|
|
339
|
+
return unless option
|
|
340
|
+
|
|
341
|
+
parse_hash_access(option)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Resolves a :hash_access value (`true`, a Symbol mode or a
|
|
345
|
+
# `{mode:, allow_missing_key:}` Hash) into a mode and allow_missing_key
|
|
346
|
+
# pair. `true` and a Hash omitting :mode resolve to
|
|
347
|
+
# `config.hash_access.default_mode`.
|
|
348
|
+
def parse_hash_access(option)
|
|
349
|
+
defaults = config.hash_access
|
|
350
|
+
|
|
351
|
+
case option
|
|
352
|
+
when true then [defaults.default_mode, defaults.default_allow_missing_key]
|
|
353
|
+
when Symbol then [option, defaults.default_allow_missing_key]
|
|
354
|
+
else
|
|
355
|
+
mode = option.fetch(:mode) { defaults.default_mode }
|
|
356
|
+
allow_missing_key = option.fetch(:allow_missing_key) { defaults.default_allow_missing_key }
|
|
357
|
+
[mode, allow_missing_key]
|
|
358
|
+
end
|
|
303
359
|
end
|
|
304
360
|
|
|
305
361
|
# Prepares preloads for this attribute.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Serega
|
|
4
|
+
module AttributeValueResolvers
|
|
5
|
+
#
|
|
6
|
+
# Builds value resolver for attributes with the :hash_access option
|
|
7
|
+
#
|
|
8
|
+
class HashAccessResolver
|
|
9
|
+
# Allowed hash access modes
|
|
10
|
+
MODES = %i[symbol string].freeze
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Creates resolver that reads a key from Hash records
|
|
14
|
+
#
|
|
15
|
+
# @param name [Symbol, String] hash key
|
|
16
|
+
# @param mode [Symbol] hash access mode (:symbol, :string)
|
|
17
|
+
# @param allow_missing_key [Boolean] whether a missing key is read via `record[key]` rather than raising
|
|
18
|
+
#
|
|
19
|
+
# @return [HashAccessKeyword] resolver instance
|
|
20
|
+
#
|
|
21
|
+
def self.get(name, mode, allow_missing_key)
|
|
22
|
+
HashAccessKeyword.new(name, mode, allow_missing_key)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
# Builds value resolver for attributes with the :delegate option using
|
|
28
|
+
# hash access on any of its steps
|
|
29
|
+
#
|
|
30
|
+
class HashAccessDelegateResolver
|
|
31
|
+
#
|
|
32
|
+
# Creates resolver that delegates through the provided step readers
|
|
33
|
+
#
|
|
34
|
+
# @param to_step [#call] reader of the intermediate object
|
|
35
|
+
# @param final_step [#call] reader of the final value
|
|
36
|
+
# @param delegate_allow_nil [Boolean] whether a nil intermediate object resolves to nil
|
|
37
|
+
#
|
|
38
|
+
# @return [HashAccessDelegate, HashAccessDelegateAllowNil] resolver instance
|
|
39
|
+
#
|
|
40
|
+
def self.get(to_step, final_step, delegate_allow_nil)
|
|
41
|
+
delegate_allow_nil ? HashAccessDelegateAllowNil.new(to_step, final_step) : HashAccessDelegate.new(to_step, final_step)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Value resolver for attributes with the :hash_access option
|
|
47
|
+
#
|
|
48
|
+
class HashAccessKeyword
|
|
49
|
+
def initialize(name, mode, allow_missing_key)
|
|
50
|
+
@key = (mode == :symbol) ? name.to_sym : name.to_s
|
|
51
|
+
@allow_missing_key = allow_missing_key
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
# Reads the key from the record
|
|
56
|
+
#
|
|
57
|
+
# @param object [Object] serialized object or delegation step value
|
|
58
|
+
# @return [Object] the value found
|
|
59
|
+
#
|
|
60
|
+
def call(object)
|
|
61
|
+
return object[@key] if @allow_missing_key
|
|
62
|
+
|
|
63
|
+
object.fetch(@key) do
|
|
64
|
+
default = object[@key]
|
|
65
|
+
next default unless default.nil?
|
|
66
|
+
|
|
67
|
+
raise KeyError.new("key not found: #{@key.inspect}", key: @key, receiver: object)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
#
|
|
73
|
+
# Value resolver for attributes with :hash_access and :delegate (without :allow_nil) options
|
|
74
|
+
#
|
|
75
|
+
class HashAccessDelegate
|
|
76
|
+
def initialize(to_step, final_step)
|
|
77
|
+
@to_step = to_step
|
|
78
|
+
@final_step = final_step
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
#
|
|
82
|
+
# Delegates the value reading through the intermediate object
|
|
83
|
+
#
|
|
84
|
+
# @param object [Object] serialized object
|
|
85
|
+
# @return [Object] the value found
|
|
86
|
+
#
|
|
87
|
+
def call(object)
|
|
88
|
+
@final_step.call(@to_step.call(object))
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
#
|
|
93
|
+
# Value resolver for attributes with :hash_access and :delegate (with :allow_nil) options
|
|
94
|
+
#
|
|
95
|
+
class HashAccessDelegateAllowNil
|
|
96
|
+
def initialize(to_step, final_step)
|
|
97
|
+
@to_step = to_step
|
|
98
|
+
@final_step = final_step
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#
|
|
102
|
+
# Delegates the value reading through the intermediate object,
|
|
103
|
+
# resolving a nil intermediate to nil
|
|
104
|
+
#
|
|
105
|
+
# @param object [Object] serialized object
|
|
106
|
+
# @return [Object, nil] the value found
|
|
107
|
+
#
|
|
108
|
+
def call(object)
|
|
109
|
+
intermediate = @to_step.call(object)
|
|
110
|
+
return if intermediate.nil?
|
|
111
|
+
|
|
112
|
+
@final_step.call(intermediate)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/serega/config.rb
CHANGED
|
@@ -25,6 +25,7 @@ class Serega
|
|
|
25
25
|
preload
|
|
26
26
|
batch
|
|
27
27
|
base_serializer
|
|
28
|
+
hash_access
|
|
28
29
|
].freeze,
|
|
29
30
|
serialize_keys: %i[context many].freeze,
|
|
30
31
|
check_attribute_name: true,
|
|
@@ -35,7 +36,8 @@ class Serega
|
|
|
35
36
|
auto_preload_excluded_methods: %i[itself].freeze,
|
|
36
37
|
hide_by_default: false,
|
|
37
38
|
batch_id_option: :id,
|
|
38
|
-
base_serializer: nil
|
|
39
|
+
base_serializer: nil,
|
|
40
|
+
hash_access: {default_mode: :symbol, default_allow_missing_key: false}
|
|
39
41
|
}.freeze
|
|
40
42
|
# :nocov:
|
|
41
43
|
|
|
@@ -205,6 +207,12 @@ class Serega
|
|
|
205
207
|
end
|
|
206
208
|
end
|
|
207
209
|
|
|
210
|
+
# Returns the hash_access config object
|
|
211
|
+
# @return [Serega::SeregaConfig::HashAccessConfig] hash_access config object
|
|
212
|
+
def hash_access
|
|
213
|
+
@hash_access ||= HashAccessConfig.new(opts.fetch(:hash_access))
|
|
214
|
+
end
|
|
215
|
+
|
|
208
216
|
# Returns :max_cached_plans_per_serializer_count config option
|
|
209
217
|
# @return [Boolean] Current :max_cached_plans_per_serializer_count config option
|
|
210
218
|
def max_cached_plans_per_serializer_count
|
|
@@ -252,6 +260,57 @@ class Serega
|
|
|
252
260
|
end
|
|
253
261
|
end
|
|
254
262
|
|
|
263
|
+
#
|
|
264
|
+
# Config for the `hash_access:` attribute option
|
|
265
|
+
#
|
|
266
|
+
class HashAccessConfig
|
|
267
|
+
# @return [Hash] hash_access config options
|
|
268
|
+
attr_reader :opts
|
|
269
|
+
|
|
270
|
+
#
|
|
271
|
+
# Initializes HashAccessConfig object
|
|
272
|
+
#
|
|
273
|
+
# @param opts [Hash] hash_access config options
|
|
274
|
+
#
|
|
275
|
+
# @return [Serega::SeregaConfig::HashAccessConfig]
|
|
276
|
+
#
|
|
277
|
+
def initialize(opts)
|
|
278
|
+
@opts = opts
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# @return [Symbol] mode used by `hash_access: true` (default :symbol)
|
|
282
|
+
def default_mode
|
|
283
|
+
opts.fetch(:default_mode)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Sets the mode used by `hash_access: true`
|
|
287
|
+
# @param value [Symbol] one of :symbol, :string
|
|
288
|
+
# @return [Symbol] new default mode
|
|
289
|
+
def default_mode=(value)
|
|
290
|
+
unless AttributeValueResolvers::HashAccessResolver::MODES.include?(value)
|
|
291
|
+
raise SeregaError, "Invalid hash_access default_mode #{value.inspect}. Allowed modes: :symbol, :string"
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
opts[:default_mode] = value
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# @return [Boolean] allow_missing_key used when an attribute omits it
|
|
298
|
+
def default_allow_missing_key
|
|
299
|
+
opts.fetch(:default_allow_missing_key)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Sets the allow_missing_key used when an attribute omits it
|
|
303
|
+
# @param value [Boolean]
|
|
304
|
+
# @return [Boolean] new default allow_missing_key
|
|
305
|
+
def default_allow_missing_key=(value)
|
|
306
|
+
unless value == true || value == false
|
|
307
|
+
raise SeregaError, "Invalid hash_access default_allow_missing_key #{value.inspect}. Must be a Boolean"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
opts[:default_allow_missing_key] = value
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
255
314
|
include SeregaConfigInstanceMethods
|
|
256
315
|
extend Serega::SeregaHelpers::SerializerClassHelper
|
|
257
316
|
end
|
|
@@ -132,11 +132,15 @@ class Serega
|
|
|
132
132
|
superclass.modified?
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
+
# Marks the class as modified, then includes the module
|
|
136
|
+
# @return [void]
|
|
135
137
|
def include(*modules)
|
|
136
138
|
@modified = true
|
|
137
139
|
super
|
|
138
140
|
end
|
|
139
141
|
|
|
142
|
+
# Marks the class as modified, then prepends the module
|
|
143
|
+
# @return [void]
|
|
140
144
|
def prepend(*modules)
|
|
141
145
|
@modified = true
|
|
142
146
|
super
|
|
@@ -34,6 +34,8 @@ class Serega
|
|
|
34
34
|
check_opt_delegate_to(delegate_opts)
|
|
35
35
|
check_opt_delegate_method(delegate_opts)
|
|
36
36
|
check_opt_delegate_allow_nil(delegate_opts)
|
|
37
|
+
check_opt_delegate_to_hash_access(delegate_opts)
|
|
38
|
+
check_opt_delegate_hash_access(delegate_opts)
|
|
37
39
|
check_opt_delegate_extra_opts(delegate_opts)
|
|
38
40
|
end
|
|
39
41
|
|
|
@@ -48,12 +50,51 @@ class Serega
|
|
|
48
50
|
Utils::CheckOptIsStringOrSymbol.call(delegate_opts, :method)
|
|
49
51
|
end
|
|
50
52
|
|
|
53
|
+
# Covers only the intermediate object being nil — a missing key on
|
|
54
|
+
# either step is each step's own :allow_missing_key sub-option.
|
|
51
55
|
def check_opt_delegate_allow_nil(delegate_opts)
|
|
52
56
|
Utils::CheckOptIsBool.call(delegate_opts, :allow_nil)
|
|
53
57
|
end
|
|
54
58
|
|
|
59
|
+
# Hash access of the intermediate (:to) step. Accepts the same
|
|
60
|
+
# forms as the final (:method) step.
|
|
61
|
+
def check_opt_delegate_to_hash_access(delegate_opts)
|
|
62
|
+
check_hash_access_value(delegate_opts, :to_hash_access)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Hash access of the final (:method) step. Accepts the same forms
|
|
66
|
+
# as the attribute :hash_access option.
|
|
67
|
+
def check_opt_delegate_hash_access(delegate_opts)
|
|
68
|
+
check_hash_access_value(delegate_opts, :hash_access)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def check_hash_access_value(delegate_opts, opt_name)
|
|
72
|
+
return unless delegate_opts.key?(opt_name)
|
|
73
|
+
|
|
74
|
+
value = delegate_opts[opt_name]
|
|
75
|
+
case value
|
|
76
|
+
when true, false then nil
|
|
77
|
+
when Symbol then check_hash_access_mode(value)
|
|
78
|
+
when Hash
|
|
79
|
+
Utils::CheckAllowedKeys.call(value, %i[mode allow_missing_key], opt_name)
|
|
80
|
+
check_hash_access_mode(value[:mode]) if value.key?(:mode)
|
|
81
|
+
Utils::CheckOptIsBool.call(value, :allow_missing_key)
|
|
82
|
+
else
|
|
83
|
+
raise SeregaError,
|
|
84
|
+
"Invalid delegate option :#{opt_name} => #{value.inspect}." \
|
|
85
|
+
" It must be a Boolean, a Symbol mode (:symbol, :string)" \
|
|
86
|
+
" or a Hash with :mode and :allow_missing_key keys"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def check_hash_access_mode(mode)
|
|
91
|
+
return true if AttributeValueResolvers::HashAccessResolver::MODES.include?(mode)
|
|
92
|
+
|
|
93
|
+
raise SeregaError, "Invalid :hash_access mode #{mode.inspect}. Allowed modes: :symbol, :string"
|
|
94
|
+
end
|
|
95
|
+
|
|
55
96
|
def check_opt_delegate_extra_opts(delegate_opts)
|
|
56
|
-
Utils::CheckAllowedKeys.call(delegate_opts, %i[to method allow_nil], :delegate)
|
|
97
|
+
Utils::CheckAllowedKeys.call(delegate_opts, %i[to method allow_nil to_hash_access hash_access], :delegate)
|
|
57
98
|
end
|
|
58
99
|
|
|
59
100
|
def check_usage_with_other_params(opts)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Serega
|
|
4
|
+
module SeregaValidations
|
|
5
|
+
module Attribute
|
|
6
|
+
#
|
|
7
|
+
# Attribute `:hash_access` option validator
|
|
8
|
+
#
|
|
9
|
+
class CheckOptHashAccess
|
|
10
|
+
class << self
|
|
11
|
+
#
|
|
12
|
+
# Checks attribute :hash_access option
|
|
13
|
+
#
|
|
14
|
+
# @param opts [Hash] Attribute options
|
|
15
|
+
#
|
|
16
|
+
# @raise [SeregaError] Attribute validation error
|
|
17
|
+
#
|
|
18
|
+
# @return [void]
|
|
19
|
+
#
|
|
20
|
+
def call(opts)
|
|
21
|
+
return unless opts.key?(:hash_access)
|
|
22
|
+
|
|
23
|
+
value = opts[:hash_access]
|
|
24
|
+
check_value(value)
|
|
25
|
+
check_usage_with_other_params(opts) if value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def check_value(value)
|
|
31
|
+
case value
|
|
32
|
+
when true, false then nil
|
|
33
|
+
when Symbol then check_mode(value)
|
|
34
|
+
when Hash
|
|
35
|
+
Utils::CheckAllowedKeys.call(value, %i[mode allow_missing_key], :hash_access)
|
|
36
|
+
check_mode(value[:mode]) if value.key?(:mode)
|
|
37
|
+
check_allow_missing_key(value[:allow_missing_key]) if value.key?(:allow_missing_key)
|
|
38
|
+
else
|
|
39
|
+
raise SeregaError,
|
|
40
|
+
"Invalid option :hash_access => #{value.inspect}." \
|
|
41
|
+
" It must be a Boolean, a Symbol mode (:symbol, :string)" \
|
|
42
|
+
" or a Hash with :mode and :allow_missing_key keys"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def check_mode(mode)
|
|
47
|
+
return if AttributeValueResolvers::HashAccessResolver::MODES.include?(mode)
|
|
48
|
+
|
|
49
|
+
raise SeregaError, "Invalid :hash_access mode #{mode.inspect}. Allowed modes: :symbol, :string"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def check_allow_missing_key(allow_missing_key)
|
|
53
|
+
return if allow_missing_key == true || allow_missing_key == false
|
|
54
|
+
|
|
55
|
+
raise SeregaError, "Invalid :hash_access option :allow_missing_key => #{allow_missing_key.inspect}. Must be a Boolean"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The :const, :value and :batch options do not read the serialized
|
|
59
|
+
# object by attribute name, so hash access can not affect them.
|
|
60
|
+
# Delegated attributes configure hash access per step with the
|
|
61
|
+
# `delegate: {to_hash_access:, hash_access:}` sub-options.
|
|
62
|
+
def check_usage_with_other_params(opts)
|
|
63
|
+
raise SeregaError, "Option :hash_access can not be used together with option :const" if opts.key?(:const)
|
|
64
|
+
raise SeregaError, "Option :hash_access can not be used together with option :value" if opts.key?(:value)
|
|
65
|
+
raise SeregaError, "Option :hash_access can not be used together with option :batch" if opts.key?(:batch)
|
|
66
|
+
|
|
67
|
+
if opts.key?(:delegate)
|
|
68
|
+
raise SeregaError,
|
|
69
|
+
"Option :hash_access can not be used together with option :delegate." \
|
|
70
|
+
" Use the delegate :to_hash_access (intermediate step) and" \
|
|
71
|
+
" :hash_access (final step) sub-options instead"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -62,6 +62,7 @@ class Serega
|
|
|
62
62
|
Attribute::CheckOptBaseSerializer.call(opts, block)
|
|
63
63
|
Attribute::CheckOptConst.call(opts)
|
|
64
64
|
Attribute::CheckOptDelegate.call(opts)
|
|
65
|
+
Attribute::CheckOptHashAccess.call(opts)
|
|
65
66
|
Attribute::CheckOptHide.call(opts)
|
|
66
67
|
Attribute::CheckOptMethod.call(opts)
|
|
67
68
|
Attribute::CheckOptMany.call(opts, block)
|
data/lib/serega.rb
CHANGED
|
@@ -29,6 +29,7 @@ require_relative "serega/utils/to_hash"
|
|
|
29
29
|
require_relative "serega/attribute_value_resolvers/batch"
|
|
30
30
|
require_relative "serega/attribute_value_resolvers/const"
|
|
31
31
|
require_relative "serega/attribute_value_resolvers/delegate"
|
|
32
|
+
require_relative "serega/attribute_value_resolvers/hash_access"
|
|
32
33
|
require_relative "serega/attribute_value_resolvers/keyword"
|
|
33
34
|
require_relative "serega/attribute"
|
|
34
35
|
require_relative "serega/attribute_normalizer"
|
|
@@ -46,6 +47,7 @@ require_relative "serega/validations/attribute/check_opt_const"
|
|
|
46
47
|
require_relative "serega/validations/attribute/check_opt_hide"
|
|
47
48
|
require_relative "serega/validations/attribute/check_opt_delegate"
|
|
48
49
|
require_relative "serega/validations/attribute/check_opt_batch"
|
|
50
|
+
require_relative "serega/validations/attribute/check_opt_hash_access"
|
|
49
51
|
require_relative "serega/validations/attribute/check_opt_many"
|
|
50
52
|
require_relative "serega/validations/attribute/check_opt_method"
|
|
51
53
|
require_relative "serega/validations/attribute/check_opt_preload"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: serega
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.40.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrey Glushkov
|
|
@@ -32,6 +32,7 @@ files:
|
|
|
32
32
|
- lib/serega/attribute_value_resolvers/batch.rb
|
|
33
33
|
- lib/serega/attribute_value_resolvers/const.rb
|
|
34
34
|
- lib/serega/attribute_value_resolvers/delegate.rb
|
|
35
|
+
- lib/serega/attribute_value_resolvers/hash_access.rb
|
|
35
36
|
- lib/serega/attribute_value_resolvers/keyword.rb
|
|
36
37
|
- lib/serega/config.rb
|
|
37
38
|
- lib/serega/data_builder.rb
|
|
@@ -83,6 +84,7 @@ files:
|
|
|
83
84
|
- lib/serega/validations/attribute/check_opt_batch.rb
|
|
84
85
|
- lib/serega/validations/attribute/check_opt_const.rb
|
|
85
86
|
- lib/serega/validations/attribute/check_opt_delegate.rb
|
|
87
|
+
- lib/serega/validations/attribute/check_opt_hash_access.rb
|
|
86
88
|
- lib/serega/validations/attribute/check_opt_hide.rb
|
|
87
89
|
- lib/serega/validations/attribute/check_opt_many.rb
|
|
88
90
|
- lib/serega/validations/attribute/check_opt_method.rb
|