serega 0.17.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
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 to serialize them to Hash or JSON.
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 code dry
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 proc or callable object to define attribute value
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 :const specifies attribute with specific constant value
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 specify serializer as Class, String or Proc.
96
- # Use String or Proc if you have cross references in serializers.
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
- # Usually it is defined automatically by checking `is_a?(Enumerable)`
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`, `:unless`, `:if_value`, `:unless_value` can be specified
110
- # when enabled `:if` plugin. They hide attribute key and value from response.
111
- # See more usage examples in :if plugin section.
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 this characters as we want to be able to use attributes names in
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
- This check can be disabled this way:
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 you always serialize same attributes it will make sense to save instance
169
- of serializer and reuse this instance, it will be a bit faster (fields will be
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 only needed attributes:
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` modifier
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 provided non-existing attribute, `Serega::AttributeNotExist` error will be
214
- raised. This error can be muted with `check_initiate_params: false` parameter.
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 not existing attribute
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 not existing attribute and disabled validation
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 you can decide to use some context during serialization, like
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
- This is initial config options, other config options can be added by plugins
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. When Oj gem is loaded then default is
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 params `:with`, `:except`, `:only`
343
- # By default it is enabled. After disabling,
344
- # when provided not existed attribute it will be just skipped.
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
- This options are very handy if you want to forget about finding preloads manually.
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 option.
371
- Also automatically added preloads can be overwritten with manually specified
372
- `preload: :another_value`.
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
- Some examples, **please read comments in the code below**
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 so nothing should be preloaded
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 as association
435
+ #### SPECIFIC CASE #1: Serializing the same object in association
425
436
 
426
- For example you decided to show your current user as "user" and "user_stats".
427
- Where stats rely on user fields and some other associations.
428
- You should specify `preload: nil` to preload nested associations, if any, to "user".
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", "old_profile", and also
450
- profiles have "avatar" association. And you decided to serialize profiles in one
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 this associations automatically.
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
- Adds ability to load nested attributes values in batches.
543
-
544
- It can be used to find value for attributes in optimal way:
554
+ Helps to omit N+1.
545
555
 
546
- - load associations for multiple objects
547
- - load counters for multiple objects
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
- After including plugin, attributes gain new `:batch` option:
559
+ The result must be returned as Hash, where each key is one of the provided IDs.
551
560
 
552
561
  ```ruby
553
- attribute :name, batch: { loader: :name_loader, key: :id, default: nil }
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
- `:batch` option must be a hash with this keys:
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
- - `loader` (required) [Symbol, Proc, callable] - Defines how to fetch values for
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
- If `:loader` was defined using name (as Symbol) then batch loader must be
567
- defined using `config.batch.define(:loader_name) { ... }` method.
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
- Result of this `:loader` callable must be a **Hash** where:
592
+ ```ruby
593
+ class AppSerializer < Serega
594
+ plugin :batch, id_method: :id
595
+ end
570
596
 
571
- - keys - provided keys
572
- - values - values for according keys
597
+ class UserSerializer < Serega
598
+ # Define loader as a callable object
599
+ attribute :comments_count,
600
+ batch: { loader: CountLoader }
573
601
 
574
- `Batch` plugin can be defined with two specific attributes:
602
+ # Define loader as a Proc
603
+ attribute :comments_count,
604
+ batch: { loader: proc { |ids| CountLoader.call(ids) } }
575
605
 
576
- - `auto_hide: true` - Marks attributes with defined :batch as hidden, so it
577
- will not be serialized by default
578
- - `default_key: :id` - Set default object key (in this case :id) that will be
579
- used for all attributes with :batch option specified.
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
- ```ruby
582
- plugin :batch, auto_hide: true, default_key: :id
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
- Options `auto_hide` and `default_key` can be overwritten in nested serializers.
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 AppSerializer
589
- plugin :batch, auto_hide: true, default_key: :id
625
+ class SomeSerializer
626
+ plugin :batch, id_method: :id
590
627
  end
591
628
 
592
629
  class UserSerializer < AppSerializer
593
- config.batch.auto_hide = false
594
- config.batch.default_key = :user_id
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
- ⚠️ ATTENTION: `Batch` plugin must be added to serializers which have no
600
- `:batch` attributes, but have nested serializers, that have some. For example
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 AppSerializer < Serega
608
- plugin :batch, auto_hide: true, default_key: :id
645
+ class SomeSerializer
646
+ plugin :batch, id_method: :id # global id_method is `:id`
609
647
  end
610
648
 
611
- class PostSerializer < AppSerializer
612
- attribute :comments_count,
613
- batch: {
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
- # Define batch loader via Symbol, later we should define this loader via
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: :posts_comments_counter }
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
- # Resulted block must return hash like { key => value(s) }
633
- config.batch.define(:posts_comments_counter) do |keys|
634
- Comment.group(:post_id).where(post_id: keys).count
635
- end
658
+ # id_method is :user_id
659
+ attribute :company,
660
+ batch: { loader: UserCompanyBatchLoader }, serializer: CompanySerializer
636
661
 
637
- # We can return objects that will be automatically serialized if attribute
638
- # defined with :serializer
639
- # Parameter `context` can be used when loading batch
640
- # Parameter `point` can be used to find nested attributes to serialize
641
- config.batch.define(:posts_comments) do |keys, context, point|
642
- # point.child_plan - if you need to manually check all nested attributes
643
- # point.preloads - nested preloads (works with :preloads plugin only)
644
-
645
- Comment
646
- .preload(point.preloads) # Skip if :activerecord_preloads plugin used
647
- .where(post_id: keys)
648
- .where(is_spam: false)
649
- .group_by(&:post_id)
650
- end
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
- Default root is `:data`.
731
+ The default root is `:data`.
732
+
733
+ The root key can be changed per serialization.
672
734
 
673
- Root also can be changed per serialization.
735
+ ```ruby
736
+ # @example Change root per serialization:
674
737
 
675
- Also root can be removed for all responses by providing `root: nil`.
676
- In this case no root will be added to response, but you still can to add it per
677
- serialization
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
- Added class-level method `:meta_attribute`, to define metadata, it accepts:
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, `false` by default
725
- - `:hide_empty`, does not show metadata key if value is nil or empty,
726
- `false` by default
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 keyword that must be used to
759
- provide metadata. By default it is `:meta`
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
- Key can be changed in children serializers using config
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 on attribute values.
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 clear code by adding attribute names as methods to Presenter
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`, `:unless`, `:if_value`, `:unless_value` options to
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 finding final value.
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 :serializer option,
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` this means:
969
+ By default, when we add an attribute like `attribute :first_name` it means:
909
970
 
910
- - adding a `:first_name` key to resulted hash
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 its often desired to response with *camelCased* keys.
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` to `X` for the whole string.
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 your own callable transformation when defining plugin,
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
- without affecting `root`, `metadata`, `context_metadata` plugins keys.
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 require to serialize too much
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
- Limit can be checked or changed with next config options:
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 are no default limit, but it should be set when enabling plugin.
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
- Plugin requires to add :many option when adding relationships
991
- (relationships are attributes with :serializer option specified)
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 returns array or single
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