serega 0.17.0 → 0.19.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 +260 -199
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +6 -1
- data/lib/serega/attribute_normalizer.rb +31 -1
- data/lib/serega/config.rb +1 -1
- data/lib/serega/object_serializer.rb +1 -1
- data/lib/serega/plugins/batch/batch.rb +11 -56
- data/lib/serega/plugins/batch/lib/batch_config.rb +13 -21
- data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +10 -12
- data/lib/serega/plugins/batch/lib/modules/object_serializer.rb +2 -2
- data/lib/serega/plugins/batch/lib/modules/plan_point.rb +2 -19
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_id_method.rb +43 -0
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +21 -5
- data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +10 -11
- metadata +4 -4
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_key.rb +0 -43
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# Serega Ruby Serializer
|
7
7
|
|
8
8
|
The Serega Ruby Serializer provides easy and powerful DSL to describe your
|
9
|
-
objects and
|
9
|
+
objects and serialize them to Hash or JSON.
|
10
10
|
|
11
11
|
---
|
12
12
|
|
@@ -24,7 +24,8 @@ It has some great features:
|
|
24
24
|
- Adding custom metadata (via [metadata][metadata] or
|
25
25
|
[context_metadata][context_metadata] plugins)
|
26
26
|
- Value formatters ([formatters][formatters] plugin) helps to transform
|
27
|
-
time, date, money, percentage and any other values same way keeping
|
27
|
+
time, date, money, percentage, and any other values in the same way keeping
|
28
|
+
the code dry
|
28
29
|
- Conditional attributes - ([if][if] plugin)
|
29
30
|
- Auto camelCase keys - [camel_case][camel_case] plugin
|
30
31
|
|
@@ -67,13 +68,14 @@ class UserSerializer < Serega
|
|
67
68
|
# Regular attribute
|
68
69
|
attribute :first_name
|
69
70
|
|
70
|
-
# Option :method specifies method that must be called on serialized object
|
71
|
+
# Option :method specifies the method that must be called on the serialized object
|
71
72
|
attribute :first_name, method: :old_first_name
|
72
73
|
|
73
74
|
# Block is used to define attribute value
|
74
75
|
attribute(:first_name) { |user| user.profile&.first_name }
|
75
76
|
|
76
|
-
# Option :value can be used with
|
77
|
+
# Option :value can be used with a Proc or callable object to define attribute
|
78
|
+
# value
|
77
79
|
attribute :first_name, value: UserProfile.new # must have #call method
|
78
80
|
attribute :first_name, value: proc { |user| user.profile&.first_name }
|
79
81
|
|
@@ -85,35 +87,42 @@ class UserSerializer < Serega
|
|
85
87
|
# is user.profile.fname
|
86
88
|
attribute :first_name, delegate: { to: :profile, method: :fname }
|
87
89
|
|
88
|
-
# Option :
|
90
|
+
# Option :default can be used to replace possible nil values.
|
91
|
+
attribute :first_name, default: ''
|
92
|
+
attribute :is_active, default: false
|
93
|
+
attribute :comments_count, default: 0
|
94
|
+
|
95
|
+
# Option :const specifies attribute with a specific constant value
|
89
96
|
attribute(:type, const: 'user')
|
90
97
|
|
91
98
|
# Option :hide specifies attributes that should not be serialized by default
|
92
99
|
attribute :tags, hide: true
|
93
100
|
|
94
101
|
# Option :serializer specifies nested serializer for attribute
|
95
|
-
# We can
|
96
|
-
# Use String or Proc if you have cross
|
102
|
+
# We can define the `:serializer` value as a Class, String, or Proc.
|
103
|
+
# Use String or Proc if you have cross-references in serializers.
|
97
104
|
attribute :posts, serializer: PostSerializer
|
98
105
|
attribute :posts, serializer: "PostSerializer"
|
99
106
|
attribute :posts, serializer: -> { PostSerializer }
|
100
107
|
|
101
|
-
# Option `:many` specifies a has_many relationship
|
102
|
-
#
|
108
|
+
# Option `:many` specifies a has_many relationship. It is optional.
|
109
|
+
# If not specified, it is defined during serialization by checking `object.is_a?(Enumerable)`
|
110
|
+
# Also the `:many` changes the default value from `nil` to `[]`.
|
103
111
|
attribute :posts, serializer: PostSerializer, many: true
|
104
112
|
|
105
113
|
# Option `:preload` can be specified when enabled `:preloads` plugin
|
106
114
|
# It allows to specify associations to preload to attribute value
|
107
115
|
attribute(:email, preload: :emails) { |user| user.emails.find(&:verified?) }
|
108
116
|
|
109
|
-
# Options `:if
|
110
|
-
# when
|
111
|
-
#
|
117
|
+
# Options `:if, :unless, :if_value and :unless_value` can be specified
|
118
|
+
# when `:if` plugin is enabled. They hide the attribute key and value from the
|
119
|
+
# response.
|
120
|
+
# See more usage examples in the `:if` plugin section.
|
112
121
|
attribute :email, if: proc { |user, ctx| user == ctx[:current_user] }
|
113
122
|
attribute :email, if_value: :present?
|
114
123
|
|
115
124
|
# Option `:format` can be specified when enabled `:formatters` plugin
|
116
|
-
# It changes attribute value
|
125
|
+
# It changes the attribute value
|
117
126
|
attribute :created_at, format: :iso_time
|
118
127
|
attribute :updated_at, format: :iso_time
|
119
128
|
|
@@ -127,10 +136,10 @@ end
|
|
127
136
|
⚠️ Attribute names are checked to include only "a-z", "A-Z", "0-9", "\_", "-",
|
128
137
|
"~" characters.
|
129
138
|
|
130
|
-
We allow ONLY
|
139
|
+
We allow ONLY these characters as we want to be able to use attribute names in
|
131
140
|
URLs without escaping.
|
132
141
|
|
133
|
-
|
142
|
+
The check can be turned off:
|
134
143
|
|
135
144
|
```ruby
|
136
145
|
# Disable globally
|
@@ -146,7 +155,7 @@ end
|
|
146
155
|
|
147
156
|
We can serialize objects using class methods `.to_h`, `.to_json`, `.as_json` and
|
148
157
|
same instance methods `#to_h`, `#to_json`, `#as_json`.
|
149
|
-
`to_h` method is also aliased as `call`.
|
158
|
+
The `to_h` method is also aliased as `call`.
|
150
159
|
|
151
160
|
```ruby
|
152
161
|
user = OpenStruct.new(username: 'serega')
|
@@ -165,9 +174,9 @@ UserSerializer.as_json(user) # => {"username":"serega"}
|
|
165
174
|
UserSerializer.as_json([user]) # => [{"username":"serega"}]
|
166
175
|
```
|
167
176
|
|
168
|
-
If
|
169
|
-
|
170
|
-
prepared only once).
|
177
|
+
If serialized fields are constant, then it's a good idea to initiate the
|
178
|
+
serializer and reuse it.
|
179
|
+
It will be a bit faster (the serialization plan will be prepared only once).
|
171
180
|
|
172
181
|
```ruby
|
173
182
|
# Example with all fields
|
@@ -182,7 +191,7 @@ serializer.to_h(user2)
|
|
182
191
|
```
|
183
192
|
|
184
193
|
---
|
185
|
-
⚠️ When you serialize `Struct` object, specify manually `many: false`. As Struct
|
194
|
+
⚠️ When you serialize the `Struct` object, specify manually `many: false`. As Struct
|
186
195
|
is Enumerable and we check `object.is_a?(Enumerable)` to detect if we should
|
187
196
|
return array.
|
188
197
|
|
@@ -192,26 +201,27 @@ UserSerializer.to_h(user_struct, many: false)
|
|
192
201
|
|
193
202
|
### Selecting Fields
|
194
203
|
|
195
|
-
By default all attributes are serialized (except marked as `hide: true`).
|
204
|
+
By default, all attributes are serialized (except marked as `hide: true`).
|
196
205
|
|
197
|
-
We can provide **modifiers** to select
|
206
|
+
We can provide **modifiers** to select serialized attributes:
|
198
207
|
|
199
|
-
- *only* - lists attributes to serialize;
|
208
|
+
- *only* - lists specific attributes to serialize;
|
200
209
|
- *except* - lists attributes to not serialize;
|
201
210
|
- *with* - lists attributes to serialize additionally (By default all attributes
|
202
211
|
are exposed and will be serialized, but some attributes can be hidden when
|
203
|
-
they are defined with `hide: true` option, more on this below. `with`
|
204
|
-
can be used to expose such attributes).
|
212
|
+
they are defined with the `hide: true` option, more on this below. `with`
|
213
|
+
modifier can be used to expose such attributes).
|
205
214
|
|
206
|
-
Modifiers can be provided as Hash, Array, String, Symbol or their combinations.
|
215
|
+
Modifiers can be provided as Hash, Array, String, Symbol, or their combinations.
|
207
216
|
|
208
217
|
With plugin [string_modifiers][string_modifiers] we can provide modifiers as
|
209
218
|
single `String` with attributes split by comma `,` and nested values inside
|
210
219
|
brackets `()`, like: `username,enemies(username,email)`. This can be very useful
|
211
|
-
to accept list of fields in **GET** requests.
|
220
|
+
to accept the list of fields in **GET** requests.
|
212
221
|
|
213
|
-
When
|
214
|
-
raised. This error can be muted with `check_initiate_params: false`
|
222
|
+
When a non-existing attribute is provided, the `Serega::AttributeNotExist` error
|
223
|
+
will be raised. This error can be muted with the `check_initiate_params: false`
|
224
|
+
option.
|
215
225
|
|
216
226
|
```ruby
|
217
227
|
class UserSerializer < Serega
|
@@ -285,7 +295,7 @@ UserSerializer.to_h(bruce, with: fields_as_string)
|
|
285
295
|
# ]
|
286
296
|
# }
|
287
297
|
|
288
|
-
# With
|
298
|
+
# With no existing attribute
|
289
299
|
fields = %i[first_name enemy]
|
290
300
|
fields_as_string = 'first_name,enemy'
|
291
301
|
UserSerializer.new(only: fields).to_h(bruce)
|
@@ -293,7 +303,7 @@ UserSerializer.to_h(bruce, only: fields)
|
|
293
303
|
UserSerializer.to_h(bruce, only: fields_as_string)
|
294
304
|
# => raises Serega::AttributeNotExist, "Attribute 'enemy' not exists"
|
295
305
|
|
296
|
-
# With
|
306
|
+
# With no existing attribute and disabled validation
|
297
307
|
fields = %i[first_name enemy]
|
298
308
|
fields_as_string = 'first_name,enemy'
|
299
309
|
UserSerializer.new(only: fields, check_initiate_params: false).to_h(bruce)
|
@@ -304,7 +314,7 @@ UserSerializer.to_h(bruce, only: fields_as_string, check_initiate_params: false)
|
|
304
314
|
|
305
315
|
### Using Context
|
306
316
|
|
307
|
-
Sometimes
|
317
|
+
Sometimes it can be required to use the context during serialization, like
|
308
318
|
current_user or any.
|
309
319
|
|
310
320
|
```ruby
|
@@ -324,28 +334,28 @@ UserSerializer.new.to_h(user, context: {current_user: user}) # same
|
|
324
334
|
|
325
335
|
## Configuration
|
326
336
|
|
327
|
-
|
337
|
+
Here are the default options. Other options can be added with plugins.
|
328
338
|
|
329
339
|
```ruby
|
330
340
|
class AppSerializer < Serega
|
331
341
|
# Configure adapter to serialize to JSON.
|
332
|
-
# It is `JSON.dump` by default.
|
333
|
-
# `Oj.dump(data, mode: :compat)`
|
342
|
+
# It is `JSON.dump` by default. But if the Oj gem is loaded, then the default
|
343
|
+
# is changed to `Oj.dump(data, mode: :compat)`
|
334
344
|
config.to_json = ->(data) { Oj.dump(data, mode: :compat) }
|
335
345
|
|
336
346
|
# Configure adapter to de-serialize JSON.
|
337
|
-
# De-serialization is used only for `#as_json` method.
|
347
|
+
# De-serialization is used only for the `#as_json` method.
|
338
348
|
# It is `JSON.parse` by default.
|
339
|
-
# When Oj gem is loaded then default is `Oj.load(data)`
|
349
|
+
# When the Oj gem is loaded, then the default is `Oj.load(data)`
|
340
350
|
config.from_json = ->(data) { Oj.load(data) }
|
341
351
|
|
342
|
-
# Disable/enable validation of modifiers
|
343
|
-
# By default
|
344
|
-
#
|
352
|
+
# Disable/enable validation of modifiers (`:with, :except, :only`)
|
353
|
+
# By default, this validation is enabled.
|
354
|
+
# After disabling, all requested incorrect attributes will be skipped.
|
345
355
|
config.check_initiate_params = false # default is true, enabled
|
346
356
|
|
347
357
|
# Stores in memory prepared `plans` - list of serialized attributes.
|
348
|
-
# Next time serialization happens with same modifiers (`only, except, with`),
|
358
|
+
# Next time serialization happens with the same modifiers (`only, except, with`),
|
349
359
|
# we will reuse already prepared `plans`.
|
350
360
|
# This defines storage size (count of stored `plans` with different modifiers).
|
351
361
|
config.max_cached_plans_per_serializer_count = 50 # default is 0, disabled
|
@@ -365,13 +375,14 @@ Plugin accepts options:
|
|
365
375
|
- `auto_preload_attributes_with_serializer` - default `false`
|
366
376
|
- `auto_hide_attributes_with_preload` - default `false`
|
367
377
|
|
368
|
-
|
378
|
+
These options are extremely useful if you want to forget about finding preloads
|
379
|
+
manually.
|
369
380
|
|
370
|
-
Preloads can be disabled with `preload: false` attribute option
|
371
|
-
|
372
|
-
`preload: :
|
381
|
+
Preloads can be disabled with the `preload: false` attribute option.
|
382
|
+
Automatically added preloads can be overwritten with the manually specified
|
383
|
+
`preload: :xxx` option.
|
373
384
|
|
374
|
-
|
385
|
+
For some examples, **please read the comments in the code below**
|
375
386
|
|
376
387
|
```ruby
|
377
388
|
class AppSerializer < Serega
|
@@ -402,8 +413,8 @@ class AlbumSerializer < AppSerializer
|
|
402
413
|
attribute :images_count, delegate: { to: :album_stats }
|
403
414
|
end
|
404
415
|
|
405
|
-
# By default preloads are empty, as we specify `auto_hide_attributes_with_preload`
|
406
|
-
# so attributes with preloads will be skipped
|
416
|
+
# By default, preloads are empty, as we specify `auto_hide_attributes_with_preload`
|
417
|
+
# so attributes with preloads will be skipped and nothing will be preloaded
|
407
418
|
UserSerializer.new.preloads
|
408
419
|
# => {}
|
409
420
|
|
@@ -421,11 +432,12 @@ UserSerializer.new(
|
|
421
432
|
|
422
433
|
---
|
423
434
|
|
424
|
-
#### SPECIFIC CASE #1: Serializing same object
|
435
|
+
#### SPECIFIC CASE #1: Serializing the same object in association
|
425
436
|
|
426
|
-
For example you
|
427
|
-
|
428
|
-
You should specify `preload: nil` to preload
|
437
|
+
For example, you show your current user as "user" and use the same user object
|
438
|
+
to serialize "user_stats". `UserStatSerializer` relies on user fields and any
|
439
|
+
other user associations. You should specify `preload: nil` to preload
|
440
|
+
`UserStatSerializer` nested associations to the "user" object.
|
429
441
|
|
430
442
|
```ruby
|
431
443
|
class AppSerializer < Serega
|
@@ -444,11 +456,11 @@ class UserSerializer < AppSerializer
|
|
444
456
|
end
|
445
457
|
```
|
446
458
|
|
447
|
-
#### SPECIFIC CASE #2: Serializing multiple associations as single relation
|
459
|
+
#### SPECIFIC CASE #2: Serializing multiple associations as a single relation
|
448
460
|
|
449
|
-
For example "user" has two relations - "new_profile"
|
450
|
-
profiles have "avatar" association. And you decided to serialize profiles in
|
451
|
-
array. You can specify `preload_path: [[:new_profile], [:old_profile]]` to
|
461
|
+
For example, "user" has two relations - "new_profile" and "old_profile". Also
|
462
|
+
profiles have the "avatar" association. And you decided to serialize profiles in
|
463
|
+
one array. You can specify `preload_path: [[:new_profile], [:old_profile]]` to
|
452
464
|
achieve this:
|
453
465
|
|
454
466
|
```ruby
|
@@ -488,7 +500,7 @@ attribute :image,
|
|
488
500
|
preload_path: [:attachment] # or preload_path: [:attachment, :blob]
|
489
501
|
```
|
490
502
|
|
491
|
-
In this case we don't know if preloads defined in ImageSerializer, should be
|
503
|
+
In this case, we don't know if preloads defined in ImageSerializer, should be
|
492
504
|
preloaded to `attachment` or `blob`, so please specify `preload_path` manually.
|
493
505
|
You can specify `preload_path: nil` if you are sure that there are no preloads
|
494
506
|
inside ImageSerializer.
|
@@ -499,7 +511,7 @@ inside ImageSerializer.
|
|
499
511
|
they should be preloaded manually.
|
500
512
|
|
501
513
|
There are only [activerecord_preloads][activerecord_preloads] plugin that can
|
502
|
-
be used to preload
|
514
|
+
be used to preload these associations automatically.
|
503
515
|
|
504
516
|
### Plugin :activerecord_preloads
|
505
517
|
|
@@ -508,7 +520,7 @@ be used to preload this associations automatically.
|
|
508
520
|
Automatically preloads associations to serialized objects.
|
509
521
|
|
510
522
|
It takes all defined preloads from serialized attributes (including attributes
|
511
|
-
from serialized relations), merges them into single associations hash and then
|
523
|
+
from serialized relations), merges them into a single associations hash, and then
|
512
524
|
uses ActiveRecord::Associations::Preloader to preload associations to objects.
|
513
525
|
|
514
526
|
```ruby
|
@@ -539,118 +551,166 @@ UserSerializer.to_h(user)
|
|
539
551
|
|
540
552
|
### Plugin :batch
|
541
553
|
|
542
|
-
|
543
|
-
|
544
|
-
It can be used to find value for attributes in optimal way:
|
554
|
+
Helps to omit N+1.
|
545
555
|
|
546
|
-
|
547
|
-
|
548
|
-
- make any heavy calculations for multiple objects only once
|
556
|
+
User must specify how attribute values are loaded -
|
557
|
+
`attribute :foo, batch: {loader: SomeLoader, id_method: :id}`.
|
549
558
|
|
550
|
-
|
559
|
+
The result must be returned as Hash, where each key is one of the provided IDs.
|
551
560
|
|
552
561
|
```ruby
|
553
|
-
|
562
|
+
class AppSerializer
|
563
|
+
plugin :batch
|
564
|
+
end
|
565
|
+
|
566
|
+
class UserSerializer < AppSerializer
|
567
|
+
attribute :comments_count,
|
568
|
+
batch: { loader: SomeLoader, id_method: :id }
|
569
|
+
|
570
|
+
attribute :company,
|
571
|
+
batch: { loader: SomeLoader, id_method: :id },
|
572
|
+
serializer: CompanySerializer
|
573
|
+
end
|
554
574
|
```
|
555
575
|
|
556
|
-
|
576
|
+
#### Option :loader
|
577
|
+
|
578
|
+
Loaders can be defined as a Proc, a callable value, or a named Symbol
|
579
|
+
Named loaders should be predefined with
|
580
|
+
`config.batch.define(:loader_name) { |ids| ... })`
|
557
581
|
|
558
|
-
|
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.
|
562
|
-
- `default` (optional) - Default value for attribute.
|
563
|
-
By default it is `nil` or `[]` when attribute has option `many: true`
|
564
|
-
(ex: `attribute :tags, many: true, batch: { ... }`).
|
582
|
+
The loader can accept 1 to 3 arguments:
|
565
583
|
|
566
|
-
|
567
|
-
|
584
|
+
1. List of IDs (each ID will be found by using the `:id_method` option)
|
585
|
+
1. Context
|
586
|
+
1. PlanPoint - a special object containing information about current
|
587
|
+
attribute and all children and parent attributes. It can be used to preload
|
588
|
+
required associations to batch values.
|
589
|
+
See [example](examples/batch_loader.rb) how
|
590
|
+
to find required preloads when using the `:preloads` plugin.
|
568
591
|
|
569
|
-
|
592
|
+
```ruby
|
593
|
+
class AppSerializer < Serega
|
594
|
+
plugin :batch, id_method: :id
|
595
|
+
end
|
570
596
|
|
571
|
-
|
572
|
-
|
597
|
+
class UserSerializer < Serega
|
598
|
+
# Define loader as a callable object
|
599
|
+
attribute :comments_count,
|
600
|
+
batch: { loader: CountLoader }
|
573
601
|
|
574
|
-
|
602
|
+
# Define loader as a Proc
|
603
|
+
attribute :comments_count,
|
604
|
+
batch: { loader: proc { |ids| CountLoader.call(ids) } }
|
575
605
|
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
606
|
+
# Define loader as a Symbol
|
607
|
+
config.batch.define(:comments_count_loader) { |ids| CountLoader.call(ids }
|
608
|
+
attribute :comments_count, batch: { loader: :comments_count_loader }
|
609
|
+
end
|
580
610
|
|
581
|
-
|
582
|
-
|
611
|
+
class CountLoader
|
612
|
+
def self.call(user_ids)
|
613
|
+
Comment.where(user_id: user_ids).group(:user_id).count
|
614
|
+
end
|
615
|
+
end
|
583
616
|
```
|
584
617
|
|
585
|
-
|
618
|
+
#### Option :id_method
|
619
|
+
|
620
|
+
The `:batch` plugin can be added with the global `:id_method` option. It can be
|
621
|
+
a Symbol, Proc or any callable value that can accept the current object and
|
622
|
+
context.
|
586
623
|
|
587
624
|
```ruby
|
588
|
-
class
|
589
|
-
plugin :batch,
|
625
|
+
class SomeSerializer
|
626
|
+
plugin :batch, id_method: :id
|
590
627
|
end
|
591
628
|
|
592
629
|
class UserSerializer < AppSerializer
|
593
|
-
|
594
|
-
|
630
|
+
attribute :comments_count,
|
631
|
+
batch: { loader: CommentsCountBatchLoader } # no :id_method here anymore
|
632
|
+
|
633
|
+
attribute :company,
|
634
|
+
batch: { loader: UserCompanyBatchLoader }, # no :id_method here anymore
|
635
|
+
serializer: CompanySerializer
|
595
636
|
end
|
637
|
+
|
596
638
|
```
|
597
639
|
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
when you serialize `User -> Album -> Song` and Song has `:batch` attribute, then
|
602
|
-
`:batch` plugin must be added to the User serializer also. \
|
603
|
-
Best way would be to create one parent `AppSerializer < Serega` for all your
|
604
|
-
serializers and add `:batch` plugin only to this parent `AppSerializer`
|
640
|
+
However, the global `id_method` option can be overwritten via
|
641
|
+
`config.batch.id_method=` method or in specific attributes with the `id_method`
|
642
|
+
option.
|
605
643
|
|
606
644
|
```ruby
|
607
|
-
class
|
608
|
-
plugin :batch,
|
645
|
+
class SomeSerializer
|
646
|
+
plugin :batch, id_method: :id # global id_method is `:id`
|
609
647
|
end
|
610
648
|
|
611
|
-
class
|
612
|
-
|
613
|
-
|
614
|
-
loader: CommentsCountBatchLoader, # callable(keys, context, plan_point)
|
615
|
-
key: :id, # can be skipped (as :id value is same as configured :default_key)
|
616
|
-
default: 0
|
617
|
-
}
|
649
|
+
class UserSerializer < AppSerializer
|
650
|
+
# :user_id will be used as default `id_method` for all batch attributes
|
651
|
+
config.batch.id_method = :user_id
|
618
652
|
|
619
|
-
#
|
620
|
-
# `config.batch.define(:posts_comments_counter) { ... }`
|
621
|
-
#
|
622
|
-
# Loader will receive array of ids, as `default_key: :id` plugin option was specified.
|
623
|
-
# Default value for not found counters is nil, as `:default` option not defined
|
653
|
+
# id_method is :user_id
|
624
654
|
attribute :comments_count,
|
625
|
-
batch: { loader:
|
655
|
+
batch: { loader: CommentsCountBatchLoader }
|
626
656
|
|
627
|
-
# Define batch loader with serializer
|
628
|
-
attribute :comments,
|
629
|
-
serializer: CommentSerializer,
|
630
|
-
batch: {loader: :posts_comments, default: []}
|
631
657
|
|
632
|
-
#
|
633
|
-
|
634
|
-
|
635
|
-
end
|
658
|
+
# id_method is :user_id
|
659
|
+
attribute :company,
|
660
|
+
batch: { loader: UserCompanyBatchLoader }, serializer: CompanySerializer
|
636
661
|
|
637
|
-
#
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
662
|
+
# id_method is :uuid
|
663
|
+
attribute :points_amount,
|
664
|
+
batch: { loader: PointsBatchLoader, id_method: :uuid }
|
665
|
+
end
|
666
|
+
```
|
667
|
+
|
668
|
+
#### Default value
|
669
|
+
|
670
|
+
The default value for attributes without found value can be specified via
|
671
|
+
`:default` option. By default, attributes without found value will be
|
672
|
+
serialized as a `nil` value. Attributes marked as `many: true` will be
|
673
|
+
serialized as empty array `[]` values.
|
674
|
+
|
675
|
+
```ruby
|
676
|
+
class UserSerializer < AppSerializer
|
677
|
+
# Missing values become empty arrays, as the `many: true` option is specified
|
678
|
+
attribute :companies,
|
679
|
+
batch: {loader: proc {}},
|
680
|
+
serializer: CompanySerializer,
|
681
|
+
many: true
|
682
|
+
|
683
|
+
# Missing values become `0` as specified directly
|
684
|
+
attribute :points_amount, batch: { loader: proc {} }, default: 0
|
651
685
|
end
|
652
686
|
```
|
653
687
|
|
688
|
+
Batch attributes can be marked as hidden by default if the plugin is enabled
|
689
|
+
with the `auto_hide` option. The `auto_hide` option can be changed with
|
690
|
+
the `config.batch.auto_hide=` method.
|
691
|
+
|
692
|
+
Look at [select serialized fields](#selecting-fields) for more information
|
693
|
+
about hiding/showing attributes.
|
694
|
+
|
695
|
+
```ruby
|
696
|
+
class AppSerializer
|
697
|
+
plugin :batch, auto_hide: true
|
698
|
+
end
|
699
|
+
|
700
|
+
class UserSerializer < AppSerializer
|
701
|
+
config.batch.auto_hide = false
|
702
|
+
end
|
703
|
+
```
|
704
|
+
|
705
|
+
---
|
706
|
+
⚠️ ATTENTION: The `:batch` plugin must be added to all serializers that have
|
707
|
+
`:batch` attributes inside nested serializers. For example, when you serialize
|
708
|
+
the `User -> Album -> Song` and the Song has a `batch` attribute, then
|
709
|
+
the `:batch` plugin must be added to the User serializer.
|
710
|
+
|
711
|
+
The best way would be to create one parent `AppSerializer < Serega` serializer
|
712
|
+
and add the `:batch` plugin once to this parent serializer.
|
713
|
+
|
654
714
|
### Plugin :root
|
655
715
|
|
656
716
|
Allows to add root key to your serialized data
|
@@ -658,8 +718,8 @@ Allows to add root key to your serialized data
|
|
658
718
|
Accepts options:
|
659
719
|
|
660
720
|
- :root - specifies root for all responses
|
661
|
-
- :root_one - specifies root for single object serialization only
|
662
|
-
- :root_many - specifies root for multiple objects serialization only
|
721
|
+
- :root_one - specifies the root key for single object serialization only
|
722
|
+
- :root_many - specifies the root key for multiple objects serialization only
|
663
723
|
|
664
724
|
Adds additional config options:
|
665
725
|
|
@@ -668,13 +728,26 @@ Adds additional config options:
|
|
668
728
|
- config.root.one=
|
669
729
|
- config.root_many=
|
670
730
|
|
671
|
-
|
731
|
+
The default root is `:data`.
|
732
|
+
|
733
|
+
The root key can be changed per serialization.
|
672
734
|
|
673
|
-
|
735
|
+
```ruby
|
736
|
+
# @example Change root per serialization:
|
674
737
|
|
675
|
-
|
676
|
-
|
677
|
-
|
738
|
+
class UserSerializer < Serega
|
739
|
+
plugin :root
|
740
|
+
end
|
741
|
+
|
742
|
+
UserSerializer.to_h(nil) # => {:data=>nil}
|
743
|
+
UserSerializer.to_h(nil, root: :user) # => {:user=>nil}
|
744
|
+
UserSerializer.to_h(nil, root: nil) # => nil
|
745
|
+
```
|
746
|
+
|
747
|
+
The root key can be removed for all responses by providing the `root: nil`
|
748
|
+
plugin option.
|
749
|
+
|
750
|
+
In this case, no root key will be added. But it still can be added manually.
|
678
751
|
|
679
752
|
```ruby
|
680
753
|
#@example Define :root plugin with different options
|
@@ -692,40 +765,29 @@ serialization
|
|
692
765
|
end
|
693
766
|
|
694
767
|
class UserSerializer < Serega
|
695
|
-
plugin :root, root: nil # no root by default
|
768
|
+
plugin :root, root: nil # no root key by default
|
696
769
|
end
|
697
770
|
```
|
698
771
|
|
699
|
-
```ruby
|
700
|
-
# @example Change root per serialization:
|
701
|
-
|
702
|
-
class UserSerializer < Serega
|
703
|
-
plugin :root
|
704
|
-
end
|
705
|
-
|
706
|
-
UserSerializer.to_h(nil) # => {:data=>nil}
|
707
|
-
UserSerializer.to_h(nil, root: :user) # => {:user=>nil}
|
708
|
-
UserSerializer.to_h(nil, root: nil) # => nil
|
709
|
-
```
|
710
|
-
|
711
772
|
### Plugin :metadata
|
712
773
|
|
713
774
|
Depends on: [`:root`][root] plugin, that must be loaded first
|
714
775
|
|
715
776
|
Adds ability to describe metadata and adds it to serialized response
|
716
777
|
|
717
|
-
|
778
|
+
Adds class-level `.meta_attribute` method. It accepts:
|
718
779
|
|
719
780
|
- `*path` [Array of Symbols] - nested hash keys.
|
720
781
|
- `**options` [Hash]
|
721
782
|
|
722
783
|
- `:const` - describes metadata value (if it is constant)
|
723
784
|
- `:value` - describes metadata value as any `#callable` instance
|
724
|
-
- `:hide_nil` - does not show metadata key if value is nil
|
725
|
-
|
726
|
-
|
785
|
+
- `:hide_nil` - does not show the metadata key if the value is nil.
|
786
|
+
It is `false` by default
|
787
|
+
- `:hide_empty` - does not show the metadata key if the value is nil or empty.
|
788
|
+
It is `false` by default.
|
727
789
|
|
728
|
-
- `&block` [Proc] - describes value for current meta attribute
|
790
|
+
- `&block` [Proc] - describes value for the current meta attribute
|
729
791
|
|
730
792
|
```ruby
|
731
793
|
class AppSerializer < Serega
|
@@ -755,11 +817,11 @@ Depends on: [`:root`][root] plugin, that must be loaded first
|
|
755
817
|
|
756
818
|
Allows to provide metadata and attach it to serialized response.
|
757
819
|
|
758
|
-
Accepts option `:context_metadata_key` with name of
|
759
|
-
|
820
|
+
Accepts option `:context_metadata_key` with the name of the root metadata keyword.
|
821
|
+
By default, it has the `:meta` value.
|
760
822
|
|
761
|
-
|
762
|
-
`config.context_metadata.key=(value)
|
823
|
+
The key can be changed in children serializers using this method:
|
824
|
+
`config.context_metadata.key=(value)`.
|
763
825
|
|
764
826
|
```ruby
|
765
827
|
class UserSerializer < Serega
|
@@ -777,11 +839,11 @@ UserSerializer.to_h(nil, meta: { version: '1.0.1' })
|
|
777
839
|
|
778
840
|
### Plugin :formatters
|
779
841
|
|
780
|
-
Allows to define `formatters` and apply them
|
842
|
+
Allows to define `formatters` and apply them to attribute values.
|
781
843
|
|
782
844
|
Config option `config.formatters.add` can be used to add formatters.
|
783
845
|
|
784
|
-
Attribute option `:format` can be used with name of formatter or with
|
846
|
+
Attribute option `:format` can be used with the name of formatter or with
|
785
847
|
callable instance.
|
786
848
|
|
787
849
|
Formatters can accept up to 2 parameters (formatted object, context)
|
@@ -797,7 +859,7 @@ class AppSerializer < Serega
|
|
797
859
|
end
|
798
860
|
|
799
861
|
class UserSerializer < Serega
|
800
|
-
# Additionally we can add formatters via config in subclasses
|
862
|
+
# Additionally, we can add formatters via config in subclasses
|
801
863
|
config.formatters.add(
|
802
864
|
iso8601: ->(value) { time.iso8601.round(6) },
|
803
865
|
on_off: ->(value) { value ? 'ON' : 'OFF' },
|
@@ -818,7 +880,7 @@ end
|
|
818
880
|
|
819
881
|
### Plugin :presenter
|
820
882
|
|
821
|
-
Helps to write
|
883
|
+
Helps to write clean code by using a Presenter class.
|
822
884
|
|
823
885
|
```ruby
|
824
886
|
class UserSerializer < Serega
|
@@ -844,9 +906,9 @@ end
|
|
844
906
|
Allows to specify modifiers as strings.
|
845
907
|
|
846
908
|
Serialized attributes must be split with `,` and nested attributes must be
|
847
|
-
defined inside brackets `(
|
909
|
+
defined inside brackets `()`.
|
848
910
|
|
849
|
-
Modifiers can still be provided old way using nested hashes or arrays.
|
911
|
+
Modifiers can still be provided the old way using nested hashes or arrays.
|
850
912
|
|
851
913
|
```ruby
|
852
914
|
PostSerializer.plugin :string_modifiers
|
@@ -854,25 +916,24 @@ PostSerializer.new(only: "id,user(id,username)").to_h(post)
|
|
854
916
|
PostSerializer.new(except: "user(username,email)").to_h(post)
|
855
917
|
PostSerializer.new(with: "user(email)").to_h(post)
|
856
918
|
|
857
|
-
# Modifiers can still be provided old way using nested hashes or arrays.
|
919
|
+
# Modifiers can still be provided the old way using nested hashes or arrays.
|
858
920
|
PostSerializer.new(with: {user: %i[email, username]}).to_h(post)
|
859
921
|
```
|
860
922
|
|
861
923
|
### Plugin :if
|
862
924
|
|
863
|
-
Plugin adds `:if
|
864
|
-
attributes so we can remove attributes from response in various ways.
|
925
|
+
Plugin adds `:if, :unless, :if_value, :unless_value` options to
|
926
|
+
attributes so we can remove attributes from the response in various ways.
|
865
927
|
|
866
928
|
Use `:if` and `:unless` when you want to hide attributes before finding
|
867
929
|
attribute value, and use `:if_value` and `:unless_value` to hide attributes
|
868
|
-
after
|
930
|
+
after getting the final value.
|
869
931
|
|
870
932
|
Options `:if` and `:unless` accept currently serialized object and context as
|
871
933
|
parameters. Options `:if_value` and `:unless_value` accept already found
|
872
934
|
serialized value and context as parameters.
|
873
935
|
|
874
|
-
Options `:if_value` and `:unless_value` cannot be used with
|
875
|
-
as serialized objects have no "serialized value".
|
936
|
+
Options `:if_value` and `:unless_value` cannot be used with the `:serializer` option.
|
876
937
|
Use `:if` and `:unless` in this case.
|
877
938
|
|
878
939
|
See also a `:hide` option that is available without any plugins to hide
|
@@ -905,27 +966,27 @@ Look at [select serialized fields](#selecting-fields) for `:hide` usage examples
|
|
905
966
|
|
906
967
|
### Plugin :camel_case
|
907
968
|
|
908
|
-
By default when we add attribute like `attribute :first_name`
|
969
|
+
By default, when we add an attribute like `attribute :first_name` it means:
|
909
970
|
|
910
|
-
- adding a `:first_name` key to
|
971
|
+
- adding a `:first_name` key to the resulting hash
|
911
972
|
- adding a `#first_name` method call result as value
|
912
973
|
|
913
|
-
But
|
914
|
-
By default this can be achieved by specifying attribute name and method directly
|
974
|
+
But it's often desired to respond with *camelCased* keys.
|
975
|
+
By default, this can be achieved by specifying the attribute name and method directly
|
915
976
|
for each attribute: `attribute :firstName, method: first_name`
|
916
977
|
|
917
978
|
This plugin transforms all attribute names automatically.
|
918
|
-
We use simple regular expression to replace `_x`
|
919
|
-
We make this transformation only once when attribute is defined.
|
979
|
+
We use a simple regular expression to replace `_x` with `X` for the whole string.
|
980
|
+
We make this transformation only once when the attribute is defined.
|
920
981
|
|
921
|
-
You can provide
|
982
|
+
You can provide custom transformation when adding the plugin,
|
922
983
|
for example `plugin :camel_case, transform: ->(name) { name.camelize }`
|
923
984
|
|
924
985
|
For any attribute camelCase-behavior can be skipped when
|
925
|
-
`camel_case: false` attribute option provided.
|
986
|
+
the `camel_case: false` attribute option provided.
|
926
987
|
|
927
|
-
This plugin transforms only attribute keys,
|
928
|
-
|
988
|
+
This plugin transforms only attribute keys, without affecting the `root`,
|
989
|
+
`metadata` and `context_metadata` plugins keys.
|
929
990
|
|
930
991
|
If you wish to [select serialized fields](#selecting-fields), you should
|
931
992
|
provide them camelCased.
|
@@ -953,7 +1014,7 @@ UserSerializer.new(only: %i[firstName lastName]).to_h(user)
|
|
953
1014
|
|
954
1015
|
### Plugin :depth_limit
|
955
1016
|
|
956
|
-
Helps to secure from malicious queries that
|
1017
|
+
Helps to secure from malicious queries that serialize too much
|
957
1018
|
or from accidental serializing of objects with cyclic relations.
|
958
1019
|
|
959
1020
|
Depth limit is checked when constructing a serialization plan, that is when
|
@@ -961,19 +1022,19 @@ Depth limit is checked when constructing a serialization plan, that is when
|
|
961
1022
|
It can be useful to instantiate serializer before any other business logic
|
962
1023
|
to get possible errors earlier.
|
963
1024
|
|
964
|
-
Any class-level serialization methods also check depth limit as they also
|
1025
|
+
Any class-level serialization methods also check the depth limit as they also
|
965
1026
|
instantiate serializer.
|
966
1027
|
|
967
|
-
When depth limit is exceeded `Serega::DepthLimitError` is raised.
|
968
|
-
Depth limit error details can be found in additional
|
1028
|
+
When the depth limit is exceeded `Serega::DepthLimitError` is raised.
|
1029
|
+
Depth limit error details can be found in the additional
|
969
1030
|
`Serega::DepthLimitError#details` method
|
970
1031
|
|
971
|
-
|
1032
|
+
The limit can be checked or changed with the next config options:
|
972
1033
|
|
973
1034
|
- `config.depth_limit.limit`
|
974
1035
|
- `config.depth_limit.limit=`
|
975
1036
|
|
976
|
-
There
|
1037
|
+
There is no default limit, but it should be set when enabling the plugin.
|
977
1038
|
|
978
1039
|
```ruby
|
979
1040
|
class AppSerializer < Serega
|
@@ -987,11 +1048,11 @@ end
|
|
987
1048
|
|
988
1049
|
### Plugin :explicit_many_option
|
989
1050
|
|
990
|
-
|
991
|
-
(
|
1051
|
+
The plugin requires adding a `:many` option when adding relationships
|
1052
|
+
(attributes with the `:serializer` option).
|
992
1053
|
|
993
|
-
Adding this plugin makes it clearer to find if relationship
|
994
|
-
object
|
1054
|
+
Adding this plugin makes it clearer to find if some relationship is an array or
|
1055
|
+
a single object.
|
995
1056
|
|
996
1057
|
```ruby
|
997
1058
|
class BaseSerializer < Serega
|
@@ -1011,8 +1072,8 @@ object
|
|
1011
1072
|
|
1012
1073
|
## Errors
|
1013
1074
|
|
1014
|
-
- `Serega::SeregaError` is a base error raised by this gem.
|
1015
|
-
- `Serega::AttributeNotExist` error is raised when validating attributes in
|
1075
|
+
- The `Serega::SeregaError` is a base error raised by this gem.
|
1076
|
+
- The `Serega::AttributeNotExist` error is raised when validating attributes in
|
1016
1077
|
`:only, :except, :with` modifiers
|
1017
1078
|
|
1018
1079
|
## Release
|