serega 0.15.0 → 0.16.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 +99 -12
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +2 -2
- data/lib/serega/attribute_normalizer.rb +8 -9
- data/lib/serega/config.rb +1 -1
- data/lib/serega/object_serializer.rb +1 -1
- data/lib/serega/plan.rb +11 -11
- data/lib/serega/plan_point.rb +5 -5
- data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +1 -1
- data/lib/serega/plugins/batch/batch.rb +15 -15
- data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +2 -2
- data/lib/serega/plugins/camel_case/camel_case.rb +195 -0
- data/lib/serega/plugins/depth_limit/depth_limit.rb +185 -0
- data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +1 -1
- data/lib/serega/plugins/if/if.rb +4 -4
- data/lib/serega/plugins/metadata/metadata.rb +6 -6
- data/lib/serega/plugins/preloads/lib/modules/attribute_normalizer.rb +1 -1
- data/lib/serega/plugins/preloads/lib/preload_paths.rb +12 -5
- data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +1 -1
- data/lib/serega/plugins/preloads/preloads.rb +12 -12
- data/lib/serega/plugins/root/root.rb +1 -1
- data/lib/serega/plugins/string_modifiers/string_modifiers.rb +1 -1
- data/lib/serega/validations/attribute/check_opt_const.rb +1 -1
- data/lib/serega/validations/attribute/check_opt_delegate.rb +9 -4
- data/lib/serega/validations/attribute/{check_opt_key.rb → check_opt_method.rb} +8 -8
- data/lib/serega/validations/attribute/check_opt_value.rb +1 -1
- data/lib/serega/validations/check_attribute_params.rb +2 -2
- data/lib/serega/validations/check_initiate_params.rb +1 -1
- data/lib/serega/validations/check_serialize_params.rb +1 -1
- data/lib/serega/validations/utils/check_allowed_keys.rb +4 -2
- data/lib/serega.rb +24 -11
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b291b12115b8ca239642972b103f896f13f02874acdc5663a8f404e02673ca8d
|
4
|
+
data.tar.gz: 18bd88ca4bd77ca147161189efe57b2c544da6cc1b460fc7c694855d4b22edc9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5c5114ed1c1b8b5a0714a340aca263983f5f8cdb24d15c8138124d64520ebafe03f6c64a7985f131ee399fab287ee578889b1affa1e2aa6f4ddacfbb9c8517e
|
7
|
+
data.tar.gz: 4d8191c7081b4650e3328141ec3cc2dc9128a7c4da3a38107e5f2c107d1d70446b67e718a3ffa2ff76e700f80dbdf975da9512d089c86ec552c3dc8a390345d1
|
data/README.md
CHANGED
@@ -17,13 +17,16 @@ objects and to serialize them to Hash or JSON.
|
|
17
17
|
It has some great features:
|
18
18
|
|
19
19
|
- Manually [select serialized fields](#selecting-fields)
|
20
|
+
- Secure from malicious queries with [depth_limit][depth_limit] plugin
|
20
21
|
- Solutions for N+1 problem (via [batch][batch], [preloads][preloads] or
|
21
22
|
[activerecord_preloads][activerecord_preloads] plugins)
|
22
23
|
- Built-in object presenter ([presenter][presenter] plugin)
|
23
24
|
- Adding custom metadata (via [metadata][metadata] or
|
24
25
|
[context_metadata][context_metadata] plugins)
|
25
|
-
-
|
26
|
-
|
26
|
+
- Value formatters ([formatters][formatters] plugin) helps to transform
|
27
|
+
time, date, money, percentage and any other values same way keeping code dry
|
28
|
+
- Conditional attributes - ([if][if] plugin)
|
29
|
+
- Auto camelCase keys - [camel_case][camel_case] plugin
|
27
30
|
|
28
31
|
## Installation
|
29
32
|
|
@@ -64,8 +67,8 @@ class UserSerializer < Serega
|
|
64
67
|
# Regular attribute
|
65
68
|
attribute :first_name
|
66
69
|
|
67
|
-
# Option :
|
68
|
-
attribute :first_name,
|
70
|
+
# Option :method specifies method that must be called on serialized object
|
71
|
+
attribute :first_name, method: :old_first_name
|
69
72
|
|
70
73
|
# Block is used to define attribute value
|
71
74
|
attribute(:first_name) { |user| user.profile&.first_name }
|
@@ -77,8 +80,9 @@ class UserSerializer < Serega
|
|
77
80
|
# Sub-option :allow_nil by default is false
|
78
81
|
attribute :first_name, delegate: { to: :profile, allow_nil: true }
|
79
82
|
|
80
|
-
# Option :delegate can be used with :
|
81
|
-
|
83
|
+
# Option :delegate can be used with :method sub-option, so method chain here
|
84
|
+
# is user.profile.fname
|
85
|
+
attribute :first_name, delegate: { to: :profile, method: :fname }
|
82
86
|
|
83
87
|
# Option :const specifies attribute with specific constant value
|
84
88
|
attribute(:type, const: 'user')
|
@@ -282,7 +286,7 @@ UserSerializer.to_h(bruce, with: fields_as_string)
|
|
282
286
|
|
283
287
|
# With not existing attribute
|
284
288
|
fields = %i[first_name enemy]
|
285
|
-
fields_as_string = 'first_name,enemy'
|
289
|
+
fields_as_string = 'first_name,enemy'
|
286
290
|
UserSerializer.new(only: fields).to_h(bruce)
|
287
291
|
UserSerializer.to_h(bruce, only: fields)
|
288
292
|
UserSerializer.to_h(bruce, only: fields_as_string)
|
@@ -433,7 +437,7 @@ end
|
|
433
437
|
class UserSerializer < AppSerializer
|
434
438
|
attribute :username
|
435
439
|
attribute :user_stats,
|
436
|
-
serializer: 'UserStatSerializer'
|
440
|
+
serializer: 'UserStatSerializer',
|
437
441
|
value: proc { |user| user },
|
438
442
|
preload: nil
|
439
443
|
end
|
@@ -545,15 +549,15 @@ It can be used to find value for attributes in optimal way:
|
|
545
549
|
After including plugin, attributes gain new `:batch` option:
|
546
550
|
|
547
551
|
```ruby
|
548
|
-
attribute :name, batch: {
|
552
|
+
attribute :name, batch: { loader: :name_loader, key: :id, default: nil }
|
549
553
|
```
|
550
554
|
|
551
555
|
`:batch` option must be a hash with this keys:
|
552
556
|
|
553
|
-
- `key` (required) [Symbol, Proc, callable] - Defines current object identifier.
|
554
|
-
Later `loader` will accept array of `keys` to detect this keys values.
|
555
557
|
- `loader` (required) [Symbol, Proc, callable] - Defines how to fetch values for
|
556
558
|
batch of keys. Receives 3 parameters: keys, context, plan_point.
|
559
|
+
- `key` (required) [Symbol, Proc, callable] - Defines current object identifier.
|
560
|
+
Key is optional if plugin was defined with `default_key` option.
|
557
561
|
- `default` (optional) - Default value for attribute.
|
558
562
|
By default it is `nil` or `[]` when attribute has option `many: true`
|
559
563
|
(ex: `attribute :tags, many: true, batch: { ... }`).
|
@@ -765,7 +769,7 @@ UserSerializer.to_h(nil, meta: { version: '1.0.1' })
|
|
765
769
|
|
766
770
|
### Plugin :formatters
|
767
771
|
|
768
|
-
Allows to define `formatters` and apply them on
|
772
|
+
Allows to define `formatters` and apply them on attribute values.
|
769
773
|
|
770
774
|
Config option `config.formatters.add` can be used to add formatters.
|
771
775
|
|
@@ -888,6 +892,88 @@ Look at [select serialized fields](#selecting-fields) for `:hide` usage examples
|
|
888
892
|
end
|
889
893
|
```
|
890
894
|
|
895
|
+
### Plugin :camel_case
|
896
|
+
|
897
|
+
By default when we add attribute like `attribute :first_name` this means:
|
898
|
+
|
899
|
+
- adding a `:first_name` key to resulted hash
|
900
|
+
- adding a `#first_name` method call result as value
|
901
|
+
|
902
|
+
But its often desired to response with *camelCased* keys.
|
903
|
+
By default this can be achieved by specifying attribute name and method directly
|
904
|
+
for each attribute: `attribute :firstName, method: first_name`
|
905
|
+
|
906
|
+
This plugin transforms all attribute names automatically.
|
907
|
+
We use simple regular expression to replace `_x` to `X` for the whole string.
|
908
|
+
We make this transformation only once when attribute is defined.
|
909
|
+
|
910
|
+
You can provide your own callable transformation when defining plugin,
|
911
|
+
for example `plugin :camel_case, transform: ->(name) { name.camelize }`
|
912
|
+
|
913
|
+
For any attribute camelCase-behavior can be skipped when
|
914
|
+
`camel_case: false` attribute option provided.
|
915
|
+
|
916
|
+
This plugin transforms only attribute keys,
|
917
|
+
without affecting `root`, `metadata`, `context_metadata` plugins keys.
|
918
|
+
|
919
|
+
If you wish to [select serialized fields](#selecting-fields), you should
|
920
|
+
provide them camelCased.
|
921
|
+
|
922
|
+
```ruby
|
923
|
+
class AppSerializer < Serega
|
924
|
+
plugin :camel_case
|
925
|
+
end
|
926
|
+
|
927
|
+
class UserSerializer < AppSerializer
|
928
|
+
attribute :first_name
|
929
|
+
attribute :last_name
|
930
|
+
attribute :full_name, camel_case: false,
|
931
|
+
value: proc { |user| [user.first_name, user.last_name].compact.join(" ") }
|
932
|
+
end
|
933
|
+
|
934
|
+
require "ostruct"
|
935
|
+
user = OpenStruct.new(first_name: "Bruce", last_name: "Wayne")
|
936
|
+
UserSerializer.to_h(user)
|
937
|
+
# => {firstName: "Bruce", lastName: "Wayne", full_name: "Bruce Wayne"}
|
938
|
+
|
939
|
+
UserSerializer.new(only: %i[firstName lastName]).to_h(user)
|
940
|
+
# => {firstName: "Bruce", lastName: "Wayne"}
|
941
|
+
```
|
942
|
+
|
943
|
+
### Plugin :depth_limit
|
944
|
+
|
945
|
+
Helps to secure from malicious queries that require to serialize too much
|
946
|
+
or from accidental serializing of objects with cyclic relations.
|
947
|
+
|
948
|
+
Depth limit is checked when constructing a serialization plan, that is when
|
949
|
+
`#new` method is called, ex: `SomeSerializer.new(with: params[:with])`.
|
950
|
+
It can be useful to instantiate serializer before any other business logic
|
951
|
+
to get possible errors earlier.
|
952
|
+
|
953
|
+
Any class-level serialization methods also check depth limit as they also
|
954
|
+
instantiate serializer.
|
955
|
+
|
956
|
+
When depth limit is exceeded `Serega::DepthLimitError` is raised.
|
957
|
+
Depth limit error details can be found in additional
|
958
|
+
`Serega::DepthLimitError#details` method
|
959
|
+
|
960
|
+
Limit can be checked or changed with next config options:
|
961
|
+
|
962
|
+
- `config.depth_limit.limit`
|
963
|
+
- `config.depth_limit.limit=`
|
964
|
+
|
965
|
+
There are no default limit, but it should be set when enabling plugin.
|
966
|
+
|
967
|
+
```ruby
|
968
|
+
class AppSerializer < Serega
|
969
|
+
plugin :depth_limit, limit: 10 # set limit for all child classes
|
970
|
+
end
|
971
|
+
|
972
|
+
class UserSerializer < AppSerializer
|
973
|
+
config.depth_limit.limit = 5 # overrides limit for UserSerializer
|
974
|
+
end
|
975
|
+
```
|
976
|
+
|
891
977
|
### Plugin :explicit_many_option
|
892
978
|
|
893
979
|
Plugin requires to add :many option when adding relationships
|
@@ -941,6 +1027,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
941
1027
|
|
942
1028
|
[activerecord_preloads]: #plugin-activerecord_preloads
|
943
1029
|
[batch]: #plugin-batch
|
1030
|
+
[camel_case]: #plugin-camel_case
|
944
1031
|
[context_metadata]: #plugin-context_metadata
|
945
1032
|
[formatters]: #plugin-formatters
|
946
1033
|
[metadata]: #plugin-metadata
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.16.0
|
data/lib/serega/attribute.rb
CHANGED
@@ -22,7 +22,7 @@ class Serega
|
|
22
22
|
attr_reader :many
|
23
23
|
|
24
24
|
# Attribute :hide option
|
25
|
-
# @return [Boolean nil] Attribute :hide option
|
25
|
+
# @return [Boolean, nil] Attribute :hide option
|
26
26
|
attr_reader :hide
|
27
27
|
|
28
28
|
#
|
@@ -30,7 +30,7 @@ class Serega
|
|
30
30
|
#
|
31
31
|
# @param name [Symbol, String] Name of attribute
|
32
32
|
# @param opts [Hash] Attribute options
|
33
|
-
# @option opts [Symbol] :
|
33
|
+
# @option opts [Symbol] :method Object method name to fetch attribute value
|
34
34
|
# @option opts [Hash] :delegate Allows to fetch value from nested object
|
35
35
|
# @option opts [Boolean] :hide Specify `true` to not serialize this attribute by default
|
36
36
|
# @option opts [Boolean] :many Specifies has_many relationship. By default is detected via object.is_a?(Enumerable)
|
@@ -36,12 +36,12 @@ class Serega
|
|
36
36
|
end
|
37
37
|
|
38
38
|
#
|
39
|
-
# Symbolized initial attribute
|
39
|
+
# Symbolized initial attribute method name
|
40
40
|
#
|
41
|
-
# @return [Symbol] Attribute normalized name
|
41
|
+
# @return [Symbol] Attribute normalized method name
|
42
42
|
#
|
43
|
-
def
|
44
|
-
@
|
43
|
+
def method_name
|
44
|
+
@method_name ||= prepare_method_name
|
45
45
|
end
|
46
46
|
|
47
47
|
#
|
@@ -121,9 +121,8 @@ class Serega
|
|
121
121
|
init_opts[:serializer]
|
122
122
|
end
|
123
123
|
|
124
|
-
def
|
125
|
-
|
126
|
-
key ? key.to_sym : name
|
124
|
+
def prepare_method_name
|
125
|
+
(init_opts[:method] || init_name).to_sym
|
127
126
|
end
|
128
127
|
|
129
128
|
def prepare_const_block
|
@@ -134,7 +133,7 @@ class Serega
|
|
134
133
|
end
|
135
134
|
|
136
135
|
def prepare_keyword_block
|
137
|
-
key_method_name =
|
136
|
+
key_method_name = method_name
|
138
137
|
proc do |object|
|
139
138
|
object.public_send(key_method_name)
|
140
139
|
end
|
@@ -144,7 +143,7 @@ class Serega
|
|
144
143
|
delegate = init_opts[:delegate]
|
145
144
|
return unless delegate
|
146
145
|
|
147
|
-
key_method_name = delegate[:
|
146
|
+
key_method_name = delegate[:method] || method_name
|
148
147
|
delegate_to = delegate[:to]
|
149
148
|
|
150
149
|
allow_nil = delegate.fetch(:allow_nil) { self.class.serializer_class.config.delegate_default_allow_nil }
|
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[
|
18
|
+
attribute_keys: %i[method value serializer many hide const delegate].freeze,
|
19
19
|
serialize_keys: %i[context many].freeze,
|
20
20
|
check_attribute_name: true,
|
21
21
|
check_initiate_params: true,
|
@@ -14,7 +14,7 @@ class Serega
|
|
14
14
|
|
15
15
|
# @param plan [SeregaPlan] Serialization plan
|
16
16
|
# @param context [Hash] Serialization context
|
17
|
-
# @param many [
|
17
|
+
# @param many [Boolean] is object is enumerable
|
18
18
|
# @param opts [Hash] Any custom options
|
19
19
|
#
|
20
20
|
# @return [SeregaObjectSerializer] New SeregaObjectSerializer
|
data/lib/serega/plan.rb
CHANGED
@@ -22,7 +22,7 @@ class Serega
|
|
22
22
|
#
|
23
23
|
def call(opts)
|
24
24
|
max_cache_size = serializer_class.config.max_cached_plans_per_serializer_count
|
25
|
-
return new(opts) if max_cache_size.zero?
|
25
|
+
return new(nil, opts) if max_cache_size.zero?
|
26
26
|
|
27
27
|
cached_plan_for(opts, max_cache_size)
|
28
28
|
end
|
@@ -33,7 +33,7 @@ class Serega
|
|
33
33
|
@cache ||= {}
|
34
34
|
cache_key = construct_cache_key(opts)
|
35
35
|
|
36
|
-
plan = @cache[cache_key] ||= new(opts)
|
36
|
+
plan = @cache[cache_key] ||= new(nil, opts)
|
37
37
|
@cache.shift if @cache.length > max_cache_size
|
38
38
|
plan
|
39
39
|
end
|
@@ -61,25 +61,26 @@ class Serega
|
|
61
61
|
# @return [SeregaPlanPoint, nil]
|
62
62
|
attr_reader :parent_plan_point
|
63
63
|
|
64
|
-
# Sets new parent plan point
|
65
|
-
# @return [SeregaPlanPoint] new parent plan point
|
66
|
-
attr_writer :parent_plan_point
|
67
|
-
|
68
64
|
# Serialization points
|
69
65
|
# @return [Array<SeregaPlanPoint>] points to serialize
|
70
66
|
attr_reader :points
|
71
67
|
|
72
68
|
#
|
73
|
-
# Instantiate new serialization plan
|
69
|
+
# Instantiate new serialization plan
|
70
|
+
#
|
71
|
+
# Patched by
|
72
|
+
# - depth_limit plugin, which checks depth limit is not exceeded when adding new plan
|
74
73
|
#
|
75
|
-
# @param
|
74
|
+
# @param parent_plan_point [SeregaPlanPoint, nil] Parent plan_point
|
75
|
+
# @param modifiers [Hash] Serialization parameters
|
76
76
|
# @option modifiers [Hash] :only The only attributes to serialize
|
77
77
|
# @option modifiers [Hash] :except Attributes to hide
|
78
78
|
# @option modifiers [Hash] :with Hidden attributes to serialize additionally
|
79
79
|
#
|
80
80
|
# @return [SeregaPlan] Serialization plan
|
81
81
|
#
|
82
|
-
def initialize(modifiers)
|
82
|
+
def initialize(parent_plan_point, modifiers)
|
83
|
+
@parent_plan_point = parent_plan_point
|
83
84
|
@points = attributes_points(modifiers)
|
84
85
|
end
|
85
86
|
|
@@ -107,8 +108,7 @@ class Serega
|
|
107
108
|
{only: only[name], with: with[name], except: except[name]}
|
108
109
|
end
|
109
110
|
|
110
|
-
point = serializer_class::SeregaPlanPoint.new(attribute, child_fields)
|
111
|
-
point.plan = self
|
111
|
+
point = serializer_class::SeregaPlanPoint.new(self, attribute, child_fields)
|
112
112
|
points << point.freeze
|
113
113
|
end
|
114
114
|
|
data/lib/serega/plan_point.rb
CHANGED
@@ -13,7 +13,7 @@ class Serega
|
|
13
13
|
|
14
14
|
# Link to current plan this point belongs to
|
15
15
|
# @return [SeregaAttribute] Current plan
|
16
|
-
|
16
|
+
attr_reader :plan
|
17
17
|
|
18
18
|
# Shows current attribute
|
19
19
|
# @return [SeregaAttribute] Current attribute
|
@@ -44,6 +44,7 @@ class Serega
|
|
44
44
|
#
|
45
45
|
# Initializes plan point
|
46
46
|
#
|
47
|
+
# @param plan [SeregaPlan] Current plan this point belongs to
|
47
48
|
# @param attribute [SeregaAttribute] Attribute to construct plan point
|
48
49
|
# @param modifiers Serialization parameters
|
49
50
|
# @option modifiers [Hash] :only The only attributes to serialize
|
@@ -52,7 +53,8 @@ class Serega
|
|
52
53
|
#
|
53
54
|
# @return [SeregaPlanPoint] New plan point
|
54
55
|
#
|
55
|
-
def initialize(attribute, modifiers = nil)
|
56
|
+
def initialize(plan, attribute, modifiers = nil)
|
57
|
+
@plan = plan
|
56
58
|
@attribute = attribute
|
57
59
|
@modifiers = modifiers
|
58
60
|
set_normalized_vars
|
@@ -79,9 +81,7 @@ class Serega
|
|
79
81
|
|
80
82
|
fields = modifiers || FROZEN_EMPTY_HASH
|
81
83
|
|
82
|
-
|
83
|
-
plan.parent_plan_point = self
|
84
|
-
plan
|
84
|
+
serializer::SeregaPlan.new(self, fields)
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
@@ -79,18 +79,18 @@ class Serega
|
|
79
79
|
# @return [void]
|
80
80
|
#
|
81
81
|
def self.load_plugin(serializer_class, **_opts)
|
82
|
-
require_relative "
|
83
|
-
require_relative "
|
84
|
-
require_relative "
|
85
|
-
require_relative "
|
86
|
-
require_relative "
|
87
|
-
require_relative "
|
88
|
-
require_relative "
|
89
|
-
require_relative "
|
90
|
-
require_relative "
|
91
|
-
require_relative "
|
92
|
-
require_relative "
|
93
|
-
require_relative "
|
82
|
+
require_relative "lib/batch_config"
|
83
|
+
require_relative "lib/loader"
|
84
|
+
require_relative "lib/loaders"
|
85
|
+
require_relative "lib/modules/attribute"
|
86
|
+
require_relative "lib/modules/attribute_normalizer"
|
87
|
+
require_relative "lib/modules/check_attribute_params"
|
88
|
+
require_relative "lib/modules/config"
|
89
|
+
require_relative "lib/modules/object_serializer"
|
90
|
+
require_relative "lib/modules/plan_point"
|
91
|
+
require_relative "lib/validations/check_batch_opt_key"
|
92
|
+
require_relative "lib/validations/check_batch_opt_loader"
|
93
|
+
require_relative "lib/validations/check_opt_batch"
|
94
94
|
|
95
95
|
serializer_class.extend(ClassMethods)
|
96
96
|
serializer_class.include(InstanceMethods)
|
@@ -121,18 +121,18 @@ class Serega
|
|
121
121
|
serializer_class.const_set(:SeregaBatchLoader, batch_loader_class)
|
122
122
|
|
123
123
|
if serializer_class.plugin_used?(:activerecord_preloads)
|
124
|
-
require_relative "
|
124
|
+
require_relative "lib/plugins_extensions/activerecord_preloads"
|
125
125
|
serializer_class::SeregaBatchLoader.include(PluginsExtensions::ActiveRecordPreloads::BatchLoaderInstanceMethods)
|
126
126
|
end
|
127
127
|
|
128
128
|
if serializer_class.plugin_used?(:formatters)
|
129
|
-
require_relative "
|
129
|
+
require_relative "lib/plugins_extensions/formatters"
|
130
130
|
serializer_class::SeregaBatchLoader.include(PluginsExtensions::Formatters::BatchLoaderInstanceMethods)
|
131
131
|
serializer_class::SeregaAttribute.include(PluginsExtensions::Formatters::SeregaAttributeInstanceMethods)
|
132
132
|
end
|
133
133
|
|
134
134
|
if serializer_class.plugin_used?(:preloads)
|
135
|
-
require_relative "
|
135
|
+
require_relative "lib/plugins_extensions/preloads"
|
136
136
|
serializer_class::SeregaAttributeNormalizer.include(PluginsExtensions::Preloads::AttributeNormalizerInstanceMethods)
|
137
137
|
end
|
138
138
|
|
@@ -23,7 +23,7 @@ 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])
|
26
|
+
SeregaValidations::Utils::CheckAllowedKeys.call(batch, %i[key loader default], :batch)
|
27
27
|
|
28
28
|
check_batch_opt_key(batch, serializer_class)
|
29
29
|
check_batch_opt_loader(batch)
|
@@ -50,7 +50,7 @@ class Serega
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def check_usage_with_other_params(opts, block)
|
53
|
-
raise SeregaError, "Option :batch can not be used together with option :
|
53
|
+
raise SeregaError, "Option :batch can not be used together with option :method" if opts.key?(:method)
|
54
54
|
raise SeregaError, "Option :batch can not be used together with option :value" if opts.key?(:value)
|
55
55
|
raise SeregaError, "Option :batch can not be used together with option :const" if opts.key?(:const)
|
56
56
|
raise SeregaError, "Option :batch can not be used together with option :delegate" if opts.key?(:delegate)
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Serega
|
4
|
+
module SeregaPlugins
|
5
|
+
#
|
6
|
+
# Plugin :camel_case
|
7
|
+
#
|
8
|
+
# By default when we add attribute like `attribute :first_name` this means:
|
9
|
+
# - adding a `:first_name` key to resulted hash
|
10
|
+
# - adding a `#first_name` method call result as value
|
11
|
+
#
|
12
|
+
# But its often desired to response with *camelCased* keys.
|
13
|
+
# Earlier this can be achieved by specifying attribute name and method directly
|
14
|
+
# for each attribute: `attribute :firstName, method: first_name`
|
15
|
+
#
|
16
|
+
# Now this plugin transforms all attribute names automatically.
|
17
|
+
# We use simple regular expression to replace `_x` to `X` for the whole string.
|
18
|
+
# You can provide your own callable transformation when defining plugin,
|
19
|
+
# for example `plugin :camel_case, transform: ->(name) { name.camelize }`
|
20
|
+
#
|
21
|
+
# For any attribute camelCase-behavior can be skipped when
|
22
|
+
# `camel_case: false` attribute option provided.
|
23
|
+
#
|
24
|
+
# @example Define plugin
|
25
|
+
# class AppSerializer < Serega
|
26
|
+
# plugin :camel_case
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# class UserSerializer < AppSerializer
|
30
|
+
# attribute :first_name
|
31
|
+
# attribute :last_name
|
32
|
+
# attribute :full_name, camel_case: false, value: proc { |user| [user.first_name, user.last_name].compact.join(" ") }
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# require "ostruct"
|
36
|
+
# user = OpenStruct.new(first_name: "Bruce", last_name: "Wayne")
|
37
|
+
# UserSerializer.to_h(user) # {firstName: "Bruce", lastName: "Wayne", full_name: "Bruce Wayne"}
|
38
|
+
#
|
39
|
+
module CamelCase
|
40
|
+
# Default camel-case transformation
|
41
|
+
TRANSFORM_DEFAULT = proc { |attribute_name|
|
42
|
+
attribute_name.gsub!(/_[a-z]/) { |m| m[-1].upcase! }
|
43
|
+
attribute_name
|
44
|
+
}
|
45
|
+
|
46
|
+
# @return [Symbol] Plugin name
|
47
|
+
def self.plugin_name
|
48
|
+
:camel_case
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Applies plugin code to specific serializer
|
53
|
+
#
|
54
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
55
|
+
# @param _opts [Hash] Loaded plugins options
|
56
|
+
#
|
57
|
+
# @return [void]
|
58
|
+
#
|
59
|
+
def self.load_plugin(serializer_class, **_opts)
|
60
|
+
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
|
61
|
+
serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
|
62
|
+
serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Adds config options and runs other callbacks after plugin was loaded
|
67
|
+
#
|
68
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
69
|
+
# @param opts [Hash] loaded plugins opts
|
70
|
+
#
|
71
|
+
# @return [void]
|
72
|
+
#
|
73
|
+
def self.after_load_plugin(serializer_class, **opts)
|
74
|
+
config = serializer_class.config
|
75
|
+
config.opts[:camel_case] = {}
|
76
|
+
config.camel_case.transform = opts[:transform] || TRANSFORM_DEFAULT
|
77
|
+
|
78
|
+
config.attribute_keys << :camel_case
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Config class additional/patched instance methods
|
83
|
+
#
|
84
|
+
# @see Serega::SeregaConfig
|
85
|
+
#
|
86
|
+
module ConfigInstanceMethods
|
87
|
+
# @return [Serega::SeregaPlugins::CamelCase::CamelCaseConfig] `camel_case` plugin config
|
88
|
+
def camel_case
|
89
|
+
@camel_case ||= CamelCaseConfig.new(opts.fetch(:camel_case))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
|
95
|
+
#
|
96
|
+
# @see Serega::SeregaValidations::CheckAttributeParams
|
97
|
+
#
|
98
|
+
module CheckAttributeParamsInstanceMethods
|
99
|
+
private
|
100
|
+
|
101
|
+
def check_opts
|
102
|
+
super
|
103
|
+
CheckOptCamelCase.call(opts)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# Validator for attribute :camel_case option
|
109
|
+
#
|
110
|
+
class CheckOptCamelCase
|
111
|
+
class << self
|
112
|
+
#
|
113
|
+
# Checks attribute :camel_case option must be boolean
|
114
|
+
#
|
115
|
+
# @param opts [Hash] Attribute options
|
116
|
+
#
|
117
|
+
# @raise [SeregaError] Attribute validation error
|
118
|
+
#
|
119
|
+
# @return [void]
|
120
|
+
#
|
121
|
+
def call(opts)
|
122
|
+
camel_case_option_exists = opts.key?(:camel_case)
|
123
|
+
return unless camel_case_option_exists
|
124
|
+
|
125
|
+
value = opts[:camel_case]
|
126
|
+
return if value.equal?(true) || value.equal?(false)
|
127
|
+
|
128
|
+
raise SeregaError, "Attribute option :camel_case must have a boolean value, but #{value.class} was provided"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# CamelCase config object
|
134
|
+
class CamelCaseConfig
|
135
|
+
attr_reader :opts
|
136
|
+
|
137
|
+
#
|
138
|
+
# Initializes CamelCaseConfig object
|
139
|
+
#
|
140
|
+
# @param opts [Hash] camel_case plugin options
|
141
|
+
# @option opts [#call] :transform Callable object that transforms original attribute name
|
142
|
+
#
|
143
|
+
# @return [Serega::SeregaPlugins::CamelCase::CamelCaseConfig] CamelCaseConfig object
|
144
|
+
#
|
145
|
+
def initialize(opts)
|
146
|
+
@opts = opts
|
147
|
+
end
|
148
|
+
|
149
|
+
# @return [#call] defined object that transforms name
|
150
|
+
def transform
|
151
|
+
opts.fetch(:transform)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Sets transformation callable object
|
155
|
+
#
|
156
|
+
# @param value [#call] transformation
|
157
|
+
#
|
158
|
+
# @return [#call] camel_case plugin transformation callable object
|
159
|
+
def transform=(value)
|
160
|
+
raise SeregaError, "Transform value must respond to #call" unless value.respond_to?(:call)
|
161
|
+
|
162
|
+
params = value.is_a?(Proc) ? value.parameters : value.method(:call).parameters
|
163
|
+
if params.count != 1 || !params.all? { |param| (param[0] == :req) || (param[0] == :opt) }
|
164
|
+
raise SeregaError, "Transform value must respond to #call and accept 1 regular parameter"
|
165
|
+
end
|
166
|
+
|
167
|
+
opts[:transform] = value
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# SeregaAttributeNormalizer additional/patched instance methods
|
173
|
+
#
|
174
|
+
# @see SeregaAttributeNormalizer::AttributeInstanceMethods
|
175
|
+
#
|
176
|
+
module AttributeNormalizerInstanceMethods
|
177
|
+
private
|
178
|
+
|
179
|
+
#
|
180
|
+
# Patch for original `prepare_name` method
|
181
|
+
#
|
182
|
+
# Makes camelCased name
|
183
|
+
#
|
184
|
+
def prepare_name
|
185
|
+
res = super
|
186
|
+
return res if init_opts[:camel_case] == false
|
187
|
+
|
188
|
+
self.class.serializer_class.config.camel_case.transform.call(res.to_s).to_sym
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
register_plugin(CamelCase.plugin_name, CamelCase)
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Serega
|
4
|
+
module SeregaPlugins
|
5
|
+
#
|
6
|
+
# Plugin :depth_limit
|
7
|
+
#
|
8
|
+
# Helps to secure from malicious queries that require to serialize too much
|
9
|
+
# or from accidental serializing of objects with cyclic relations.
|
10
|
+
#
|
11
|
+
# Depth limit is checked when constructing a serialization plan, that is when
|
12
|
+
# `#new` method is called, ex: `SomeSerializer.new(with: params[:with])`.
|
13
|
+
# It can be useful to instantiate serializer before any other business logic
|
14
|
+
# to get possible errors earlier.
|
15
|
+
#
|
16
|
+
# Any class-level serialization methods also check depth limit as they also instantiate serializer.
|
17
|
+
#
|
18
|
+
# When depth limit is exceeded `Serega::DepthLimitError` is raised.
|
19
|
+
# Depth limit error details can be found in additional `Serega::DepthLimitError#details` method
|
20
|
+
#
|
21
|
+
# Limit can be checked or changed with next config options:
|
22
|
+
#
|
23
|
+
# - config.depth_limit.limit
|
24
|
+
# - config.depth_limit.limit=
|
25
|
+
#
|
26
|
+
# There are no default limit, but it should be set when enabling plugin.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
#
|
30
|
+
# class AppSerializer < Serega
|
31
|
+
# plugin :depth_limit, limit: 10 # set limit for all child classes
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# class UserSerializer < AppSerializer
|
35
|
+
# config.depth_limit.limit = 5 # overrides limit for UserSerializer
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
module DepthLimit
|
39
|
+
# @return [Symbol] Plugin name
|
40
|
+
def self.plugin_name
|
41
|
+
:depth_limit
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Applies plugin code to specific serializer
|
46
|
+
#
|
47
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
48
|
+
# @param _opts [Hash] Loaded plugins options
|
49
|
+
#
|
50
|
+
# @return [void]
|
51
|
+
#
|
52
|
+
def self.load_plugin(serializer_class, **_opts)
|
53
|
+
serializer_class::SeregaPlan.include(PlanInstanceMethods)
|
54
|
+
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Adds config options and runs other callbacks after plugin was loaded
|
59
|
+
#
|
60
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
61
|
+
# @param opts [Hash] loaded plugins opts
|
62
|
+
#
|
63
|
+
# @return [void]
|
64
|
+
#
|
65
|
+
def self.after_load_plugin(serializer_class, **opts)
|
66
|
+
config = serializer_class.config
|
67
|
+
limit = opts.fetch(:limit) { raise SeregaError, "Please provide :limit option. Example: `plugin :depth_limit, limit: 10`" }
|
68
|
+
config.opts[:depth_limit] = {}
|
69
|
+
config.depth_limit.limit = limit
|
70
|
+
end
|
71
|
+
|
72
|
+
# DepthLimit config object
|
73
|
+
class DepthLimitConfig
|
74
|
+
attr_reader :opts
|
75
|
+
|
76
|
+
#
|
77
|
+
# Initializes DepthLimitConfig object
|
78
|
+
#
|
79
|
+
# @param opts [Hash] depth_limit plugin options
|
80
|
+
#
|
81
|
+
# @return [SeregaPlugins::DepthLimit::DepthLimitConfig] DepthLimitConfig object
|
82
|
+
#
|
83
|
+
def initialize(opts)
|
84
|
+
@opts = opts
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Integer] defined depth limit
|
88
|
+
def limit
|
89
|
+
opts.fetch(:limit)
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# Set depth limit
|
94
|
+
#
|
95
|
+
# @param value [Integer] depth limit
|
96
|
+
#
|
97
|
+
# @return [Integer] depth limit
|
98
|
+
def limit=(value)
|
99
|
+
raise SeregaError, "Depth limit must be an Integer" unless value.is_a?(Integer)
|
100
|
+
|
101
|
+
opts[:limit] = value
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Serega::SeregaConfig additional/patched class methods
|
107
|
+
#
|
108
|
+
# @see Serega::SeregaConfig
|
109
|
+
#
|
110
|
+
module ConfigInstanceMethods
|
111
|
+
# @return [Serega::SeregaPlugins::DepthLimit::DepthLimitConfig] current depth_limit config
|
112
|
+
def depth_limit
|
113
|
+
@depth_limit ||= DepthLimitConfig.new(opts.fetch(:depth_limit))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# SeregaPlan additional/patched instance methods
|
119
|
+
#
|
120
|
+
# @see SeregaPlan
|
121
|
+
#
|
122
|
+
module PlanInstanceMethods
|
123
|
+
#
|
124
|
+
# Initializes serialization plan
|
125
|
+
# Overrides original method (adds depth_limit validation)
|
126
|
+
#
|
127
|
+
def initialize(parent_plan_point, *)
|
128
|
+
check_depth_limit_exceeded(parent_plan_point)
|
129
|
+
super
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def check_depth_limit_exceeded(current_point)
|
135
|
+
plan = self
|
136
|
+
depth_level = 1
|
137
|
+
point = current_point
|
138
|
+
|
139
|
+
while point
|
140
|
+
depth_level += 1
|
141
|
+
plan = point.plan
|
142
|
+
point = plan.parent_plan_point
|
143
|
+
end
|
144
|
+
|
145
|
+
root_serializer = plan.class.serializer_class
|
146
|
+
root_depth_limit = root_serializer.config.depth_limit.limit
|
147
|
+
|
148
|
+
if depth_level > root_depth_limit
|
149
|
+
fields_chain = [current_point.name]
|
150
|
+
fields_chain << current_point.name while (current_point = current_point.plan.parent_plan_point)
|
151
|
+
details = "#{root_serializer} (depth limit: #{root_depth_limit}) -> #{fields_chain.reverse!.join(" -> ")}"
|
152
|
+
raise DepthLimitError.new("Depth limit was exceeded", details)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
register_plugin(DepthLimit.plugin_name, DepthLimit)
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Special error for depth_limit plugin
|
163
|
+
#
|
164
|
+
class DepthLimitError < SeregaError
|
165
|
+
#
|
166
|
+
# Details of why depth limit error happens.
|
167
|
+
#
|
168
|
+
# @return [String] error details
|
169
|
+
#
|
170
|
+
attr_reader :details
|
171
|
+
|
172
|
+
#
|
173
|
+
# Initializes new error
|
174
|
+
#
|
175
|
+
# @param message [String] Error message
|
176
|
+
# @param details [String] Error additional details
|
177
|
+
#
|
178
|
+
# @return [DepthLimitError] error instance
|
179
|
+
#
|
180
|
+
def initialize(message, details = nil)
|
181
|
+
super(message)
|
182
|
+
@details = details
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -40,7 +40,7 @@ class Serega
|
|
40
40
|
# @return [void]
|
41
41
|
#
|
42
42
|
def self.load_plugin(serializer_class, **_opts)
|
43
|
-
require_relative "
|
43
|
+
require_relative "validations/check_opt_many"
|
44
44
|
|
45
45
|
serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
|
46
46
|
end
|
data/lib/serega/plugins/if/if.rb
CHANGED
@@ -56,10 +56,10 @@ class Serega
|
|
56
56
|
# @return [void]
|
57
57
|
#
|
58
58
|
def self.load_plugin(serializer_class, **_opts)
|
59
|
-
require_relative "
|
60
|
-
require_relative "
|
61
|
-
require_relative "
|
62
|
-
require_relative "
|
59
|
+
require_relative "validations/check_opt_if"
|
60
|
+
require_relative "validations/check_opt_if_value"
|
61
|
+
require_relative "validations/check_opt_unless"
|
62
|
+
require_relative "validations/check_opt_unless_value"
|
63
63
|
|
64
64
|
serializer_class::SeregaAttribute.include(SeregaAttributeInstanceMethods)
|
65
65
|
serializer_class::SeregaPlanPoint.include(PlanPointInstanceMethods)
|
@@ -62,12 +62,12 @@ class Serega
|
|
62
62
|
serializer_class.include(InstanceMethods)
|
63
63
|
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
|
64
64
|
|
65
|
-
require_relative "
|
66
|
-
require_relative "
|
67
|
-
require_relative "
|
68
|
-
require_relative "
|
69
|
-
require_relative "
|
70
|
-
require_relative "
|
65
|
+
require_relative "meta_attribute"
|
66
|
+
require_relative "validations/check_block"
|
67
|
+
require_relative "validations/check_opt_hide_nil"
|
68
|
+
require_relative "validations/check_opt_hide_empty"
|
69
|
+
require_relative "validations/check_opts"
|
70
|
+
require_relative "validations/check_path"
|
71
71
|
|
72
72
|
meta_attribute_class = Class.new(MetaAttribute)
|
73
73
|
meta_attribute_class.serializer_class = serializer_class
|
@@ -36,7 +36,7 @@ class Serega
|
|
36
36
|
if preloads_provided
|
37
37
|
opts[:preload]
|
38
38
|
elsif opts.key?(:serializer) && self.class.serializer_class.config.preloads.auto_preload_attributes_with_serializer
|
39
|
-
|
39
|
+
method_name
|
40
40
|
elsif opts.key?(:delegate) && self.class.serializer_class.config.preloads.auto_preload_attributes_with_delegate
|
41
41
|
opts[:delegate].fetch(:to)
|
42
42
|
end
|
@@ -22,18 +22,25 @@ class Serega
|
|
22
22
|
#
|
23
23
|
# Transforms user provided preloads to array of paths
|
24
24
|
#
|
25
|
-
# @param
|
25
|
+
# @param preloads [Array,Hash,String,Symbol,nil,false] association(s) to preload
|
26
26
|
#
|
27
27
|
# @return [Hash] preloads transformed to hash
|
28
28
|
#
|
29
|
-
def call(preloads
|
30
|
-
|
29
|
+
def call(preloads)
|
30
|
+
formatted_preloads = FormatUserPreloads.call(preloads)
|
31
|
+
return FROZEN_EMPTY_ARRAY if formatted_preloads.empty?
|
31
32
|
|
32
|
-
|
33
|
+
paths(formatted_preloads, [], [])
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def paths(formatted_preloads, path, result)
|
39
|
+
formatted_preloads.each do |key, nested_preloads|
|
33
40
|
path << key
|
34
41
|
result << path.dup
|
35
42
|
|
36
|
-
|
43
|
+
paths(nested_preloads, path, result)
|
37
44
|
path.pop
|
38
45
|
end
|
39
46
|
|
@@ -15,7 +15,7 @@ class Serega
|
|
15
15
|
#
|
16
16
|
# This options are very handy if you want to forget about finding preloads manually.
|
17
17
|
#
|
18
|
-
# Preloads can be disabled with `preload: false` attribute option
|
18
|
+
# Preloads can be disabled with `preload: false` attribute option.
|
19
19
|
# Also automatically added preloads can be overwritten with manually specified `preload: :another_value`.
|
20
20
|
#
|
21
21
|
# Some examples, **please read comments in the code below**
|
@@ -79,17 +79,17 @@ class Serega
|
|
79
79
|
# @return [void]
|
80
80
|
#
|
81
81
|
def self.load_plugin(serializer_class, **_opts)
|
82
|
-
require_relative "
|
83
|
-
require_relative "
|
84
|
-
require_relative "
|
85
|
-
require_relative "
|
86
|
-
require_relative "
|
87
|
-
require_relative "
|
88
|
-
require_relative "
|
89
|
-
require_relative "
|
90
|
-
require_relative "
|
91
|
-
require_relative "
|
92
|
-
require_relative "
|
82
|
+
require_relative "lib/format_user_preloads"
|
83
|
+
require_relative "lib/modules/attribute"
|
84
|
+
require_relative "lib/modules/attribute_normalizer"
|
85
|
+
require_relative "lib/modules/check_attribute_params"
|
86
|
+
require_relative "lib/modules/config"
|
87
|
+
require_relative "lib/modules/plan_point"
|
88
|
+
require_relative "lib/preload_paths"
|
89
|
+
require_relative "lib/preloads_config"
|
90
|
+
require_relative "lib/preloads_constructor"
|
91
|
+
require_relative "validations/check_opt_preload"
|
92
|
+
require_relative "validations/check_opt_preload_path"
|
93
93
|
|
94
94
|
serializer_class.include(InstanceMethods)
|
95
95
|
serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
|
@@ -127,7 +127,7 @@ class Serega
|
|
127
127
|
# @option opts [Symbol, String, nil] :one root for single-object serialization
|
128
128
|
# @option opts [Symbol, String, nil] :many root for many-objects serialization
|
129
129
|
#
|
130
|
-
# @return [
|
130
|
+
# @return [SeregaPlugins::Root::RootConfig] RootConfig object
|
131
131
|
#
|
132
132
|
def initialize(opts)
|
133
133
|
@opts = opts
|
@@ -26,7 +26,7 @@ class Serega
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def check_usage_with_other_params(opts, block)
|
29
|
-
raise SeregaError, "Option :const can not be used together with option :
|
29
|
+
raise SeregaError, "Option :const can not be used together with option :method" if opts.key?(:method)
|
30
30
|
raise SeregaError, "Option :const can not be used together with option :value" if opts.key?(:value)
|
31
31
|
raise SeregaError, "Option :const can not be used together with block" if block
|
32
32
|
end
|
@@ -32,8 +32,9 @@ class Serega
|
|
32
32
|
|
33
33
|
delegate_opts = opts[:delegate]
|
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_extra_opts(delegate_opts)
|
37
38
|
end
|
38
39
|
|
39
40
|
def check_opt_delegate_to(delegate_opts)
|
@@ -43,16 +44,20 @@ class Serega
|
|
43
44
|
Utils::CheckOptIsStringOrSymbol.call(delegate_opts, :to)
|
44
45
|
end
|
45
46
|
|
46
|
-
def
|
47
|
-
Utils::CheckOptIsStringOrSymbol.call(delegate_opts, :
|
47
|
+
def check_opt_delegate_method(delegate_opts)
|
48
|
+
Utils::CheckOptIsStringOrSymbol.call(delegate_opts, :method)
|
48
49
|
end
|
49
50
|
|
50
51
|
def check_opt_delegate_allow_nil(delegate_opts)
|
51
52
|
Utils::CheckOptIsBool.call(delegate_opts, :allow_nil)
|
52
53
|
end
|
53
54
|
|
55
|
+
def check_opt_delegate_extra_opts(delegate_opts)
|
56
|
+
Utils::CheckAllowedKeys.call(delegate_opts, %i[to method allow_nil], :delegate)
|
57
|
+
end
|
58
|
+
|
54
59
|
def check_usage_with_other_params(opts, block)
|
55
|
-
raise SeregaError, "Option :delegate can not be used together with option :
|
60
|
+
raise SeregaError, "Option :delegate can not be used together with option :method" if opts.key?(:method)
|
56
61
|
raise SeregaError, "Option :delegate can not be used together with option :const" if opts.key?(:const)
|
57
62
|
raise SeregaError, "Option :delegate can not be used together with option :value" if opts.key?(:value)
|
58
63
|
raise SeregaError, "Option :delegate can not be used together with block" if block
|
@@ -4,12 +4,12 @@ class Serega
|
|
4
4
|
module SeregaValidations
|
5
5
|
module Attribute
|
6
6
|
#
|
7
|
-
# Attribute `:
|
7
|
+
# Attribute `:method` option validator
|
8
8
|
#
|
9
|
-
class
|
9
|
+
class CheckOptMethod
|
10
10
|
class << self
|
11
11
|
#
|
12
|
-
# Checks attribute :
|
12
|
+
# Checks attribute :method option
|
13
13
|
#
|
14
14
|
# @param opts [Hash] Attribute options
|
15
15
|
#
|
@@ -18,18 +18,18 @@ class Serega
|
|
18
18
|
# @return [void]
|
19
19
|
#
|
20
20
|
def call(opts, block = nil)
|
21
|
-
return unless opts.key?(:
|
21
|
+
return unless opts.key?(:method)
|
22
22
|
|
23
23
|
check_usage_with_other_params(opts, block)
|
24
|
-
Utils::CheckOptIsStringOrSymbol.call(opts, :
|
24
|
+
Utils::CheckOptIsStringOrSymbol.call(opts, :method)
|
25
25
|
end
|
26
26
|
|
27
27
|
private
|
28
28
|
|
29
29
|
def check_usage_with_other_params(opts, block)
|
30
|
-
raise SeregaError, "Option :
|
31
|
-
raise SeregaError, "Option :
|
32
|
-
raise SeregaError, "Option :
|
30
|
+
raise SeregaError, "Option :method can not be used together with option :const" if opts.key?(:const)
|
31
|
+
raise SeregaError, "Option :method can not be used together with option :value" if opts.key?(:value)
|
32
|
+
raise SeregaError, "Option :method can not be used together with block" if block
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -27,7 +27,7 @@ class Serega
|
|
27
27
|
private
|
28
28
|
|
29
29
|
def check_usage_with_other_params(opts, block)
|
30
|
-
raise SeregaError, "Option :value can not be used together with option :
|
30
|
+
raise SeregaError, "Option :value can not be used together with option :method" if opts.key?(:method)
|
31
31
|
raise SeregaError, "Option :value can not be used together with option :const" if opts.key?(:const)
|
32
32
|
raise SeregaError, "Option :value can not be used together with block" if block
|
33
33
|
end
|
@@ -58,12 +58,12 @@ class Serega
|
|
58
58
|
# - plugin :if (checks :if, :if_value, :unless, :unless_value options)
|
59
59
|
# - plugin :preloads (checks :preload option)
|
60
60
|
def check_opts
|
61
|
-
Utils::CheckAllowedKeys.call(opts, allowed_opts_keys)
|
61
|
+
Utils::CheckAllowedKeys.call(opts, allowed_opts_keys, :attribute)
|
62
62
|
|
63
63
|
Attribute::CheckOptConst.call(opts, block)
|
64
64
|
Attribute::CheckOptDelegate.call(opts, block)
|
65
65
|
Attribute::CheckOptHide.call(opts)
|
66
|
-
Attribute::
|
66
|
+
Attribute::CheckOptMethod.call(opts, block)
|
67
67
|
Attribute::CheckOptMany.call(opts)
|
68
68
|
Attribute::CheckOptSerializer.call(opts)
|
69
69
|
Attribute::CheckOptValue.call(opts, block)
|
@@ -33,7 +33,7 @@ class Serega
|
|
33
33
|
private
|
34
34
|
|
35
35
|
def check_opts
|
36
|
-
Utils::CheckAllowedKeys.call(opts, serializer_class.config.serialize_keys)
|
36
|
+
Utils::CheckAllowedKeys.call(opts, serializer_class.config.serialize_keys, :serialize)
|
37
37
|
|
38
38
|
Utils::CheckOptIsHash.call(opts, :context)
|
39
39
|
Utils::CheckOptIsBool.call(opts, :many)
|
@@ -18,11 +18,13 @@ class Serega
|
|
18
18
|
# @raise [Serega::SeregaError] error when any hash key is not allowed
|
19
19
|
#
|
20
20
|
# @return [void]
|
21
|
-
def self.call(opts, allowed_keys)
|
21
|
+
def self.call(opts, allowed_keys, parameter_name)
|
22
22
|
opts.each_key do |key|
|
23
23
|
next if allowed_keys.include?(key)
|
24
24
|
|
25
|
-
raise SeregaError,
|
25
|
+
raise SeregaError,
|
26
|
+
"Invalid #{parameter_name} option #{key.inspect}." \
|
27
|
+
" Allowed options are: #{allowed_keys.map(&:inspect).sort.join(", ")}"
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
data/lib/serega.rb
CHANGED
@@ -32,8 +32,8 @@ require_relative "serega/validations/attribute/check_name"
|
|
32
32
|
require_relative "serega/validations/attribute/check_opt_const"
|
33
33
|
require_relative "serega/validations/attribute/check_opt_hide"
|
34
34
|
require_relative "serega/validations/attribute/check_opt_delegate"
|
35
|
-
require_relative "serega/validations/attribute/check_opt_key"
|
36
35
|
require_relative "serega/validations/attribute/check_opt_many"
|
36
|
+
require_relative "serega/validations/attribute/check_opt_method"
|
37
37
|
require_relative "serega/validations/attribute/check_opt_serializer"
|
38
38
|
require_relative "serega/validations/attribute/check_opt_value"
|
39
39
|
require_relative "serega/validations/initiate/check_modifiers"
|
@@ -172,7 +172,20 @@ class Serega
|
|
172
172
|
new(modifiers_opts).to_h(object, serialize_opts)
|
173
173
|
end
|
174
174
|
|
175
|
-
#
|
175
|
+
#
|
176
|
+
# Serializes provided object to Hash
|
177
|
+
#
|
178
|
+
# @param object [Object] Serialized object
|
179
|
+
# @param opts [Hash, nil] Serializer modifiers and other instantiating options
|
180
|
+
# @option opts [Array, Hash, String, Symbol] :only The only attributes to serialize
|
181
|
+
# @option opts [Array, Hash, String, Symbol] :except Attributes to hide
|
182
|
+
# @option opts [Array, Hash, String, Symbol] :with Attributes (usually hidden) to serialize additionally
|
183
|
+
# @option opts [Boolean] :validate Validates provided modifiers (Default is true)
|
184
|
+
# @option opts [Hash] :context Serialization context
|
185
|
+
# @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
|
186
|
+
#
|
187
|
+
# @return [Hash] Serialization result
|
188
|
+
#
|
176
189
|
def to_h(object, opts = nil)
|
177
190
|
call(object, opts)
|
178
191
|
end
|
@@ -283,8 +296,17 @@ class Serega
|
|
283
296
|
def initialize(opts = nil)
|
284
297
|
@opts = (opts.nil? || opts.empty?) ? FROZEN_EMPTY_HASH : parse_modifiers(opts)
|
285
298
|
self.class::CheckInitiateParams.new(@opts).validate if opts&.fetch(:check_initiate_params) { config.check_initiate_params }
|
299
|
+
|
300
|
+
@plan = self.class::SeregaPlan.call(@opts)
|
286
301
|
end
|
287
302
|
|
303
|
+
#
|
304
|
+
# Plan for serialization.
|
305
|
+
# This plan can be traversed to find serialized attributes and nested attributes.
|
306
|
+
#
|
307
|
+
# @return [Serega::SeregaPlan] Serialization plan
|
308
|
+
attr_reader :plan
|
309
|
+
|
288
310
|
#
|
289
311
|
# Serializes provided object to Hash
|
290
312
|
#
|
@@ -338,15 +360,6 @@ class Serega
|
|
338
360
|
config.from_json.call(json)
|
339
361
|
end
|
340
362
|
|
341
|
-
#
|
342
|
-
# Plan for serialization.
|
343
|
-
# This plan can be traversed to find serialized attributes and nested attributes.
|
344
|
-
#
|
345
|
-
# @return [Array<Serega::SeregaPlanPoint>] plan
|
346
|
-
def plan
|
347
|
-
@plan ||= self.class::SeregaPlan.call(opts)
|
348
|
-
end
|
349
|
-
|
350
363
|
private
|
351
364
|
|
352
365
|
attr_reader :opts
|
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.
|
4
|
+
version: 0.16.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
|
+
date: 2023-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
14
|
JSON Serializer
|
@@ -58,7 +58,9 @@ files:
|
|
58
58
|
- lib/serega/plugins/batch/lib/validations/check_batch_opt_key.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
|
+
- lib/serega/plugins/camel_case/camel_case.rb
|
61
62
|
- lib/serega/plugins/context_metadata/context_metadata.rb
|
63
|
+
- lib/serega/plugins/depth_limit/depth_limit.rb
|
62
64
|
- lib/serega/plugins/explicit_many_option/explicit_many_option.rb
|
63
65
|
- lib/serega/plugins/explicit_many_option/validations/check_opt_many.rb
|
64
66
|
- lib/serega/plugins/formatters/formatters.rb
|
@@ -99,8 +101,8 @@ files:
|
|
99
101
|
- lib/serega/validations/attribute/check_opt_const.rb
|
100
102
|
- lib/serega/validations/attribute/check_opt_delegate.rb
|
101
103
|
- lib/serega/validations/attribute/check_opt_hide.rb
|
102
|
-
- lib/serega/validations/attribute/check_opt_key.rb
|
103
104
|
- lib/serega/validations/attribute/check_opt_many.rb
|
105
|
+
- lib/serega/validations/attribute/check_opt_method.rb
|
104
106
|
- lib/serega/validations/attribute/check_opt_serializer.rb
|
105
107
|
- lib/serega/validations/attribute/check_opt_value.rb
|
106
108
|
- lib/serega/validations/check_attribute_params.rb
|