serega 0.15.0 → 0.17.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 +121 -22
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +5 -5
- data/lib/serega/attribute_normalizer.rb +29 -11
- 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/batch_config.rb +11 -8
- data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +26 -11
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_key.rb +5 -35
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +5 -35
- 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/formatters/formatters.rb +88 -14
- data/lib/serega/plugins/if/if.rb +47 -23
- data/lib/serega/plugins/if/validations/check_opt_if.rb +4 -36
- data/lib/serega/plugins/if/validations/check_opt_if_value.rb +7 -39
- data/lib/serega/plugins/if/validations/check_opt_unless.rb +4 -45
- data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +7 -39
- data/lib/serega/plugins/metadata/meta_attribute.rb +21 -5
- data/lib/serega/plugins/metadata/metadata.rb +22 -13
- data/lib/serega/plugins/metadata/validations/check_block.rb +10 -10
- data/lib/serega/plugins/metadata/validations/check_opt_const.rb +38 -0
- data/lib/serega/plugins/metadata/validations/check_opt_value.rb +61 -0
- data/lib/serega/plugins/metadata/validations/check_opts.rb +24 -10
- 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/utils/params_count.rb +50 -0
- data/lib/serega/utils/to_hash.rb +1 -1
- data/lib/serega/validations/attribute/check_block.rb +4 -5
- 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 +17 -13
- data/lib/serega/validations/check_attribute_params.rb +3 -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/validations/utils/check_extra_keyword_arg.rb +33 -0
- data/lib/serega.rb +26 -11
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47eacc613b0a58af4dc552e09e0f55ed731f7e2a852e5c668794e7eb5128be69
|
4
|
+
data.tar.gz: bb9fec8432d12108e374bf15da74a230a4efda994835e9480e795b7222bb9ced
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7536453cd03fd6e0f65faf22f85f5a12de61ef2bda50d674cd5b31ab1fba815c4b94d5118d82902f5a434d06112cfdc16757eefd334ba217946f9e920442e05
|
7
|
+
data.tar.gz: 9bcd5c46c1ffa32f95bc02e821e94acd46408e4e65a0861e4738454e24a57767105a5d3a9af48d52c4662a1030819963f11ee8c317c84ed179eb44b282a02c73
|
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,21 +67,23 @@ 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 }
|
72
75
|
|
73
|
-
# Option :value can be used with callable object to define attribute value
|
76
|
+
# Option :value can be used with proc or callable object to define attribute value
|
77
|
+
attribute :first_name, value: UserProfile.new # must have #call method
|
74
78
|
attribute :first_name, value: proc { |user| user.profile&.first_name }
|
75
79
|
|
76
80
|
# Option :delegate can be used to define attribute value.
|
77
81
|
# Sub-option :allow_nil by default is false
|
78
82
|
attribute :first_name, delegate: { to: :profile, allow_nil: true }
|
79
83
|
|
80
|
-
# Option :delegate can be used with :
|
81
|
-
|
84
|
+
# Option :delegate can be used with :method sub-option, so method chain here
|
85
|
+
# is user.profile.fname
|
86
|
+
attribute :first_name, delegate: { to: :profile, method: :fname }
|
82
87
|
|
83
88
|
# Option :const specifies attribute with specific constant value
|
84
89
|
attribute(:type, const: 'user')
|
@@ -282,7 +287,7 @@ UserSerializer.to_h(bruce, with: fields_as_string)
|
|
282
287
|
|
283
288
|
# With not existing attribute
|
284
289
|
fields = %i[first_name enemy]
|
285
|
-
fields_as_string = 'first_name,enemy'
|
290
|
+
fields_as_string = 'first_name,enemy'
|
286
291
|
UserSerializer.new(only: fields).to_h(bruce)
|
287
292
|
UserSerializer.to_h(bruce, only: fields)
|
288
293
|
UserSerializer.to_h(bruce, only: fields_as_string)
|
@@ -433,7 +438,7 @@ end
|
|
433
438
|
class UserSerializer < AppSerializer
|
434
439
|
attribute :username
|
435
440
|
attribute :user_stats,
|
436
|
-
serializer: 'UserStatSerializer'
|
441
|
+
serializer: 'UserStatSerializer',
|
437
442
|
value: proc { |user| user },
|
438
443
|
preload: nil
|
439
444
|
end
|
@@ -545,15 +550,15 @@ It can be used to find value for attributes in optimal way:
|
|
545
550
|
After including plugin, attributes gain new `:batch` option:
|
546
551
|
|
547
552
|
```ruby
|
548
|
-
attribute :name, batch: {
|
553
|
+
attribute :name, batch: { loader: :name_loader, key: :id, default: nil }
|
549
554
|
```
|
550
555
|
|
551
556
|
`:batch` option must be a hash with this keys:
|
552
557
|
|
553
|
-
- `key` (required) [Symbol, Proc, callable] - Defines current object identifier.
|
554
|
-
Later `loader` will accept array of `keys` to detect this keys values.
|
555
558
|
- `loader` (required) [Symbol, Proc, callable] - Defines how to fetch values for
|
556
|
-
batch of keys. Receives 3 parameters: keys, context,
|
559
|
+
batch of keys. Receives 3 parameters: keys, context, plan.
|
560
|
+
- `key` (required) [Symbol, Proc, callable] - Defines current object identifier.
|
561
|
+
Key is optional if plugin was defined with `default_key` option.
|
557
562
|
- `default` (optional) - Default value for attribute.
|
558
563
|
By default it is `nil` or `[]` when attribute has option `many: true`
|
559
564
|
(ex: `attribute :tags, many: true, batch: { ... }`).
|
@@ -711,17 +716,24 @@ Adds ability to describe metadata and adds it to serialized response
|
|
711
716
|
|
712
717
|
Added class-level method `:meta_attribute`, to define metadata, it accepts:
|
713
718
|
|
714
|
-
-
|
715
|
-
-
|
716
|
-
|
719
|
+
- `*path` [Array of Symbols] - nested hash keys.
|
720
|
+
- `**options` [Hash]
|
721
|
+
|
722
|
+
- `:const` - describes metadata value (if it is constant)
|
723
|
+
- `:value` - describes metadata value as any `#callable` instance
|
724
|
+
- `:hide_nil` - does not show metadata key if value is nil, `false` by default
|
725
|
+
- `:hide_empty`, does not show metadata key if value is nil or empty,
|
726
|
+
`false` by default
|
727
|
+
|
728
|
+
- `&block` [Proc] - describes value for current meta attribute
|
717
729
|
|
718
730
|
```ruby
|
719
731
|
class AppSerializer < Serega
|
720
732
|
plugin :root
|
721
733
|
plugin :metadata
|
722
734
|
|
723
|
-
meta_attribute(:version
|
724
|
-
meta_attribute(:ab_tests, :names
|
735
|
+
meta_attribute(:version, const: '1.2.3')
|
736
|
+
meta_attribute(:ab_tests, :names, value: ABTests.new.method(:names))
|
725
737
|
meta_attribute(:meta, :paging, hide_nil: true) do |records, ctx|
|
726
738
|
next unless records.respond_to?(:total_count)
|
727
739
|
|
@@ -734,7 +746,7 @@ class AppSerializer < Serega
|
|
734
746
|
end
|
735
747
|
|
736
748
|
AppSerializer.to_h(nil)
|
737
|
-
# => {:data=>nil, :version=>"1.2.3", :ab_tests=>{:names=>
|
749
|
+
# => {:data=>nil, :version=>"1.2.3", :ab_tests=>{:names=> ... }}
|
738
750
|
```
|
739
751
|
|
740
752
|
### Plugin :context_metadata
|
@@ -765,19 +777,22 @@ UserSerializer.to_h(nil, meta: { version: '1.0.1' })
|
|
765
777
|
|
766
778
|
### Plugin :formatters
|
767
779
|
|
768
|
-
Allows to define `formatters` and apply them on
|
780
|
+
Allows to define `formatters` and apply them on attribute values.
|
769
781
|
|
770
782
|
Config option `config.formatters.add` can be used to add formatters.
|
771
783
|
|
772
|
-
Attribute option `:format`
|
784
|
+
Attribute option `:format` can be used with name of formatter or with
|
773
785
|
callable instance.
|
774
786
|
|
787
|
+
Formatters can accept up to 2 parameters (formatted object, context)
|
788
|
+
|
775
789
|
```ruby
|
776
790
|
class AppSerializer < Serega
|
777
791
|
plugin :formatters, formatters: {
|
778
792
|
iso8601: ->(value) { time.iso8601.round(6) },
|
779
793
|
on_off: ->(value) { value ? 'ON' : 'OFF' },
|
780
|
-
money: ->(value) { value
|
794
|
+
money: ->(value, ctx) { value / 10**ctx[:digits) }
|
795
|
+
date: DateTypeFormatter # callable
|
781
796
|
}
|
782
797
|
end
|
783
798
|
|
@@ -888,6 +903,88 @@ Look at [select serialized fields](#selecting-fields) for `:hide` usage examples
|
|
888
903
|
end
|
889
904
|
```
|
890
905
|
|
906
|
+
### Plugin :camel_case
|
907
|
+
|
908
|
+
By default when we add attribute like `attribute :first_name` this means:
|
909
|
+
|
910
|
+
- adding a `:first_name` key to resulted hash
|
911
|
+
- adding a `#first_name` method call result as value
|
912
|
+
|
913
|
+
But its often desired to response with *camelCased* keys.
|
914
|
+
By default this can be achieved by specifying attribute name and method directly
|
915
|
+
for each attribute: `attribute :firstName, method: first_name`
|
916
|
+
|
917
|
+
This plugin transforms all attribute names automatically.
|
918
|
+
We use simple regular expression to replace `_x` to `X` for the whole string.
|
919
|
+
We make this transformation only once when attribute is defined.
|
920
|
+
|
921
|
+
You can provide your own callable transformation when defining plugin,
|
922
|
+
for example `plugin :camel_case, transform: ->(name) { name.camelize }`
|
923
|
+
|
924
|
+
For any attribute camelCase-behavior can be skipped when
|
925
|
+
`camel_case: false` attribute option provided.
|
926
|
+
|
927
|
+
This plugin transforms only attribute keys,
|
928
|
+
without affecting `root`, `metadata`, `context_metadata` plugins keys.
|
929
|
+
|
930
|
+
If you wish to [select serialized fields](#selecting-fields), you should
|
931
|
+
provide them camelCased.
|
932
|
+
|
933
|
+
```ruby
|
934
|
+
class AppSerializer < Serega
|
935
|
+
plugin :camel_case
|
936
|
+
end
|
937
|
+
|
938
|
+
class UserSerializer < AppSerializer
|
939
|
+
attribute :first_name
|
940
|
+
attribute :last_name
|
941
|
+
attribute :full_name, camel_case: false,
|
942
|
+
value: proc { |user| [user.first_name, user.last_name].compact.join(" ") }
|
943
|
+
end
|
944
|
+
|
945
|
+
require "ostruct"
|
946
|
+
user = OpenStruct.new(first_name: "Bruce", last_name: "Wayne")
|
947
|
+
UserSerializer.to_h(user)
|
948
|
+
# => {firstName: "Bruce", lastName: "Wayne", full_name: "Bruce Wayne"}
|
949
|
+
|
950
|
+
UserSerializer.new(only: %i[firstName lastName]).to_h(user)
|
951
|
+
# => {firstName: "Bruce", lastName: "Wayne"}
|
952
|
+
```
|
953
|
+
|
954
|
+
### Plugin :depth_limit
|
955
|
+
|
956
|
+
Helps to secure from malicious queries that require to serialize too much
|
957
|
+
or from accidental serializing of objects with cyclic relations.
|
958
|
+
|
959
|
+
Depth limit is checked when constructing a serialization plan, that is when
|
960
|
+
`#new` method is called, ex: `SomeSerializer.new(with: params[:with])`.
|
961
|
+
It can be useful to instantiate serializer before any other business logic
|
962
|
+
to get possible errors earlier.
|
963
|
+
|
964
|
+
Any class-level serialization methods also check depth limit as they also
|
965
|
+
instantiate serializer.
|
966
|
+
|
967
|
+
When depth limit is exceeded `Serega::DepthLimitError` is raised.
|
968
|
+
Depth limit error details can be found in additional
|
969
|
+
`Serega::DepthLimitError#details` method
|
970
|
+
|
971
|
+
Limit can be checked or changed with next config options:
|
972
|
+
|
973
|
+
- `config.depth_limit.limit`
|
974
|
+
- `config.depth_limit.limit=`
|
975
|
+
|
976
|
+
There are no default limit, but it should be set when enabling plugin.
|
977
|
+
|
978
|
+
```ruby
|
979
|
+
class AppSerializer < Serega
|
980
|
+
plugin :depth_limit, limit: 10 # set limit for all child classes
|
981
|
+
end
|
982
|
+
|
983
|
+
class UserSerializer < AppSerializer
|
984
|
+
config.depth_limit.limit = 5 # overrides limit for UserSerializer
|
985
|
+
end
|
986
|
+
```
|
987
|
+
|
891
988
|
### Plugin :explicit_many_option
|
892
989
|
|
893
990
|
Plugin requires to add :many option when adding relationships
|
@@ -941,7 +1038,9 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
941
1038
|
|
942
1039
|
[activerecord_preloads]: #plugin-activerecord_preloads
|
943
1040
|
[batch]: #plugin-batch
|
1041
|
+
[camel_case]: #plugin-camel_case
|
944
1042
|
[context_metadata]: #plugin-context_metadata
|
1043
|
+
[depth_limit]: #plugin-depth_limit
|
945
1044
|
[formatters]: #plugin-formatters
|
946
1045
|
[metadata]: #plugin-metadata
|
947
1046
|
[preloads]: #plugin-preloads
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.17.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)
|
@@ -61,10 +61,10 @@ class Serega
|
|
61
61
|
# Shows specified serializer class
|
62
62
|
# @return [Serega, nil] Attribute serializer if exists
|
63
63
|
def serializer
|
64
|
-
|
65
|
-
return
|
64
|
+
serializer = @serializer
|
65
|
+
return serializer if (serializer.is_a?(Class) && (serializer < Serega)) || !serializer
|
66
66
|
|
67
|
-
@serializer =
|
67
|
+
@serializer = serializer.is_a?(String) ? Object.const_get(serializer, false) : serializer.call
|
68
68
|
end
|
69
69
|
|
70
70
|
#
|
@@ -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
|
#
|
@@ -97,8 +97,8 @@ class Serega
|
|
97
97
|
# - plugin :formatters (wraps resulted block in formatter block and formats :const values)
|
98
98
|
#
|
99
99
|
def prepare_value_block
|
100
|
-
|
101
|
-
|
100
|
+
prepare_init_block ||
|
101
|
+
prepare_value_option_block ||
|
102
102
|
prepare_const_block ||
|
103
103
|
prepare_delegate_block ||
|
104
104
|
prepare_keyword_block
|
@@ -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,17 +133,36 @@ 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
|
141
140
|
end
|
142
141
|
|
142
|
+
def prepare_init_block
|
143
|
+
prepare_callable_proc(init_block)
|
144
|
+
end
|
145
|
+
|
146
|
+
def prepare_value_option_block
|
147
|
+
prepare_callable_proc(init_opts[:value])
|
148
|
+
end
|
149
|
+
|
150
|
+
def prepare_callable_proc(callable)
|
151
|
+
return unless callable
|
152
|
+
|
153
|
+
params_count = SeregaUtils::ParamsCount.call(callable, max_count: 2)
|
154
|
+
case params_count
|
155
|
+
when 0 then proc { |obj, _ctx| callable.call }
|
156
|
+
when 1 then proc { |obj, _ctx| callable.call(obj) }
|
157
|
+
else callable
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
143
161
|
def prepare_delegate_block
|
144
162
|
delegate = init_opts[:delegate]
|
145
163
|
return unless delegate
|
146
164
|
|
147
|
-
key_method_name = delegate[:
|
165
|
+
key_method_name = delegate[:method] || method_name
|
148
166
|
delegate_to = delegate[:to]
|
149
167
|
|
150
168
|
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
|
|
@@ -22,17 +22,20 @@ class Serega
|
|
22
22
|
#
|
23
23
|
# @return [void]
|
24
24
|
#
|
25
|
-
def define(loader_name, &block)
|
26
|
-
|
27
|
-
raise SeregaError, "
|
25
|
+
def define(loader_name, callable = nil, &block)
|
26
|
+
if (!callable && !block) || (callable && block)
|
27
|
+
raise SeregaError, "Batch loader can be specified with one of arguments - callable value or &block"
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
callable ||= block
|
31
|
+
SeregaValidations::Utils::CheckExtraKeywordArg.call(callable, "batch loader `#{loader_name}`")
|
32
|
+
params_count = SeregaUtils::ParamsCount.call(callable, max_count: 3)
|
33
|
+
|
34
|
+
if params_count > 3
|
35
|
+
raise SeregaError, "Batch loader can have maximum 3 parameters (keys, context, plan)"
|
33
36
|
end
|
34
37
|
|
35
|
-
loaders[loader_name] =
|
38
|
+
loaders[loader_name] = callable
|
36
39
|
end
|
37
40
|
|
38
41
|
# Shows defined loaders
|
@@ -48,7 +51,7 @@ class Serega
|
|
48
51
|
#
|
49
52
|
# @return [Proc] batch loader block
|
50
53
|
def fetch_loader(loader_name)
|
51
|
-
loaders[loader_name] || (raise SeregaError, "Batch loader with name `#{loader_name.inspect}` was not defined. Define example: config.batch.define(:#{loader_name}) { |keys
|
54
|
+
loaders[loader_name] || (raise SeregaError, "Batch loader with name `#{loader_name.inspect}` was not defined. Define example: config.batch.define(:#{loader_name}) { |keys| ... }")
|
52
55
|
end
|
53
56
|
|
54
57
|
# Shows option to auto hide attributes with :batch specified
|
@@ -42,22 +42,37 @@ class Serega
|
|
42
42
|
batch = init_opts[:batch]
|
43
43
|
return unless batch
|
44
44
|
|
45
|
-
|
46
|
-
loader = batch[:loader]
|
45
|
+
loader = prepare_batch_loader(batch[:loader])
|
47
46
|
|
48
|
-
# take key
|
49
47
|
key = batch[:key] || self.class.serializer_class.config.batch.default_key
|
50
|
-
|
51
|
-
if key.is_a?(Symbol)
|
52
|
-
proc { |object| object.public_send(key) }
|
53
|
-
else
|
54
|
-
key
|
55
|
-
end
|
48
|
+
key = prepare_batch_key(key)
|
56
49
|
|
57
|
-
# take default value
|
58
50
|
default = batch.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
|
59
51
|
|
60
|
-
{loader: loader, key:
|
52
|
+
{loader: loader, key: key, default: default}
|
53
|
+
end
|
54
|
+
|
55
|
+
def prepare_batch_key(key)
|
56
|
+
return proc { |object| object.public_send(key) } if key.is_a?(Symbol)
|
57
|
+
|
58
|
+
params_count = SeregaUtils::ParamsCount.call(key, max_count: 2)
|
59
|
+
case params_count
|
60
|
+
when 0 then proc { key.call }
|
61
|
+
when 1 then proc { |object| key.call(object) }
|
62
|
+
else key
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def prepare_batch_loader(loader)
|
67
|
+
return loader if loader.is_a?(Symbol)
|
68
|
+
|
69
|
+
params_count = SeregaUtils::ParamsCount.call(loader, max_count: 3)
|
70
|
+
case params_count
|
71
|
+
when 0 then proc { loader.call }
|
72
|
+
when 1 then proc { |object| loader.call(object) }
|
73
|
+
when 2 then proc { |object, context| loader.call(object, context) }
|
74
|
+
else loader
|
75
|
+
end
|
61
76
|
end
|
62
77
|
end
|
63
78
|
end
|