serega 0.39.0 → 0.40.1
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/LICENSE.txt +21 -0
- data/README.md +164 -109
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +2 -0
- data/lib/serega/attribute_normalizer.rb +61 -3
- data/lib/serega/attribute_value_resolvers/batch.rb +3 -0
- data/lib/serega/attribute_value_resolvers/const.rb +3 -0
- data/lib/serega/attribute_value_resolvers/delegate.rb +4 -0
- data/lib/serega/attribute_value_resolvers/hash_access.rb +122 -0
- data/lib/serega/attribute_value_resolvers/keyword.rb +3 -0
- data/lib/serega/config.rb +60 -1
- data/lib/serega/data_builder.rb +2 -0
- data/lib/serega/engine/level.rb +2 -0
- data/lib/serega/engine/level_queue.rb +2 -0
- data/lib/serega/engine/loader.rb +3 -0
- data/lib/serega/helpers/serializer_class_helper.rb +2 -0
- data/lib/serega/object_serializer.rb +2 -0
- data/lib/serega/plan.rb +3 -0
- data/lib/serega/plan_point.rb +2 -0
- data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +22 -16
- data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +6 -0
- data/lib/serega/plugins/camel_case/camel_case.rb +13 -13
- data/lib/serega/plugins/context_metadata/context_metadata.rb +8 -1
- data/lib/serega/plugins/depth_limit/depth_limit.rb +15 -12
- data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +12 -10
- data/lib/serega/plugins/explicit_many_option/validations/check_opt_many.rb +1 -0
- data/lib/serega/plugins/formatters/formatters.rb +18 -10
- data/lib/serega/plugins/if/if.rb +29 -32
- data/lib/serega/plugins/if/validations/check_opt_if.rb +1 -0
- data/lib/serega/plugins/if/validations/check_opt_if_value.rb +1 -0
- data/lib/serega/plugins/if/validations/check_opt_unless.rb +1 -0
- data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +1 -0
- data/lib/serega/plugins/metadata/meta_attribute.rb +2 -0
- data/lib/serega/plugins/metadata/metadata.rb +14 -12
- data/lib/serega/plugins/metadata/validations/check_block.rb +1 -0
- data/lib/serega/plugins/metadata/validations/check_opt_const.rb +1 -0
- data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +1 -0
- data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +1 -0
- data/lib/serega/plugins/metadata/validations/check_opt_value.rb +1 -0
- data/lib/serega/plugins/metadata/validations/check_opts.rb +1 -0
- data/lib/serega/plugins/metadata/validations/check_path.rb +1 -0
- data/lib/serega/plugins/presenter/presenter.rb +14 -7
- data/lib/serega/plugins/root/root.rb +10 -6
- data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +1 -0
- data/lib/serega/plugins/string_modifiers/string_modifiers.rb +21 -0
- data/lib/serega/plugins.rb +2 -0
- data/lib/serega/utils/collection_detector.rb +2 -0
- data/lib/serega/utils/enum_deep_dup.rb +2 -0
- data/lib/serega/utils/enum_deep_freeze.rb +2 -0
- data/lib/serega/utils/method_signature.rb +2 -0
- data/lib/serega/utils/serialized_attribute_error.rb +2 -0
- data/lib/serega/utils/symbol_name.rb +2 -0
- data/lib/serega/utils/to_hash.rb +2 -0
- data/lib/serega/validations/attribute/check_block.rb +3 -0
- data/lib/serega/validations/attribute/check_name.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_base_serializer.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_batch.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_const.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_delegate.rb +45 -1
- data/lib/serega/validations/attribute/check_opt_hash_access.rb +81 -0
- data/lib/serega/validations/attribute/check_opt_hide.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_many.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_method.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_preload.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_serializer.rb +3 -0
- data/lib/serega/validations/attribute/check_opt_value.rb +3 -0
- data/lib/serega/validations/check_attribute_params.rb +4 -0
- data/lib/serega/validations/check_batch_loader_params.rb +3 -0
- data/lib/serega/validations/check_initiate_params.rb +3 -0
- data/lib/serega/validations/check_serialize_params.rb +3 -0
- data/lib/serega/validations/initiate/check_modifiers.rb +3 -0
- data/lib/serega/validations/utils/check_allowed_keys.rb +3 -0
- data/lib/serega/validations/utils/check_opt_is_bool.rb +3 -0
- data/lib/serega/validations/utils/check_opt_is_hash.rb +3 -0
- data/lib/serega/validations/utils/check_opt_is_string_or_symbol.rb +3 -0
- data/lib/serega.rb +8 -0
- metadata +15 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: edecae0c35540be687e394092e3b0a158fa1c2aeff4710ff5ce8e17effba1eb2
|
|
4
|
+
data.tar.gz: 7e63d238aad501f31be388824bb6b594e9f7988e17c49e4fc478d9353993c63b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 506607362308145ffcc0a34474d76d8b0a0edb7fbb570deb1fb973c71c396aab65a1b7b408069d31c8a5e15f8c8146316f835194c53e4a68315fe62eb4992421
|
|
7
|
+
data.tar.gz: 6d5b5ebe921a36b197b4829721af89eb92f5e47522101d126cffc8c72e30ad2679a6f9084df2685cf2250d772c5b0974b76861260b2eb19c6f53660fba16f5d4
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Andrey Glushkov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
[![GitHub Actions][build-badge]][build]
|
|
5
5
|
|
|
6
6
|
The Serega Ruby Serializer provides easy and powerful DSL to describe your
|
|
7
|
-
objects and serialize them to Hash
|
|
7
|
+
objects and serialize them to Hash, ready for further conversion to JSON or
|
|
8
|
+
other formats.
|
|
8
9
|
|
|
9
10
|
---
|
|
10
11
|
|
|
@@ -26,6 +27,7 @@ It has some great features:
|
|
|
26
27
|
keeping the code dry
|
|
27
28
|
- Conditional attributes - ([if][if] plugin)
|
|
28
29
|
- Auto camelCase keys - [camel_case][camel_case] plugin
|
|
30
|
+
- Serializing Hash records - [hash_access][hash_access] attribute option
|
|
29
31
|
|
|
30
32
|
## Installation
|
|
31
33
|
|
|
@@ -74,8 +76,9 @@ class UserSerializer < Serega
|
|
|
74
76
|
# Regular attribute
|
|
75
77
|
attribute :first_name
|
|
76
78
|
|
|
77
|
-
# Option :method specifies the
|
|
78
|
-
# serialized object
|
|
79
|
+
# Option :method specifies the name used to read the attribute's value —
|
|
80
|
+
# a method called on the serialized object by default, or a Hash key when
|
|
81
|
+
# combined with :hash_access
|
|
79
82
|
attribute :first_name, method: :old_first_name
|
|
80
83
|
|
|
81
84
|
# Attribute blocks below require a base serializer for nested serializers.
|
|
@@ -494,6 +497,12 @@ class AppSerializer < Serega
|
|
|
494
497
|
# proc { |object, batches:| batches[:counter][object.id] }
|
|
495
498
|
config.batch_id_option = :id
|
|
496
499
|
|
|
500
|
+
# Defaults for the `hash_access:` attribute option — `default_mode` is
|
|
501
|
+
# what `hash_access: true` (and a Hash form omitting :mode) resolves to.
|
|
502
|
+
# See "Serializing Hash records".
|
|
503
|
+
config.hash_access.default_mode = :symbol # the default
|
|
504
|
+
config.hash_access.default_allow_missing_key = false # the default
|
|
505
|
+
|
|
497
506
|
# Parent class for nested serializers defined with attribute blocks.
|
|
498
507
|
# Usually a settings-only serializer, e.g. `config.base_serializer = self`
|
|
499
508
|
# in an application base serializer class. There is no default — an
|
|
@@ -664,6 +673,69 @@ end
|
|
|
664
673
|
|
|
665
674
|
---
|
|
666
675
|
|
|
676
|
+
## Serializing Hash records
|
|
677
|
+
|
|
678
|
+
(objects should implement `#[]`, `#fetch`, and `#key?` - Hash already has them)
|
|
679
|
+
|
|
680
|
+
The `hash_access: <mode>` attribute option makes an attribute read its value from
|
|
681
|
+
Hash keys instead of calling a method.
|
|
682
|
+
|
|
683
|
+
Allowed `<mode>`:
|
|
684
|
+
|
|
685
|
+
- `:symbol` reads record[:name]
|
|
686
|
+
- `:string` reads record["name"]
|
|
687
|
+
- `true` shorthand for `config.hash_access.default_mode` (`:symbol` by default)
|
|
688
|
+
|
|
689
|
+
By default missing keys will raise `KeyError`.
|
|
690
|
+
|
|
691
|
+
`config.hash_access.default_allow_missing_key = true` will change `KeyError`
|
|
692
|
+
to return `nil` value instead. `config.hash_access.default_mode = <mode>`
|
|
693
|
+
changes what `true` (and a Hash form omitting `:mode`) resolves to.
|
|
694
|
+
|
|
695
|
+
```ruby
|
|
696
|
+
class UserSerializer < Serega
|
|
697
|
+
config.hash_access.default_mode = :symbol # the default
|
|
698
|
+
config.hash_access.default_allow_missing_key = true # false by default
|
|
699
|
+
|
|
700
|
+
attribute :name, hash_access: true # reads record[:name] (config.hash_access.default_mode)
|
|
701
|
+
attribute :name, hash_access: :symbol # reads record[:name]
|
|
702
|
+
attribute :name, hash_access: :string # reads record["name"]
|
|
703
|
+
|
|
704
|
+
# Use long form if you need to override `allow_missing_key`
|
|
705
|
+
attribute :name, hash_access: { allow_missing_key: false }
|
|
706
|
+
attribute :name, hash_access: { mode: :symbol, allow_missing_key: false }
|
|
707
|
+
attribute :name, hash_access: { mode: :string, allow_missing_key: false }
|
|
708
|
+
end
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
Delegated attributes configure hash access **per step**:
|
|
712
|
+
|
|
713
|
+
- `to_hash_access` configures reading `<mode>` for intermediate object
|
|
714
|
+
- `hash_access` configures reading `<mode>` for final key
|
|
715
|
+
|
|
716
|
+
```ruby
|
|
717
|
+
# reads `record.address.city` (no hash access)
|
|
718
|
+
attribute :city, delegate: { to: :address } # record.address.city
|
|
719
|
+
|
|
720
|
+
# reads record.address[:city]
|
|
721
|
+
attribute :city, delegate: { to: :address, hash_access: :symbol }
|
|
722
|
+
|
|
723
|
+
# reads record[:address].city
|
|
724
|
+
attribute :city, delegate: { to: :address, to_hash_access: :symbol }
|
|
725
|
+
|
|
726
|
+
# reads record[:address][:city]
|
|
727
|
+
attribute :city, delegate: { to: :address, to_hash_access: :symbol, hash_access: :symbol }
|
|
728
|
+
|
|
729
|
+
# reads record["address"] and if exists returns record["address"][:city]
|
|
730
|
+
attribute :city,
|
|
731
|
+
delegate: {
|
|
732
|
+
to: :address,
|
|
733
|
+
allow_nil: true,
|
|
734
|
+
to_hash_access: { mode: :string, allow_missing_key: true },
|
|
735
|
+
hash_access: { mode: :symbol, allow_missing_key: false }
|
|
736
|
+
}
|
|
737
|
+
```
|
|
738
|
+
|
|
667
739
|
## Plugins
|
|
668
740
|
|
|
669
741
|
### Plugin :activerecord_preloads
|
|
@@ -678,31 +750,38 @@ handler; to use a different ORM, register your own instead.
|
|
|
678
750
|
```ruby
|
|
679
751
|
class AppSerializer < Serega
|
|
680
752
|
config.auto_preload = true
|
|
681
|
-
config.hide_by_default = false
|
|
682
|
-
|
|
683
753
|
plugin :activerecord_preloads
|
|
684
754
|
end
|
|
685
755
|
|
|
756
|
+
class AlbumSerializer < AppSerializer
|
|
757
|
+
# no preloads
|
|
758
|
+
attribute :title
|
|
759
|
+
|
|
760
|
+
# preloads :downloads, as manually specified
|
|
761
|
+
attribute :downloads_count, preload: :downloads, value: proc { |album| album.downloads.count }
|
|
762
|
+
end
|
|
763
|
+
|
|
686
764
|
class UserSerializer < AppSerializer
|
|
765
|
+
# no preloads
|
|
687
766
|
attribute :username
|
|
767
|
+
|
|
768
|
+
# preloads :user_stats, as auto_preload is enabled for :delegate attributes
|
|
688
769
|
attribute :comments_count, delegate: { to: :user_stats }
|
|
689
|
-
attribute :albums, serializer: AlbumSerializer
|
|
690
|
-
end
|
|
691
770
|
|
|
692
|
-
|
|
693
|
-
attribute :
|
|
694
|
-
attribute :downloads_count, preload: :downloads,
|
|
695
|
-
value: proc { |album| album.downloads.count }
|
|
771
|
+
# preloads :albums, as auto_preload is enabled for :serializer attributes
|
|
772
|
+
attribute :albums, serializer: AlbumSerializer
|
|
696
773
|
end
|
|
697
774
|
|
|
698
|
-
UserSerializer.to_h(
|
|
699
|
-
#
|
|
700
|
-
#
|
|
775
|
+
UserSerializer.to_h(users)
|
|
776
|
+
# 1 query to load :user_stats for all users
|
|
777
|
+
# + 1 query to load :albums for all users
|
|
778
|
+
# + 1 query to load :downloads for all albums
|
|
779
|
+
# = 3 queries total, regardless of how many users/albums are serialized
|
|
701
780
|
```
|
|
702
781
|
|
|
703
782
|
### Plugin :root
|
|
704
783
|
|
|
705
|
-
|
|
784
|
+
Adds a root key to serialized data.
|
|
706
785
|
|
|
707
786
|
Accepts options:
|
|
708
787
|
|
|
@@ -715,56 +794,47 @@ Adds additional config options:
|
|
|
715
794
|
- config.root.one
|
|
716
795
|
- config.root.many
|
|
717
796
|
- config.root.one=
|
|
718
|
-
- config.
|
|
797
|
+
- config.root.many=
|
|
719
798
|
|
|
720
799
|
The default root is `:data`.
|
|
721
800
|
|
|
722
|
-
The root key can be changed per serialization.
|
|
723
|
-
|
|
724
801
|
```ruby
|
|
725
|
-
|
|
802
|
+
class UserSerializer < Serega
|
|
803
|
+
plugin :root # default root is :data
|
|
804
|
+
end
|
|
726
805
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
806
|
+
class UserSerializer < Serega
|
|
807
|
+
plugin :root, root: :users
|
|
808
|
+
end
|
|
730
809
|
|
|
731
|
-
UserSerializer
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
```
|
|
810
|
+
class UserSerializer < Serega
|
|
811
|
+
plugin :root, root_one: :user, root_many: :people
|
|
812
|
+
end
|
|
735
813
|
|
|
736
|
-
|
|
737
|
-
plugin
|
|
814
|
+
class UserSerializer < Serega
|
|
815
|
+
plugin :root, root: nil # no root key by default
|
|
816
|
+
end
|
|
817
|
+
```
|
|
738
818
|
|
|
739
|
-
|
|
819
|
+
The root key can also be changed per serialization, or removed entirely by
|
|
820
|
+
providing `root: nil` (it can still be added back per serialization).
|
|
740
821
|
|
|
741
822
|
```ruby
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
plugin :root # default root is :data
|
|
746
|
-
end
|
|
747
|
-
|
|
748
|
-
class UserSerializer < Serega
|
|
749
|
-
plugin :root, root: :users
|
|
750
|
-
end
|
|
751
|
-
|
|
752
|
-
class UserSerializer < Serega
|
|
753
|
-
plugin :root, root_one: :user, root_many: :people
|
|
754
|
-
end
|
|
823
|
+
class UserSerializer < Serega
|
|
824
|
+
plugin :root
|
|
825
|
+
end
|
|
755
826
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
827
|
+
UserSerializer.to_h(nil) # => {:data=>nil}
|
|
828
|
+
UserSerializer.to_h(nil, root: :user) # => {:user=>nil}
|
|
829
|
+
UserSerializer.to_h(nil, root: nil) # => nil
|
|
759
830
|
```
|
|
760
831
|
|
|
761
832
|
### Plugin :metadata
|
|
762
833
|
|
|
763
834
|
Depends on: [`:root`][root] plugin, that must be loaded first
|
|
764
835
|
|
|
765
|
-
Adds
|
|
766
|
-
|
|
767
|
-
Adds class-level `.meta_attribute` method. It accepts:
|
|
836
|
+
Adds metadata to the serialized response via the class-level `meta_attribute`
|
|
837
|
+
method, which accepts:
|
|
768
838
|
|
|
769
839
|
- `*path` [Array of Symbols] - nested hash keys.
|
|
770
840
|
- `**options` [Hash]
|
|
@@ -804,7 +874,8 @@ AppSerializer.to_h(nil)
|
|
|
804
874
|
|
|
805
875
|
Depends on: [`:root`][root] plugin, that must be loaded first
|
|
806
876
|
|
|
807
|
-
|
|
877
|
+
Adds metadata supplied per serialization call (as opposed to `:metadata`,
|
|
878
|
+
which is defined statically on the serializer).
|
|
808
879
|
|
|
809
880
|
Accepts option `:context_metadata_key` with the name of the root metadata keyword.
|
|
810
881
|
By default, it has the `:meta` value.
|
|
@@ -828,41 +899,39 @@ UserSerializer.to_h(nil, meta: { version: '1.0.1' })
|
|
|
828
899
|
|
|
829
900
|
### Plugin :formatters
|
|
830
901
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
Config option `config.formatters.add` can be used to add formatters.
|
|
902
|
+
Defines named value formatters once and applies them to any attribute.
|
|
834
903
|
|
|
835
|
-
|
|
836
|
-
callable
|
|
904
|
+
Use `config.formatters.add()` to register formatters. The `:format`
|
|
905
|
+
attribute option then accepts a formatter name or a callable directly.
|
|
837
906
|
|
|
838
|
-
Formatters
|
|
907
|
+
Formatters receive up to 2 parameters: the value and the context.
|
|
839
908
|
|
|
840
909
|
```ruby
|
|
841
910
|
class AppSerializer < Serega
|
|
842
911
|
plugin :formatters, formatters: {
|
|
843
|
-
iso8601: ->(value) {
|
|
912
|
+
iso8601: ->(value) { value.iso8601 },
|
|
844
913
|
on_off: ->(value) { value ? 'ON' : 'OFF' },
|
|
845
|
-
money: ->(value
|
|
914
|
+
money: ->(value) { value.round(2) },
|
|
846
915
|
date: DateTypeFormatter # callable
|
|
847
916
|
}
|
|
848
917
|
end
|
|
849
918
|
|
|
850
919
|
class UserSerializer < Serega
|
|
851
|
-
# Additionally
|
|
920
|
+
# Additionally we can add formatters via config in subclasses
|
|
852
921
|
config.formatters.add(
|
|
853
|
-
iso8601: ->(value) {
|
|
922
|
+
iso8601: ->(value) { value.iso8601 },
|
|
854
923
|
on_off: ->(value) { value ? 'ON' : 'OFF' },
|
|
855
924
|
money: ->(value) { value.round(2) }
|
|
856
925
|
)
|
|
857
926
|
|
|
858
927
|
# Using predefined formatter
|
|
859
928
|
attribute :commission, format: :money
|
|
860
|
-
attribute :
|
|
929
|
+
attribute :is_logged_in, format: :on_off
|
|
861
930
|
attribute :created_at, format: :iso8601
|
|
862
931
|
attribute :updated_at, format: :iso8601
|
|
863
932
|
|
|
864
933
|
# Using `callable` formatter
|
|
865
|
-
attribute :score_percent, format:
|
|
934
|
+
attribute :score_percent, format: PercentFormatter # callable class
|
|
866
935
|
attribute :score_percent, format: proc { |percent| "#{percent.round(2)}%" }
|
|
867
936
|
end
|
|
868
937
|
```
|
|
@@ -924,12 +993,11 @@ callables keep receiving the raw objects.
|
|
|
924
993
|
|
|
925
994
|
### Plugin :string_modifiers
|
|
926
995
|
|
|
927
|
-
Allows to
|
|
996
|
+
Allows `:only`, `:except` and `:with` to be given as a single comma-separated
|
|
997
|
+
string, with nested attributes in parentheses. Useful for accepting a field
|
|
998
|
+
list straight from a query parameter.
|
|
928
999
|
|
|
929
|
-
|
|
930
|
-
defined inside brackets `()`.
|
|
931
|
-
|
|
932
|
-
Modifiers can still be provided the old way using nested hashes or arrays.
|
|
1000
|
+
Modifiers can still be provided the old way, as nested hashes or arrays.
|
|
933
1001
|
|
|
934
1002
|
```ruby
|
|
935
1003
|
PostSerializer.plugin :string_modifiers
|
|
@@ -937,25 +1005,19 @@ PostSerializer.new(only: "id,user(id,username)").to_h(post)
|
|
|
937
1005
|
PostSerializer.new(except: "user(username,email)").to_h(post)
|
|
938
1006
|
PostSerializer.new(with: "user(email)").to_h(post)
|
|
939
1007
|
|
|
940
|
-
# Modifiers can still be provided the old way using nested hashes or arrays.
|
|
941
1008
|
PostSerializer.new(with: {user: %i[email, username]}).to_h(post)
|
|
942
1009
|
```
|
|
943
1010
|
|
|
944
1011
|
### Plugin :if
|
|
945
1012
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
Use `:if` and `:unless` when you want to hide attributes before finding
|
|
950
|
-
attribute value, and use `:if_value` and `:unless_value` to hide attributes
|
|
951
|
-
after getting the final value.
|
|
1013
|
+
Adds `:if`, `:unless`, `:if_value`, `:unless_value` attribute options to
|
|
1014
|
+
conditionally remove attributes from the response.
|
|
952
1015
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
Use `:if` and `:unless` in this case.
|
|
1016
|
+
`:if`/`:unless` receive the serialized object and context, and are checked
|
|
1017
|
+
before the attribute value is found. `:if_value`/`:unless_value` receive the
|
|
1018
|
+
already-found value and context, checked after. The latter two cannot be
|
|
1019
|
+
used with the `:serializer` option, since a relationship has no "serialized
|
|
1020
|
+
value" of its own — use `:if`/`:unless` instead.
|
|
959
1021
|
|
|
960
1022
|
See also a `:hide` option that is available without any plugins to hide
|
|
961
1023
|
attribute without conditions.
|
|
@@ -987,24 +1049,18 @@ Look at [select serialized fields](#selecting-fields) for `:hide` usage examples
|
|
|
987
1049
|
|
|
988
1050
|
### Plugin :camel_case
|
|
989
1051
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
- adding a `#first_name` method call result as value
|
|
994
|
-
|
|
995
|
-
But it's often desired to respond with *camelCased* keys.
|
|
996
|
-
By default, this can be achieved by specifying the attribute name and method directly
|
|
997
|
-
for each attribute: `attribute :firstName, method: first_name`
|
|
1052
|
+
Without this plugin, responding with *camelCased* keys means specifying the
|
|
1053
|
+
attribute name and method directly for each attribute:
|
|
1054
|
+
`attribute :firstName, method: first_name`
|
|
998
1055
|
|
|
999
|
-
This plugin
|
|
1000
|
-
|
|
1001
|
-
|
|
1056
|
+
This plugin camelCases every attribute name automatically, replacing `_x`
|
|
1057
|
+
with `X` throughout the string. The transformation runs once, when the
|
|
1058
|
+
attribute is defined, not on every serialization.
|
|
1002
1059
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1060
|
+
Provide a custom transformation when adding the plugin, for example
|
|
1061
|
+
`plugin :camel_case, transform: ->(name) { name.camelize }`
|
|
1005
1062
|
|
|
1006
|
-
|
|
1007
|
-
the `camel_case: false` attribute option provided.
|
|
1063
|
+
Skip camelCase for a single attribute with `camel_case: false`.
|
|
1008
1064
|
|
|
1009
1065
|
This plugin transforms only attribute keys, without affecting the `root`,
|
|
1010
1066
|
`metadata` and `context_metadata` plugins keys.
|
|
@@ -1069,27 +1125,25 @@ end
|
|
|
1069
1125
|
|
|
1070
1126
|
### Plugin :explicit_many_option
|
|
1071
1127
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
Adding this plugin makes it clearer to find if some relationship is an array or
|
|
1077
|
-
a single object.
|
|
1128
|
+
Requires the `:many` option on every relationship attribute (an attribute
|
|
1129
|
+
with the `:serializer` option or a block defining a nested serializer), so
|
|
1130
|
+
it's always explicit whether it returns one object or many.
|
|
1078
1131
|
|
|
1079
1132
|
```ruby
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1133
|
+
class BaseSerializer < Serega
|
|
1134
|
+
plugin :explicit_many_option
|
|
1135
|
+
config.base_serializer = self
|
|
1136
|
+
end
|
|
1137
|
+
|
|
1138
|
+
class PostSerializer < BaseSerializer
|
|
1139
|
+
attribute :text
|
|
1083
1140
|
|
|
1084
|
-
|
|
1141
|
+
attribute :user, many: false do
|
|
1085
1142
|
attribute :name
|
|
1086
1143
|
end
|
|
1087
1144
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
attribute :user, serializer: UserSerializer, many: false
|
|
1091
|
-
attribute :comments, serializer: PostSerializer, many: true
|
|
1092
|
-
end
|
|
1145
|
+
attribute :comments, serializer: PostSerializer, many: true
|
|
1146
|
+
end
|
|
1093
1147
|
```
|
|
1094
1148
|
|
|
1095
1149
|
## Errors
|
|
@@ -1128,6 +1182,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
1128
1182
|
[context_metadata]: #plugin-context_metadata
|
|
1129
1183
|
[depth_limit]: #plugin-depth_limit
|
|
1130
1184
|
[formatters]: #plugin-formatters
|
|
1185
|
+
[hash_access]: #serializing-hash-records
|
|
1131
1186
|
[metadata]: #plugin-metadata
|
|
1132
1187
|
[preloads]: #preloads
|
|
1133
1188
|
[presenter]: #plugin-presenter
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.40.1
|
data/lib/serega/attribute.rb
CHANGED
|
@@ -4,10 +4,12 @@ class Serega
|
|
|
4
4
|
#
|
|
5
5
|
# Stores serialized attribute data
|
|
6
6
|
#
|
|
7
|
+
# @private
|
|
7
8
|
class SeregaAttribute
|
|
8
9
|
#
|
|
9
10
|
# Attribute instance methods
|
|
10
11
|
#
|
|
12
|
+
# @private
|
|
11
13
|
module AttributeInstanceMethods
|
|
12
14
|
# Attribute initial params
|
|
13
15
|
# @return [Hash] Attribute initial params
|
|
@@ -4,10 +4,12 @@ class Serega
|
|
|
4
4
|
#
|
|
5
5
|
# Prepares provided attribute options
|
|
6
6
|
#
|
|
7
|
+
# @private
|
|
7
8
|
class SeregaAttributeNormalizer
|
|
8
9
|
#
|
|
9
10
|
# AttributeNormalizer instance methods
|
|
10
11
|
#
|
|
12
|
+
# @private
|
|
11
13
|
module AttributeNormalizerInstanceMethods
|
|
12
14
|
# Attribute initial params
|
|
13
15
|
# @return [Hash] Attribute initial params
|
|
@@ -254,7 +256,10 @@ class Serega
|
|
|
254
256
|
end
|
|
255
257
|
|
|
256
258
|
def prepare_keyword_block
|
|
257
|
-
|
|
259
|
+
mode, allow_nil = hash_access
|
|
260
|
+
return AttributeValueResolvers::KeywordResolver.get(method_name) unless mode
|
|
261
|
+
|
|
262
|
+
AttributeValueResolvers::HashAccessResolver.get(method_name, mode, allow_nil)
|
|
258
263
|
end
|
|
259
264
|
|
|
260
265
|
def prepare_batch_loader_block
|
|
@@ -291,15 +296,68 @@ class Serega
|
|
|
291
296
|
init_opts.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
|
|
292
297
|
end
|
|
293
298
|
|
|
299
|
+
# `delegate: {to_hash_access: ...}` configures hash access for the
|
|
300
|
+
# intermediate (:to) read, `delegate: {hash_access: ...}` for the
|
|
301
|
+
# final (:method) read. A step without its sub-option keeps a plain
|
|
302
|
+
# method read.
|
|
294
303
|
def prepare_delegate_block
|
|
295
304
|
delegate = init_opts[:delegate]
|
|
296
305
|
return unless delegate
|
|
297
306
|
|
|
298
307
|
key_method_name = delegate[:method] || method_name
|
|
299
308
|
delegate_to = delegate[:to]
|
|
309
|
+
delegate_allow_nil = delegate.fetch(:allow_nil) { config.delegate_default_allow_nil }
|
|
310
|
+
|
|
311
|
+
to_access = delegate[:to_hash_access]
|
|
312
|
+
final_access = delegate[:hash_access]
|
|
313
|
+
unless to_access || final_access
|
|
314
|
+
return AttributeValueResolvers::DelegateResolver.get(delegate_to, key_method_name, delegate_allow_nil)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
to_step = delegate_to_step(delegate_to, to_access)
|
|
318
|
+
final_step = delegate_final_step(key_method_name, final_access)
|
|
319
|
+
AttributeValueResolvers::HashAccessDelegateResolver.get(to_step, final_step, delegate_allow_nil)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def delegate_to_step(delegate_to, access)
|
|
323
|
+
return AttributeValueResolvers::Keyword.new(delegate_to) unless access
|
|
324
|
+
|
|
325
|
+
mode, allow_missing_key = parse_hash_access(access)
|
|
326
|
+
AttributeValueResolvers::HashAccessKeyword.new(delegate_to, mode, allow_missing_key)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def delegate_final_step(key_method_name, access)
|
|
330
|
+
return AttributeValueResolvers::Keyword.new(key_method_name) unless access
|
|
300
331
|
|
|
301
|
-
|
|
302
|
-
AttributeValueResolvers::
|
|
332
|
+
mode, allow_missing_key = parse_hash_access(access)
|
|
333
|
+
AttributeValueResolvers::HashAccessKeyword.new(key_method_name, mode, allow_missing_key)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Resolves the :hash_access option of plain attributes
|
|
337
|
+
# @return [Array(Symbol, Boolean), nil] mode and allow_missing_key pair,
|
|
338
|
+
# or nil when hash access is not enabled for this attribute
|
|
339
|
+
def hash_access
|
|
340
|
+
option = init_opts[:hash_access]
|
|
341
|
+
return unless option
|
|
342
|
+
|
|
343
|
+
parse_hash_access(option)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Resolves a :hash_access value (`true`, a Symbol mode or a
|
|
347
|
+
# `{mode:, allow_missing_key:}` Hash) into a mode and allow_missing_key
|
|
348
|
+
# pair. `true` and a Hash omitting :mode resolve to
|
|
349
|
+
# `config.hash_access.default_mode`.
|
|
350
|
+
def parse_hash_access(option)
|
|
351
|
+
defaults = config.hash_access
|
|
352
|
+
|
|
353
|
+
case option
|
|
354
|
+
when true then [defaults.default_mode, defaults.default_allow_missing_key]
|
|
355
|
+
when Symbol then [option, defaults.default_allow_missing_key]
|
|
356
|
+
else
|
|
357
|
+
mode = option.fetch(:mode) { defaults.default_mode }
|
|
358
|
+
allow_missing_key = option.fetch(:allow_missing_key) { defaults.default_allow_missing_key }
|
|
359
|
+
[mode, allow_missing_key]
|
|
360
|
+
end
|
|
303
361
|
end
|
|
304
362
|
|
|
305
363
|
# Prepares preloads for this attribute.
|
|
@@ -4,10 +4,12 @@ class Serega
|
|
|
4
4
|
#
|
|
5
5
|
# Attribute value resolvers
|
|
6
6
|
#
|
|
7
|
+
# @private
|
|
7
8
|
module AttributeValueResolvers
|
|
8
9
|
#
|
|
9
10
|
# Builds value resolver class for attributes with :batch option
|
|
10
11
|
#
|
|
12
|
+
# @private
|
|
11
13
|
class BatchResolver
|
|
12
14
|
#
|
|
13
15
|
# Generates callable block to find attribute value when attribute with :batch
|
|
@@ -55,6 +57,7 @@ class Serega
|
|
|
55
57
|
#
|
|
56
58
|
# Builds value resolver class for attributes with :batch option
|
|
57
59
|
#
|
|
60
|
+
# @private
|
|
58
61
|
class Batch
|
|
59
62
|
def initialize(loader_name, id_method)
|
|
60
63
|
@loader_name = loader_name
|
|
@@ -4,10 +4,12 @@ class Serega
|
|
|
4
4
|
#
|
|
5
5
|
# Attribute value resolvers
|
|
6
6
|
#
|
|
7
|
+
# @private
|
|
7
8
|
module AttributeValueResolvers
|
|
8
9
|
#
|
|
9
10
|
# Builds value resolver class for attributes with :const option
|
|
10
11
|
#
|
|
12
|
+
# @private
|
|
11
13
|
class ConstResolver
|
|
12
14
|
#
|
|
13
15
|
# Creates resolver that returns constant value
|
|
@@ -23,6 +25,7 @@ class Serega
|
|
|
23
25
|
#
|
|
24
26
|
# Value resolver class for attributes with :const option
|
|
25
27
|
#
|
|
28
|
+
# @private
|
|
26
29
|
class Const
|
|
27
30
|
def initialize(const_value)
|
|
28
31
|
@const_value = const_value
|
|
@@ -4,10 +4,12 @@ class Serega
|
|
|
4
4
|
#
|
|
5
5
|
# Attribute value resolvers
|
|
6
6
|
#
|
|
7
|
+
# @private
|
|
7
8
|
module AttributeValueResolvers
|
|
8
9
|
#
|
|
9
10
|
# Builds value resolver class for attributes with :delegate option
|
|
10
11
|
#
|
|
12
|
+
# @private
|
|
11
13
|
class DelegateResolver
|
|
12
14
|
#
|
|
13
15
|
# Creates resolver that delegates method call to another object
|
|
@@ -25,6 +27,7 @@ class Serega
|
|
|
25
27
|
#
|
|
26
28
|
# Value resolver class for attributes with :delegate (with :allow_nil) option
|
|
27
29
|
#
|
|
30
|
+
# @private
|
|
28
31
|
class DelegateAllowNil
|
|
29
32
|
def initialize(delegate_to, method_name)
|
|
30
33
|
@delegate_to = delegate_to
|
|
@@ -49,6 +52,7 @@ class Serega
|
|
|
49
52
|
#
|
|
50
53
|
# Value resolver class for attributes with :delegate (without :allow_nil) option
|
|
51
54
|
#
|
|
55
|
+
# @private
|
|
52
56
|
class Delegate
|
|
53
57
|
def initialize(delegate_to, method_name)
|
|
54
58
|
@delegate_to = delegate_to
|