serega 0.11.2 → 0.14.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 +163 -13
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +9 -4
- data/lib/serega/attribute_normalizer.rb +4 -13
- data/lib/serega/object_serializer.rb +11 -0
- data/lib/serega/plan.rb +20 -25
- data/lib/serega/plan_point.rb +13 -16
- data/lib/serega/plugins/batch/lib/loader.rb +25 -7
- data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +1 -9
- data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +69 -0
- data/lib/serega/plugins/explicit_many_option/validations/check_opt_many.rb +35 -0
- data/lib/serega/plugins/metadata/metadata.rb +5 -0
- data/lib/serega/plugins/openapi/lib/modules/config.rb +23 -0
- data/lib/serega/plugins/openapi/lib/openapi_config.rb +101 -0
- data/lib/serega/plugins/openapi/openapi.rb +245 -0
- data/lib/serega/plugins/preloads/lib/modules/attribute.rb +28 -0
- data/lib/serega/plugins/preloads/lib/modules/attribute_normalizer.rb +99 -0
- data/lib/serega/plugins/preloads/lib/modules/check_attribute_params.rb +22 -0
- data/lib/serega/plugins/preloads/lib/modules/config.rb +19 -0
- data/lib/serega/plugins/preloads/lib/modules/plan_point.rb +41 -0
- data/lib/serega/plugins/preloads/lib/preload_paths.rb +46 -0
- data/lib/serega/plugins/preloads/lib/preloads_config.rb +62 -0
- data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +20 -7
- data/lib/serega/plugins/preloads/preloads.rb +12 -210
- data/lib/serega/plugins/preloads/validations/check_opt_preload_path.rb +54 -15
- metadata +18 -7
- data/lib/serega/plugins/preloads/lib/main_preload_path.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f11821b5953500ef9f895ad8c96e97951734615363f3147c623b70519b6199a5
|
4
|
+
data.tar.gz: 8b03241a9def2e5a8777a75e61062f8b81544e497a1a1e490e085d52f14a326c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb5cb48e74c0e9c8307c6ceb6c47b918eb46ef3c160de90d0eb976239bbc036f1d3fb78219abd5567977f348e3e03d7e492e29140e6d85223fcf14289d794d89
|
7
|
+
data.tar.gz: acb7623ea317aab640aae70d0a5dfa0c436b706529741b36b15d938ab6af7523576f72ab4633b591b61dcdc38df768818eab3811005c248c3a00d2872cdccc46
|
data/README.md
CHANGED
@@ -415,30 +415,79 @@ UserSerializer.new(
|
|
415
415
|
```
|
416
416
|
|
417
417
|
---
|
418
|
-
One tricky case, that you will probably never see in real life:
|
419
418
|
|
420
|
-
|
419
|
+
#### SPECIFIC CASE #1: Serializing same object as association
|
420
|
+
|
421
|
+
For example you decided to show your current user as "user" and "user_stats".
|
422
|
+
Where stats rely on user fields and some other associations.
|
423
|
+
You should specify `preload: nil` to preload nested associations, if any, to "user".
|
421
424
|
|
422
425
|
```ruby
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
426
|
+
class AppSerializer < Serega
|
427
|
+
plugin :preloads,
|
428
|
+
auto_preload_attributes_with_delegate: true,
|
429
|
+
auto_preload_attributes_with_serializer: true,
|
430
|
+
auto_hide_attributes_with_preload: true
|
431
|
+
end
|
432
|
+
|
433
|
+
class UserSerializer < AppSerializer
|
434
|
+
attribute :username
|
435
|
+
attribute :user_stats,
|
436
|
+
serializer: 'UserStatSerializer'
|
437
|
+
value: proc { |user| user },
|
438
|
+
preload: nil
|
439
|
+
end
|
440
|
+
```
|
441
|
+
|
442
|
+
#### SPECIFIC CASE #2: Serializing multiple associations as single relation
|
443
|
+
|
444
|
+
For example "user" has two relations - "new_profile", "old_profile", and also
|
445
|
+
profiles have "avatar" association. And you decided to serialize profiles in one
|
446
|
+
array. You can specify `preload_path: [[:new_profile], [:old_profile]]` to
|
447
|
+
achieve this:
|
448
|
+
|
449
|
+
```ruby
|
450
|
+
class AppSerializer < Serega
|
451
|
+
plugin :preloads,
|
452
|
+
auto_preload_attributes_with_delegate: true,
|
453
|
+
auto_preload_attributes_with_serializer: true
|
454
|
+
end
|
455
|
+
|
456
|
+
class UserSerializer < AppSerializer
|
457
|
+
attribute :username
|
458
|
+
attribute :profiles,
|
459
|
+
serializer: 'ProfileSerializer',
|
460
|
+
value: proc { |user| [user.new_profile, user.old_profile] },
|
461
|
+
preload: [:new_profile, :old_profile],
|
462
|
+
preload_path: [[:new_profile], [:old_profile]] # <--- like here
|
463
|
+
end
|
464
|
+
|
465
|
+
class ProfileSerializer < AppSerializer
|
466
|
+
attribute :avatar, serializer: 'AvatarSerializer'
|
467
|
+
end
|
468
|
+
|
469
|
+
class AvatarSerializer < AppSerializer
|
470
|
+
end
|
471
|
+
|
472
|
+
UserSerializer.new.preloads
|
473
|
+
# => {:new_profile=>{:avatar=>{}}, :old_profile=>{:avatar=>{}}}
|
427
474
|
```
|
428
475
|
|
429
|
-
|
430
|
-
so nested associations, if any, will be preloaded to this `blob`.
|
431
|
-
If you need to preload them to `attachment`,
|
432
|
-
please specify additionally `:preload_path` option like this:
|
476
|
+
#### SPECIFIC CASE #3: Preload association through another association
|
433
477
|
|
434
478
|
```ruby
|
435
479
|
attribute :image,
|
480
|
+
preload: { attachment: :blob }, # <--------- like this one
|
481
|
+
value: proc { |record| record.attachment },
|
436
482
|
serializer: ImageSerializer,
|
437
|
-
|
438
|
-
preload_path: %i[attachment],
|
439
|
-
value: proc { |record| record.attachment }
|
483
|
+
preload_path: [:attachment] # or preload_path: [:attachment, :blob]
|
440
484
|
```
|
441
485
|
|
486
|
+
In this case we don't know if preloads defined in ImageSerializer, should be
|
487
|
+
preloaded to `attachment` or `blob`, so please specify `preload_path` manually.
|
488
|
+
You can specify `preload_path: nil` if you are sure that there are no preloads
|
489
|
+
inside ImageSerializer.
|
490
|
+
|
442
491
|
---
|
443
492
|
|
444
493
|
📌 Plugin `:preloads` only allows to group preloads together in single Hash, but
|
@@ -839,6 +888,107 @@ Look at [select serialized fields](#selecting-fields) for `:hide` usage examples
|
|
839
888
|
end
|
840
889
|
```
|
841
890
|
|
891
|
+
### Plugin :openapi
|
892
|
+
|
893
|
+
Helps to build OpenAPI schemas
|
894
|
+
|
895
|
+
This schemas can be easily used with [rswag](https://github.com/rswag/rswag#referenced-parameters-and-schema-definitions)"
|
896
|
+
gem by adding them to "config.swagger_docs"
|
897
|
+
|
898
|
+
Schemas properties will have no any "type" or other limits specified by default,
|
899
|
+
you should provide them as new attribute `:openapi` option.
|
900
|
+
|
901
|
+
This plugin adds type "object" or "array" only for relationships and marks
|
902
|
+
attributes as **required** if they have no `:hide` option set
|
903
|
+
(manually or automatically).
|
904
|
+
|
905
|
+
After enabling this plugin attributes with :serializer option will have
|
906
|
+
to have `:many` option set to construct "object" or "array" openapi
|
907
|
+
property type.
|
908
|
+
|
909
|
+
- constructing all serializers schemas:
|
910
|
+
`Serega::OpenAPI.schemas`
|
911
|
+
- constructing specific serializers schemas:
|
912
|
+
`Serega::OpenAPI.schemas(Serega::OpenAPI.serializers - [MyBaseSerializer])`
|
913
|
+
- constructing one serializer schema:
|
914
|
+
`SomeSerializer.openapi_schema`
|
915
|
+
|
916
|
+
```ruby
|
917
|
+
class BaseSerializer < Serega
|
918
|
+
plugin :openapi
|
919
|
+
end
|
920
|
+
|
921
|
+
class UserSerializer < BaseSerializer
|
922
|
+
attribute :name, openapi: { type: "string" }
|
923
|
+
|
924
|
+
openapi_properties(
|
925
|
+
name: { type: :string }
|
926
|
+
)
|
927
|
+
end
|
928
|
+
|
929
|
+
class PostSerializer < BaseSerializer
|
930
|
+
attribute :text, openapi: { type: "string" }
|
931
|
+
attribute :user, serializer: UserSerializer, many: false
|
932
|
+
attribute :comments, serializer: PostSerializer, many: true, hide: true
|
933
|
+
|
934
|
+
openapi_properties(
|
935
|
+
text: { type: :string },
|
936
|
+
user: { type: 'object' }, # `$ref` added automatically
|
937
|
+
comments: { type: 'array' } # `items` option with `$ref` added automatically
|
938
|
+
)
|
939
|
+
end
|
940
|
+
|
941
|
+
puts Serega::OpenAPI.schemas
|
942
|
+
# =>
|
943
|
+
# {
|
944
|
+
# "PostSerializer" => {
|
945
|
+
# type: "object",
|
946
|
+
# properties: {
|
947
|
+
# text: {type: "string"},
|
948
|
+
# user: {:$ref => "#/components/schemas/UserSerializer"},
|
949
|
+
# comments: {type: "array", items: {:$ref => "#/components/schemas/PostSerializer"}}
|
950
|
+
# },
|
951
|
+
# required: [:text, :comments],
|
952
|
+
# additionalProperties: false
|
953
|
+
# },
|
954
|
+
# "UserSerializer" => {
|
955
|
+
# type: "object",
|
956
|
+
# properties: {
|
957
|
+
# name: {type: "string"}
|
958
|
+
# },
|
959
|
+
# required: [:name],
|
960
|
+
# additionalProperties: false
|
961
|
+
# }
|
962
|
+
# }
|
963
|
+
```
|
964
|
+
|
965
|
+
### Plugin :explicit_many_option
|
966
|
+
|
967
|
+
Plugin requires to add :many option when adding relationships
|
968
|
+
(relationships are attributes with :serializer option specified)
|
969
|
+
|
970
|
+
Adding this plugin makes clearer to find if relationship returns array or single
|
971
|
+
object
|
972
|
+
|
973
|
+
Also plugin `:openapi` load this plugin automatically as it need to know if
|
974
|
+
relationship is array
|
975
|
+
|
976
|
+
```ruby
|
977
|
+
class BaseSerializer < Serega
|
978
|
+
plugin :explicit_many_option
|
979
|
+
end
|
980
|
+
|
981
|
+
class UserSerializer < BaseSerializer
|
982
|
+
attribute :name
|
983
|
+
end
|
984
|
+
|
985
|
+
class PostSerializer < BaseSerializer
|
986
|
+
attribute :text
|
987
|
+
attribute :user, serializer: UserSerializer, many: false
|
988
|
+
attribute :comments, serializer: PostSerializer, many: true
|
989
|
+
end
|
990
|
+
```
|
991
|
+
|
842
992
|
## Errors
|
843
993
|
|
844
994
|
- `Serega::SeregaError` is a base error raised by this gem.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.14.0
|
data/lib/serega/attribute.rb
CHANGED
@@ -82,13 +82,18 @@ class Serega
|
|
82
82
|
#
|
83
83
|
# Checks if attribute must be added to serialized response
|
84
84
|
#
|
85
|
-
# @param
|
86
|
-
# @
|
87
|
-
# @
|
85
|
+
# @param modifiers [Hash] Serialization modifiers
|
86
|
+
# @option modifiers [Hash] :only The only attributes to serialize
|
87
|
+
# @option modifiers [Hash] :except Attributes to hide
|
88
|
+
# @option modifiers [Hash] :with Hidden attributes to serialize additionally
|
88
89
|
#
|
89
90
|
# @return [Boolean]
|
90
91
|
#
|
91
|
-
def visible?(
|
92
|
+
def visible?(modifiers)
|
93
|
+
except = modifiers[:except] || FROZEN_EMPTY_HASH
|
94
|
+
only = modifiers[:only] || FROZEN_EMPTY_HASH
|
95
|
+
with = modifiers[:with] || FROZEN_EMPTY_HASH
|
96
|
+
|
92
97
|
return false if except.member?(name) && except[name].empty?
|
93
98
|
return true if only.member?(name)
|
94
99
|
return true if with.member?(name)
|
@@ -107,6 +107,7 @@ class Serega
|
|
107
107
|
#
|
108
108
|
# Patched in:
|
109
109
|
# - plugin :preloads (returns true by default if config option auto_hide_attribute_with_preloads is enabled)
|
110
|
+
# - plugin :batch (returns true by default if auto_hide option was set and attribute has batch loader)
|
110
111
|
#
|
111
112
|
def prepare_hide
|
112
113
|
init_opts[:hide]
|
@@ -135,7 +136,7 @@ class Serega
|
|
135
136
|
def prepare_keyword_block
|
136
137
|
key_method_name = key
|
137
138
|
proc do |object|
|
138
|
-
|
139
|
+
object.public_send(key_method_name)
|
139
140
|
end
|
140
141
|
end
|
141
142
|
|
@@ -150,24 +151,14 @@ class Serega
|
|
150
151
|
|
151
152
|
if allow_nil
|
152
153
|
proc do |object|
|
153
|
-
|
154
|
-
object.public_send(delegate_to)&.public_send(key_method_name)
|
155
|
-
end
|
154
|
+
object.public_send(delegate_to)&.public_send(key_method_name)
|
156
155
|
end
|
157
156
|
else
|
158
157
|
proc do |object|
|
159
|
-
|
160
|
-
object.public_send(delegate_to).public_send(key_method_name)
|
161
|
-
end
|
158
|
+
object.public_send(delegate_to).public_send(key_method_name)
|
162
159
|
end
|
163
160
|
end
|
164
161
|
end
|
165
|
-
|
166
|
-
def handle_no_method_error
|
167
|
-
yield
|
168
|
-
rescue NoMethodError => error
|
169
|
-
raise error, "NoMethodError when serializing '#{name}' attribute in #{self.class.serializer_class}\n\n#{error.message}", error.backtrace
|
170
|
-
end
|
171
162
|
end
|
172
163
|
|
173
164
|
extend Serega::SeregaHelpers::SerializerClassHelper
|
@@ -47,6 +47,10 @@ class Serega
|
|
47
47
|
def serialize_object(object)
|
48
48
|
plan.points.each_with_object({}) do |point, container|
|
49
49
|
serialize_point(object, point, container)
|
50
|
+
rescue SeregaError
|
51
|
+
raise
|
52
|
+
rescue => error
|
53
|
+
reraise_with_serialized_attribute_details(error, point)
|
50
54
|
end
|
51
55
|
end
|
52
56
|
|
@@ -85,6 +89,13 @@ class Serega
|
|
85
89
|
def array?(object, many)
|
86
90
|
many.nil? ? object.is_a?(Enumerable) : many
|
87
91
|
end
|
92
|
+
|
93
|
+
def reraise_with_serialized_attribute_details(error, point)
|
94
|
+
raise error.exception(<<~MESSAGE.strip)
|
95
|
+
#{error.message}
|
96
|
+
(when serializing '#{point.name}' attribute in #{self.class.serializer_class})
|
97
|
+
MESSAGE
|
98
|
+
end
|
88
99
|
end
|
89
100
|
|
90
101
|
extend Serega::SeregaHelpers::SerializerClassHelper
|
data/lib/serega/plan.rb
CHANGED
@@ -22,34 +22,22 @@ 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
|
25
|
+
return new(opts) if max_cache_size.zero?
|
26
26
|
|
27
27
|
cached_plan_for(opts, max_cache_size)
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
|
-
def plan_for(opts)
|
33
|
-
new(**modifiers(opts))
|
34
|
-
end
|
35
|
-
|
36
32
|
def cached_plan_for(opts, max_cache_size)
|
37
33
|
@cache ||= {}
|
38
34
|
cache_key = construct_cache_key(opts)
|
39
35
|
|
40
|
-
plan = @cache[cache_key] ||=
|
36
|
+
plan = @cache[cache_key] ||= new(opts)
|
41
37
|
@cache.shift if @cache.length > max_cache_size
|
42
38
|
plan
|
43
39
|
end
|
44
40
|
|
45
|
-
def modifiers(opts)
|
46
|
-
{
|
47
|
-
only: opts[:only] || FROZEN_EMPTY_HASH,
|
48
|
-
except: opts[:except] || FROZEN_EMPTY_HASH,
|
49
|
-
with: opts[:with] || FROZEN_EMPTY_HASH
|
50
|
-
}
|
51
|
-
end
|
52
|
-
|
53
41
|
def construct_cache_key(opts, cache_key = nil)
|
54
42
|
return nil if opts.empty?
|
55
43
|
|
@@ -69,10 +57,14 @@ class Serega
|
|
69
57
|
# SeregaPlan instance methods
|
70
58
|
#
|
71
59
|
module InstanceMethods
|
72
|
-
# Parent plan point
|
60
|
+
# Parent plan point
|
73
61
|
# @return [SeregaPlanPoint, nil]
|
74
62
|
attr_reader :parent_plan_point
|
75
63
|
|
64
|
+
# Sets new parent plan point
|
65
|
+
# @return [SeregaPlanPoint] new parent plan point
|
66
|
+
attr_writer :parent_plan_point
|
67
|
+
|
76
68
|
# Serialization points
|
77
69
|
# @return [Array<SeregaPlanPoint>] points to serialize
|
78
70
|
attr_reader :points
|
@@ -80,17 +72,15 @@ class Serega
|
|
80
72
|
#
|
81
73
|
# Instantiate new serialization plan.
|
82
74
|
#
|
83
|
-
# @param
|
84
|
-
# @option
|
85
|
-
# @option
|
86
|
-
# @option
|
87
|
-
# @option opts [Hash] :with Attributes (usually marked hide: true`) to serialize additionally
|
75
|
+
# @param modifiers Serialization parameters
|
76
|
+
# @option modifiers [Hash] :only The only attributes to serialize
|
77
|
+
# @option modifiers [Hash] :except Attributes to hide
|
78
|
+
# @option modifiers [Hash] :with Hidden attributes to serialize additionally
|
88
79
|
#
|
89
80
|
# @return [SeregaPlan] Serialization plan
|
90
81
|
#
|
91
|
-
def initialize(
|
92
|
-
@
|
93
|
-
@points = attributes_points(only: only, except: except, with: with)
|
82
|
+
def initialize(modifiers)
|
83
|
+
@points = attributes_points(modifiers)
|
94
84
|
end
|
95
85
|
|
96
86
|
#
|
@@ -102,7 +92,10 @@ class Serega
|
|
102
92
|
|
103
93
|
private
|
104
94
|
|
105
|
-
def attributes_points(
|
95
|
+
def attributes_points(modifiers)
|
96
|
+
only = modifiers[:only] || FROZEN_EMPTY_HASH
|
97
|
+
except = modifiers[:except] || FROZEN_EMPTY_HASH
|
98
|
+
with = modifiers[:with] || FROZEN_EMPTY_HASH
|
106
99
|
points = []
|
107
100
|
|
108
101
|
serializer_class.attributes.each_value do |attribute|
|
@@ -114,7 +107,9 @@ class Serega
|
|
114
107
|
{only: only[name], with: with[name], except: except[name]}
|
115
108
|
end
|
116
109
|
|
117
|
-
|
110
|
+
point = serializer_class::SeregaPlanPoint.new(attribute, child_fields)
|
111
|
+
point.plan = self
|
112
|
+
points << point.freeze
|
118
113
|
end
|
119
114
|
|
120
115
|
points.freeze
|
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_accessor :plan
|
17
17
|
|
18
18
|
# Shows current attribute
|
19
19
|
# @return [SeregaAttribute] Current attribute
|
@@ -24,8 +24,8 @@ class Serega
|
|
24
24
|
attr_reader :child_plan
|
25
25
|
|
26
26
|
# Child fields to serialize
|
27
|
-
# @return [
|
28
|
-
attr_reader :
|
27
|
+
# @return [Hash] Attributes to serialize
|
28
|
+
attr_reader :modifiers
|
29
29
|
|
30
30
|
# @!method name
|
31
31
|
# Attribute `name`
|
@@ -44,18 +44,18 @@ class Serega
|
|
44
44
|
#
|
45
45
|
# Initializes plan point
|
46
46
|
#
|
47
|
-
# @param plan [SeregaPlan] Plan where this point belongs to.
|
48
47
|
# @param attribute [SeregaAttribute] Attribute to construct plan point
|
49
|
-
# @param
|
48
|
+
# @param modifiers Serialization parameters
|
49
|
+
# @option modifiers [Hash] :only The only attributes to serialize
|
50
|
+
# @option modifiers [Hash] :except Attributes to hide
|
51
|
+
# @option modifiers [Hash] :with Hidden attributes to serialize additionally
|
50
52
|
#
|
51
53
|
# @return [SeregaPlanPoint] New plan point
|
52
54
|
#
|
53
|
-
def initialize(attribute,
|
54
|
-
@plan = plan
|
55
|
+
def initialize(attribute, modifiers = nil)
|
55
56
|
@attribute = attribute
|
56
|
-
@
|
57
|
+
@modifiers = modifiers
|
57
58
|
set_normalized_vars
|
58
|
-
freeze
|
59
59
|
end
|
60
60
|
|
61
61
|
#
|
@@ -77,14 +77,11 @@ class Serega
|
|
77
77
|
def prepare_child_plan
|
78
78
|
return unless serializer
|
79
79
|
|
80
|
-
fields =
|
80
|
+
fields = modifiers || FROZEN_EMPTY_HASH
|
81
81
|
|
82
|
-
serializer::SeregaPlan.new(
|
83
|
-
|
84
|
-
|
85
|
-
with: fields[:with] || FROZEN_EMPTY_HASH,
|
86
|
-
except: fields[:except] || FROZEN_EMPTY_HASH
|
87
|
-
)
|
82
|
+
plan = serializer::SeregaPlan.new(fields)
|
83
|
+
plan.parent_plan_point = self
|
84
|
+
plan
|
88
85
|
end
|
89
86
|
end
|
90
87
|
|
@@ -60,6 +60,10 @@ class Serega
|
|
60
60
|
|
61
61
|
private
|
62
62
|
|
63
|
+
def keys
|
64
|
+
@keys ||= {}
|
65
|
+
end
|
66
|
+
|
63
67
|
def each_key
|
64
68
|
keys.each do |key, containers|
|
65
69
|
containers.each do |container|
|
@@ -74,16 +78,30 @@ class Serega
|
|
74
78
|
def keys_values
|
75
79
|
ids = keys.keys
|
76
80
|
|
77
|
-
|
78
|
-
|
81
|
+
keys_values = load_keys_values(ids)
|
82
|
+
validate(keys_values)
|
79
83
|
|
80
|
-
|
81
|
-
raise SeregaError, "Batch loader for `#{attribute_name}` must return Hash, but #{vals.inspect} was returned"
|
82
|
-
end
|
84
|
+
keys_values
|
83
85
|
end
|
84
86
|
|
85
|
-
def
|
86
|
-
|
87
|
+
def load_keys_values(ids)
|
88
|
+
point.batch[:loader].call(ids, object_serializer.context, point)
|
89
|
+
rescue => error
|
90
|
+
raise reraise_with_serialized_attribute_details(error, point)
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate(keys_values)
|
94
|
+
return if keys_values.is_a?(Hash)
|
95
|
+
|
96
|
+
attribute_name = "#{point.class.serializer_class}.#{point.name}"
|
97
|
+
raise SeregaError, "Batch loader for `#{attribute_name}` must return Hash, but #{keys_values.inspect} was returned"
|
98
|
+
end
|
99
|
+
|
100
|
+
def reraise_with_serialized_attribute_details(error, point)
|
101
|
+
raise error.exception(<<~MESSAGE.strip)
|
102
|
+
#{error.message}
|
103
|
+
(when serializing '#{point.name}' attribute in #{self.class.serializer_class})
|
104
|
+
MESSAGE
|
87
105
|
end
|
88
106
|
end
|
89
107
|
|
@@ -49,9 +49,7 @@ class Serega
|
|
49
49
|
key = batch[:key] || self.class.serializer_class.config.batch.default_key
|
50
50
|
proc_key =
|
51
51
|
if key.is_a?(Symbol)
|
52
|
-
proc
|
53
|
-
handle_no_method_error { object.public_send(key) }
|
54
|
-
end
|
52
|
+
proc { |object| object.public_send(key) }
|
55
53
|
else
|
56
54
|
key
|
57
55
|
end
|
@@ -61,12 +59,6 @@ class Serega
|
|
61
59
|
|
62
60
|
{loader: loader, key: proc_key, default: default}
|
63
61
|
end
|
64
|
-
|
65
|
-
def handle_no_method_error
|
66
|
-
yield
|
67
|
-
rescue NoMethodError => error
|
68
|
-
raise error, "NoMethodError when serializing '#{name}' attribute in #{self.class.serializer_class}\n\n#{error.message}", error.backtrace
|
69
|
-
end
|
70
62
|
end
|
71
63
|
end
|
72
64
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Serega
|
4
|
+
module SeregaPlugins
|
5
|
+
#
|
6
|
+
# Plugin :explicit_many_option
|
7
|
+
#
|
8
|
+
# Plugin requires to add :many option when adding relationships
|
9
|
+
# (relationships are attributes with :serializer option specified)
|
10
|
+
#
|
11
|
+
# Adding this plugin makes clearer to find if relationship returns array or single object
|
12
|
+
#
|
13
|
+
# Also some plugins like :openapi load this plugin automatically as they need to know if
|
14
|
+
# relationship is array
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# class BaseSerializer < Serega
|
18
|
+
# plugin :explicit_many_option
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# class UserSerializer < BaseSerializer
|
22
|
+
# attribute :name
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# class PostSerializer < BaseSerializer
|
26
|
+
# attribute :text
|
27
|
+
# attribute :user, serializer: UserSerializer, many: false
|
28
|
+
# attribute :comments, serializer: PostSerializer, many: true
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
module ExplicitManyOption
|
32
|
+
# @return [Symbol] Plugin name
|
33
|
+
def self.plugin_name
|
34
|
+
:explicit_many_option
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Applies plugin code to specific serializer
|
39
|
+
#
|
40
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
41
|
+
# @param _opts [Hash] Loaded plugins options
|
42
|
+
#
|
43
|
+
# @return [void]
|
44
|
+
#
|
45
|
+
def self.load_plugin(serializer_class, **_opts)
|
46
|
+
require_relative "./validations/check_opt_many"
|
47
|
+
|
48
|
+
serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
|
53
|
+
#
|
54
|
+
# @see Serega::SeregaValidations::CheckAttributeParams
|
55
|
+
#
|
56
|
+
module CheckAttributeParamsInstanceMethods
|
57
|
+
private
|
58
|
+
|
59
|
+
def check_opts
|
60
|
+
super
|
61
|
+
|
62
|
+
CheckOptMany.call(opts)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
register_plugin(ExplicitManyOption.plugin_name, ExplicitManyOption)
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Serega
|
4
|
+
module SeregaPlugins
|
5
|
+
module ExplicitManyOption
|
6
|
+
#
|
7
|
+
# Validator for attribute :many option
|
8
|
+
#
|
9
|
+
class CheckOptMany
|
10
|
+
class << self
|
11
|
+
#
|
12
|
+
# Checks attribute :many option must be provided with relations
|
13
|
+
#
|
14
|
+
# @param opts [Hash] Attribute options
|
15
|
+
#
|
16
|
+
# @raise [SeregaError] Attribute validation error
|
17
|
+
#
|
18
|
+
# @return [void]
|
19
|
+
#
|
20
|
+
def call(opts)
|
21
|
+
serializer = opts[:serializer]
|
22
|
+
return unless serializer
|
23
|
+
|
24
|
+
many_option_exists = opts.key?(:many)
|
25
|
+
return if many_option_exists
|
26
|
+
|
27
|
+
raise SeregaError,
|
28
|
+
"Attribute option :many [Boolean] must be provided" \
|
29
|
+
" for attributes with :serializer option"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -194,6 +194,11 @@ class Serega
|
|
194
194
|
next unless metadata
|
195
195
|
|
196
196
|
deep_merge_metadata(hash, metadata)
|
197
|
+
rescue => error
|
198
|
+
raise error.exception(<<~MESSAGE.strip)
|
199
|
+
#{error.message}
|
200
|
+
(when serializing meta_attribute #{meta_attribute.path.inspect} in #{self.class})
|
201
|
+
MESSAGE
|
197
202
|
end
|
198
203
|
end
|
199
204
|
|