serega 0.16.0 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +145 -86
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +3 -3
- data/lib/serega/attribute_normalizer.rb +21 -2
- 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 +22 -27
- data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +27 -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 +17 -31
- data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +10 -11
- data/lib/serega/plugins/formatters/formatters.rb +88 -14
- data/lib/serega/plugins/if/if.rb +43 -19
- data/lib/serega/plugins/if/validations/check_opt_if.rb +4 -36
- data/lib/serega/plugins/if/validations/check_opt_if_value.rb +7 -39
- data/lib/serega/plugins/if/validations/check_opt_unless.rb +4 -45
- data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +7 -39
- data/lib/serega/plugins/metadata/meta_attribute.rb +21 -5
- data/lib/serega/plugins/metadata/metadata.rb +16 -7
- data/lib/serega/plugins/metadata/validations/check_block.rb +10 -10
- data/lib/serega/plugins/metadata/validations/check_opt_const.rb +38 -0
- data/lib/serega/plugins/metadata/validations/check_opt_value.rb +61 -0
- data/lib/serega/plugins/metadata/validations/check_opts.rb +24 -10
- data/lib/serega/utils/params_count.rb +50 -0
- data/lib/serega/utils/to_hash.rb +1 -1
- data/lib/serega/validations/attribute/check_block.rb +4 -5
- data/lib/serega/validations/attribute/check_opt_value.rb +16 -12
- data/lib/serega/validations/check_attribute_params.rb +1 -0
- data/lib/serega/validations/utils/check_extra_keyword_arg.rb +33 -0
- data/lib/serega.rb +2 -0
- metadata +8 -4
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_key.rb +0 -73
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60cc0f4e593d4600bf316e9c18bd93804008e28deebd5f51c231b632bb3a4eff
|
4
|
+
data.tar.gz: e3bdc8e6a0489a14916d84781dcf61c6a82dedb246a0b5366f324d78ca81d9b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 879f46f3bface078c156ad0c89e71b4abc08945754034235d03a688c5613e549d477ae1d24bfa6d0f2c44de1def6266d9b18677ab9fab53ef8341510075806d5
|
7
|
+
data.tar.gz: 45b302b1efc001b9ff3ef35bb9d71bf05e3dcfffbfd35938cf938448b308e9c6c0f74a8f1c1eeceabf8a3bd0eb759f50dd053948664565fbeef2f4b8502841b2
|
data/README.md
CHANGED
@@ -73,7 +73,8 @@ class UserSerializer < Serega
|
|
73
73
|
# Block is used to define attribute value
|
74
74
|
attribute(:first_name) { |user| user.profile&.first_name }
|
75
75
|
|
76
|
-
# Option :value can be used with callable object to define attribute value
|
76
|
+
# Option :value can be used with proc or callable object to define attribute value
|
77
|
+
attribute :first_name, value: UserProfile.new # must have #call method
|
77
78
|
attribute :first_name, value: proc { |user| user.profile&.first_name }
|
78
79
|
|
79
80
|
# Option :delegate can be used to define attribute value.
|
@@ -538,118 +539,165 @@ UserSerializer.to_h(user)
|
|
538
539
|
|
539
540
|
### Plugin :batch
|
540
541
|
|
541
|
-
|
542
|
+
Must be used to omit N+1 when loading attributes values.
|
542
543
|
|
543
|
-
|
544
|
+
User must provide batch loader object to attribute -
|
545
|
+
`attribute :foo, batch: {loader: SomeLoader, id_method: :id}`.
|
544
546
|
|
545
|
-
|
546
|
-
- load counters for multiple objects
|
547
|
-
- make any heavy calculations for multiple objects only once
|
548
|
-
|
549
|
-
After including plugin, attributes gain new `:batch` option:
|
547
|
+
Result must be returned as Hash, where each key is one of provided ids.
|
550
548
|
|
551
549
|
```ruby
|
552
|
-
|
550
|
+
class AppSerializer
|
551
|
+
plugin :batch
|
552
|
+
end
|
553
|
+
|
554
|
+
class UserSerializer < AppSerializer
|
555
|
+
attribute :comments_count,
|
556
|
+
batch: { loader: SomeLoader, id_method: :id }
|
557
|
+
|
558
|
+
attribute :company,
|
559
|
+
batch: { loader: SomeLoader, id_method: :id },
|
560
|
+
serializer: CompanySerializer
|
561
|
+
end
|
553
562
|
```
|
554
563
|
|
555
|
-
|
564
|
+
#### Option :loader
|
556
565
|
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
Key is optional if plugin was defined with `default_key` option.
|
561
|
-
- `default` (optional) - Default value for attribute.
|
562
|
-
By default it is `nil` or `[]` when attribute has option `many: true`
|
563
|
-
(ex: `attribute :tags, many: true, batch: { ... }`).
|
566
|
+
Loaders can be defined as a Proc, a callable value or a named Symbol
|
567
|
+
Named loaders should be predefined with
|
568
|
+
`config.batch.define(:loader_name) { |ids| ... })`
|
564
569
|
|
565
|
-
|
566
|
-
defined using `config.batch.define(:loader_name) { ... }` method.
|
570
|
+
Loader can accept 1 to 3 arguments:
|
567
571
|
|
568
|
-
|
572
|
+
1. List of ids (each id will be found by using `:id_method` option)
|
573
|
+
1. Context
|
574
|
+
1. PlanPoint - a special object containing information about current
|
575
|
+
attribute and all children and parent attributes. It can be used to preload
|
576
|
+
required associations to batch values.
|
577
|
+
See [example](examples/batch_loader.rb) how
|
578
|
+
to find required preloads when using with `:preloads` plugin.
|
569
579
|
|
570
|
-
|
571
|
-
|
580
|
+
```ruby
|
581
|
+
class AppSerializer < Serega
|
582
|
+
plugin :batch, id_method: :id
|
583
|
+
end
|
572
584
|
|
573
|
-
|
585
|
+
class UserSerializer < Serega
|
586
|
+
# Define loader as callable object
|
587
|
+
attribute :comments_count,
|
588
|
+
batch: { loader: CountLoader }
|
574
589
|
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
used for all attributes with :batch option specified.
|
590
|
+
# Define loader as a Proc
|
591
|
+
attribute :comments_count,
|
592
|
+
batch: { loader: proc { |ids| CountLoader.call(ids) } }
|
579
593
|
|
580
|
-
|
581
|
-
|
594
|
+
# Define loader as a Symbol
|
595
|
+
config.batch.define(:comments_count_loader) { |ids| CountLoader.call(ids }
|
596
|
+
attribute :comments_count, batch: { loader: :comments_count_loader }
|
597
|
+
end
|
598
|
+
|
599
|
+
class CountLoader
|
600
|
+
def self.call(user_ids)
|
601
|
+
Comment.where(user_id: user_ids).group(:user_id).count
|
602
|
+
end
|
603
|
+
end
|
582
604
|
```
|
583
605
|
|
584
|
-
|
606
|
+
#### Option :id_method
|
607
|
+
|
608
|
+
Batch plugin can be added with global `:id_method` option. It can be a Symbol,
|
609
|
+
Proc or any callable value, which can accept current object and current context.
|
585
610
|
|
586
611
|
```ruby
|
587
|
-
class
|
588
|
-
plugin :batch,
|
612
|
+
class SomeSerializer
|
613
|
+
plugin :batch, id_method: :id
|
589
614
|
end
|
590
615
|
|
591
616
|
class UserSerializer < AppSerializer
|
592
|
-
|
593
|
-
|
617
|
+
attribute :comments_count,
|
618
|
+
batch: { loader: CommentsCountBatchLoader } # no :id_method here anymore
|
619
|
+
|
620
|
+
attribute :company,
|
621
|
+
batch: { loader: UserCompanyBatchLoader }, # no :id_method here anymore
|
622
|
+
serializer: CompanySerializer
|
594
623
|
end
|
624
|
+
|
595
625
|
```
|
596
626
|
|
597
|
-
|
598
|
-
|
599
|
-
`:batch` attributes, but have nested serializers, that have some. For example
|
600
|
-
when you serialize `User -> Album -> Song` and Song has `:batch` attribute, then
|
601
|
-
`:batch` plugin must be added to the User serializer also. \
|
602
|
-
Best way would be to create one parent `AppSerializer < Serega` for all your
|
603
|
-
serializers and add `:batch` plugin only to this parent `AppSerializer`
|
627
|
+
However, global `id_method` option can be overwritten via `config.batch.id_method=`
|
628
|
+
method or in specific attributes with `id_method` option.
|
604
629
|
|
605
630
|
```ruby
|
606
|
-
class
|
607
|
-
plugin :batch,
|
631
|
+
class SomeSerializer
|
632
|
+
plugin :batch, id_method: :id # global id_method is `:id`
|
608
633
|
end
|
609
634
|
|
610
|
-
class
|
611
|
-
|
612
|
-
|
613
|
-
loader: CommentsCountBatchLoader, # callable(keys, context, plan_point)
|
614
|
-
key: :id, # can be skipped (as :id value is same as configured :default_key)
|
615
|
-
default: 0
|
616
|
-
}
|
635
|
+
class UserSerializer < AppSerializer
|
636
|
+
# :user_id will be used as default `id_method` for all batch attributes
|
637
|
+
config.batch.id_method = :user_id
|
617
638
|
|
618
|
-
#
|
619
|
-
# `config.batch.define(:posts_comments_counter) { ... }`
|
620
|
-
#
|
621
|
-
# Loader will receive array of ids, as `default_key: :id` plugin option was specified.
|
622
|
-
# Default value for not found counters is nil, as `:default` option not defined
|
639
|
+
# id_method is :user_id
|
623
640
|
attribute :comments_count,
|
624
|
-
batch: { loader:
|
641
|
+
batch: { loader: CommentsCountBatchLoader }
|
625
642
|
|
626
|
-
# Define batch loader with serializer
|
627
|
-
attribute :comments,
|
628
|
-
serializer: CommentSerializer,
|
629
|
-
batch: {loader: :posts_comments, default: []}
|
630
643
|
|
631
|
-
#
|
632
|
-
|
633
|
-
|
634
|
-
end
|
644
|
+
# id_method is :user_id
|
645
|
+
attribute :company,
|
646
|
+
batch: { loader: UserCompanyBatchLoader }, serializer: CompanySerializer
|
635
647
|
|
636
|
-
#
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
648
|
+
# id_method is :uuid
|
649
|
+
attribute :points_amount,
|
650
|
+
batch: { loader: PointsBatchLoader, id_method: :uuid }
|
651
|
+
end
|
652
|
+
```
|
653
|
+
|
654
|
+
#### Option :default
|
655
|
+
|
656
|
+
The default value for attributes without found value can be specified via
|
657
|
+
`:default` option. By default attributes without found value will be
|
658
|
+
serialized as `nil`. Attribute marked as `many: true` will be
|
659
|
+
serialized as empty array `[]`
|
660
|
+
|
661
|
+
```ruby
|
662
|
+
class UserSerializer < AppSerializer
|
663
|
+
# Missing values become empty arrays, as `many: true` option specified
|
664
|
+
attribute :companies,
|
665
|
+
batch: {loader: proc {}},
|
666
|
+
serializer: CompanySerializer,
|
667
|
+
many: true
|
668
|
+
|
669
|
+
# Missing values become `0` as specified directly
|
670
|
+
attribute :points_amount,
|
671
|
+
batch: { loader: proc {}, default: 0 }
|
672
|
+
end
|
673
|
+
```
|
674
|
+
|
675
|
+
Batch attributes can be marked as hidden by default if plugin specified with
|
676
|
+
`auto_hide` option. Also `auto_hide` option can be changed with
|
677
|
+
`config.batch.auto_hide=` method.
|
678
|
+
|
679
|
+
Look at [select serialized fields](#selecting-fields) for more information
|
680
|
+
about hiding/showing attributes.
|
681
|
+
|
682
|
+
```ruby
|
683
|
+
class AppSerializer
|
684
|
+
plugin :batch, auto_hide: true
|
685
|
+
end
|
686
|
+
|
687
|
+
class UserSerializer < AppSerializer
|
688
|
+
config.batch.auto_hide = false
|
650
689
|
end
|
651
690
|
```
|
652
691
|
|
692
|
+
---
|
693
|
+
⚠️ ATTENTION: `Batch` plugin must be added to all serializers that have
|
694
|
+
`:batch` attributes inside nested serializers. For example when you serialize
|
695
|
+
`User -> Album -> Song` and Song has `batch` attribute, then
|
696
|
+
`batch` plugin must be added to the User serializer also.
|
697
|
+
|
698
|
+
Best way would be to create one parent `AppSerializer < Serega` serializer
|
699
|
+
and add `:batch` plugin once to this parent serializer.
|
700
|
+
|
653
701
|
### Plugin :root
|
654
702
|
|
655
703
|
Allows to add root key to your serialized data
|
@@ -715,17 +763,24 @@ Adds ability to describe metadata and adds it to serialized response
|
|
715
763
|
|
716
764
|
Added class-level method `:meta_attribute`, to define metadata, it accepts:
|
717
765
|
|
718
|
-
-
|
719
|
-
-
|
720
|
-
|
766
|
+
- `*path` [Array of Symbols] - nested hash keys.
|
767
|
+
- `**options` [Hash]
|
768
|
+
|
769
|
+
- `:const` - describes metadata value (if it is constant)
|
770
|
+
- `:value` - describes metadata value as any `#callable` instance
|
771
|
+
- `:hide_nil` - does not show metadata key if value is nil, `false` by default
|
772
|
+
- `:hide_empty`, does not show metadata key if value is nil or empty,
|
773
|
+
`false` by default
|
774
|
+
|
775
|
+
- `&block` [Proc] - describes value for current meta attribute
|
721
776
|
|
722
777
|
```ruby
|
723
778
|
class AppSerializer < Serega
|
724
779
|
plugin :root
|
725
780
|
plugin :metadata
|
726
781
|
|
727
|
-
meta_attribute(:version
|
728
|
-
meta_attribute(:ab_tests, :names
|
782
|
+
meta_attribute(:version, const: '1.2.3')
|
783
|
+
meta_attribute(:ab_tests, :names, value: ABTests.new.method(:names))
|
729
784
|
meta_attribute(:meta, :paging, hide_nil: true) do |records, ctx|
|
730
785
|
next unless records.respond_to?(:total_count)
|
731
786
|
|
@@ -738,7 +793,7 @@ class AppSerializer < Serega
|
|
738
793
|
end
|
739
794
|
|
740
795
|
AppSerializer.to_h(nil)
|
741
|
-
# => {:data=>nil, :version=>"1.2.3", :ab_tests=>{:names=>
|
796
|
+
# => {:data=>nil, :version=>"1.2.3", :ab_tests=>{:names=> ... }}
|
742
797
|
```
|
743
798
|
|
744
799
|
### Plugin :context_metadata
|
@@ -773,15 +828,18 @@ Allows to define `formatters` and apply them on attribute values.
|
|
773
828
|
|
774
829
|
Config option `config.formatters.add` can be used to add formatters.
|
775
830
|
|
776
|
-
Attribute option `:format`
|
831
|
+
Attribute option `:format` can be used with name of formatter or with
|
777
832
|
callable instance.
|
778
833
|
|
834
|
+
Formatters can accept up to 2 parameters (formatted object, context)
|
835
|
+
|
779
836
|
```ruby
|
780
837
|
class AppSerializer < Serega
|
781
838
|
plugin :formatters, formatters: {
|
782
839
|
iso8601: ->(value) { time.iso8601.round(6) },
|
783
840
|
on_off: ->(value) { value ? 'ON' : 'OFF' },
|
784
|
-
money: ->(value) { value
|
841
|
+
money: ->(value, ctx) { value / 10**ctx[:digits) }
|
842
|
+
date: DateTypeFormatter # callable
|
785
843
|
}
|
786
844
|
end
|
787
845
|
|
@@ -1029,6 +1087,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
1029
1087
|
[batch]: #plugin-batch
|
1030
1088
|
[camel_case]: #plugin-camel_case
|
1031
1089
|
[context_metadata]: #plugin-context_metadata
|
1090
|
+
[depth_limit]: #plugin-depth_limit
|
1032
1091
|
[formatters]: #plugin-formatters
|
1033
1092
|
[metadata]: #plugin-metadata
|
1034
1093
|
[preloads]: #plugin-preloads
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.18.0
|
data/lib/serega/attribute.rb
CHANGED
@@ -61,10 +61,10 @@ class Serega
|
|
61
61
|
# Shows specified serializer class
|
62
62
|
# @return [Serega, nil] Attribute serializer if exists
|
63
63
|
def serializer
|
64
|
-
|
65
|
-
return
|
64
|
+
serializer = @serializer
|
65
|
+
return serializer if (serializer.is_a?(Class) && (serializer < Serega)) || !serializer
|
66
66
|
|
67
|
-
@serializer =
|
67
|
+
@serializer = serializer.is_a?(String) ? Object.const_get(serializer, false) : serializer.call
|
68
68
|
end
|
69
69
|
|
70
70
|
#
|
@@ -97,8 +97,8 @@ class Serega
|
|
97
97
|
# - plugin :formatters (wraps resulted block in formatter block and formats :const values)
|
98
98
|
#
|
99
99
|
def prepare_value_block
|
100
|
-
|
101
|
-
|
100
|
+
prepare_init_block ||
|
101
|
+
prepare_value_option_block ||
|
102
102
|
prepare_const_block ||
|
103
103
|
prepare_delegate_block ||
|
104
104
|
prepare_keyword_block
|
@@ -139,6 +139,25 @@ class Serega
|
|
139
139
|
end
|
140
140
|
end
|
141
141
|
|
142
|
+
def prepare_init_block
|
143
|
+
prepare_callable_proc(init_block)
|
144
|
+
end
|
145
|
+
|
146
|
+
def prepare_value_option_block
|
147
|
+
prepare_callable_proc(init_opts[:value])
|
148
|
+
end
|
149
|
+
|
150
|
+
def prepare_callable_proc(callable)
|
151
|
+
return unless callable
|
152
|
+
|
153
|
+
params_count = SeregaUtils::ParamsCount.call(callable, max_count: 2)
|
154
|
+
case params_count
|
155
|
+
when 0 then proc { |obj, _ctx| callable.call }
|
156
|
+
when 1 then proc { |obj, _ctx| callable.call(obj) }
|
157
|
+
else callable
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
142
161
|
def prepare_delegate_block
|
143
162
|
delegate = init_opts[:delegate]
|
144
163
|
return unless delegate
|
@@ -5,62 +5,17 @@ class Serega
|
|
5
5
|
#
|
6
6
|
# Plugin `:batch`
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# Must be used to omit N+1 when loading attributes values.
|
9
9
|
#
|
10
|
-
#
|
11
|
-
# - load associations for multiple objects
|
12
|
-
# - load counters for multiple objects
|
13
|
-
# - make any heavy calculations for multiple objects only once
|
10
|
+
# @example Quick example
|
14
11
|
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# - `key` (required) [Symbol, Proc, callable] - Defines current object identifier.
|
19
|
-
# Later `loader` will accept array of `keys` to find `values`.
|
20
|
-
# - `loader` (required) [Symbol, Proc, callable] - Defines how to fetch values for
|
21
|
-
# batch of keys. Receives 3 parameters: keys, context, plan_point.
|
22
|
-
# - `default` (optional) - Default value for attribute.
|
23
|
-
# By default it is `nil` or `[]` when attribute has option `many: true`
|
24
|
-
#
|
25
|
-
# If `:loader` was defined using name (as Symbol) then batch loader must be
|
26
|
-
# defined in serializer config: `config.batch.define(:loader_name) { ... }` method.
|
27
|
-
#
|
28
|
-
# *Result of this `:loader` callable must be a **Hash** where*:
|
29
|
-
# - keys - provided keys
|
30
|
-
# - values - values for according keys
|
31
|
-
#
|
32
|
-
# `Batch` plugin can be defined with two specific attributes:
|
33
|
-
# - `auto_hide: true` - Marks attributes with defined :batch as hidden, so it
|
34
|
-
# will not be serialized by default
|
35
|
-
# - `default_key: :id` - Set default object key (in this case :id) that will be used for all attributes with :batch option specified.
|
36
|
-
#
|
37
|
-
# This options (`auto_hide`, `default_key`) also can be set as config options in
|
38
|
-
# any nested serializer.
|
39
|
-
#
|
40
|
-
# @example
|
41
|
-
# class PostSerializer < Serega
|
42
|
-
# plugin :batch, auto_hide: true, default_key: :id
|
43
|
-
#
|
44
|
-
# # Define batch loader via callable class, it must accept three args (keys, context, plan_point)
|
45
|
-
# attribute :comments_count, batch: { loader: PostCommentsCountBatchLoader, default: 0}
|
46
|
-
#
|
47
|
-
# # Define batch loader via Symbol, later we should define this loader via config.batch.define(:posts_comments_counter) { ... }
|
48
|
-
# attribute :comments_count, batch: { loader: :posts_comments_counter, default: 0}
|
49
|
-
#
|
50
|
-
# # Define batch loader with serializer
|
51
|
-
# attribute :comments, serializer: CommentSerializer, batch: { loader: :posts_comments, default: []}
|
52
|
-
#
|
53
|
-
# # Resulted block must return hash like { key => value(s) }
|
54
|
-
# config.batch.define(:posts_comments_counter) do |keys|
|
55
|
-
# Comment.group(:post_id).where(post_id: keys).count
|
56
|
-
# end
|
12
|
+
# class AppSerializer
|
13
|
+
# plugin :batch, id_method: :id
|
14
|
+
# end
|
57
15
|
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# config.batch.define(:posts_comments) do |keys, context, plan_point|
|
62
|
-
# Comment.where(post_id: keys).where(is_spam: false).group_by(&:post_id)
|
63
|
-
# end
|
16
|
+
# class UserSerializer < AppSerializer
|
17
|
+
# attribute :comments_count, batch: { loader: CommentsCountBatchLoader, default: 0 }
|
18
|
+
# attribute :company, serializer: CompanySerializer, batch: { loader: UserCompanyBatchLoader }
|
64
19
|
# end
|
65
20
|
#
|
66
21
|
module Batch
|
@@ -88,7 +43,7 @@ class Serega
|
|
88
43
|
require_relative "lib/modules/config"
|
89
44
|
require_relative "lib/modules/object_serializer"
|
90
45
|
require_relative "lib/modules/plan_point"
|
91
|
-
require_relative "lib/validations/
|
46
|
+
require_relative "lib/validations/check_batch_opt_id_method"
|
92
47
|
require_relative "lib/validations/check_batch_opt_loader"
|
93
48
|
require_relative "lib/validations/check_opt_batch"
|
94
49
|
|
@@ -138,9 +93,9 @@ class Serega
|
|
138
93
|
|
139
94
|
config = serializer_class.config
|
140
95
|
config.attribute_keys << :batch
|
141
|
-
config.opts[:batch] = {loaders: {},
|
96
|
+
config.opts[:batch] = {loaders: {}, id_method: nil, auto_hide: false}
|
142
97
|
config.batch.auto_hide = opts[:auto_hide] if opts.key?(:auto_hide)
|
143
|
-
config.batch.
|
98
|
+
config.batch.id_method = opts[:id_method] if opts.key?(:id_method)
|
144
99
|
end
|
145
100
|
|
146
101
|
#
|
@@ -17,22 +17,25 @@ class Serega
|
|
17
17
|
# Defines batch loader
|
18
18
|
#
|
19
19
|
# @param loader_name [Symbol] Batch loader name, that is used when defining attribute with batch loader.
|
20
|
-
# @param block [Proc] Block that can accept 3 parameters -
|
21
|
-
# and returns hash
|
20
|
+
# @param block [Proc] Block that can accept 3 parameters - ids, context, plan_point
|
21
|
+
# and returns hash with ids as keys and values are batch loaded objects
|
22
22
|
#
|
23
23
|
# @return [void]
|
24
24
|
#
|
25
|
-
def define(loader_name, &block)
|
26
|
-
|
27
|
-
raise SeregaError, "
|
25
|
+
def define(loader_name, callable = nil, &block)
|
26
|
+
if (!callable && !block) || (callable && block)
|
27
|
+
raise SeregaError, "Batch loader can be specified with one of arguments - callable value or &block"
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
callable ||= block
|
31
|
+
SeregaValidations::Utils::CheckExtraKeywordArg.call(callable, "batch loader `#{loader_name}`")
|
32
|
+
params_count = SeregaUtils::ParamsCount.call(callable, max_count: 3)
|
33
|
+
|
34
|
+
if params_count > 3
|
35
|
+
raise SeregaError, "Batch loader can have maximum 3 parameters (ids, context, plan)"
|
33
36
|
end
|
34
37
|
|
35
|
-
loaders[loader_name] =
|
38
|
+
loaders[loader_name] = callable
|
36
39
|
end
|
37
40
|
|
38
41
|
# Shows defined loaders
|
@@ -41,16 +44,6 @@ class Serega
|
|
41
44
|
opts[:loaders]
|
42
45
|
end
|
43
46
|
|
44
|
-
#
|
45
|
-
# Finds previously defined batch loader by name
|
46
|
-
#
|
47
|
-
# @param loader_name [Symbol]
|
48
|
-
#
|
49
|
-
# @return [Proc] batch loader block
|
50
|
-
def fetch_loader(loader_name)
|
51
|
-
loaders[loader_name] || (raise SeregaError, "Batch loader with name `#{loader_name.inspect}` was not defined. Define example: config.batch.define(:#{loader_name}) { |keys, ctx, points| ... }")
|
52
|
-
end
|
53
|
-
|
54
47
|
# Shows option to auto hide attributes with :batch specified
|
55
48
|
# @return [Boolean, nil] option value
|
56
49
|
def auto_hide
|
@@ -64,17 +57,19 @@ class Serega
|
|
64
57
|
opts[:auto_hide] = value
|
65
58
|
end
|
66
59
|
|
67
|
-
# Shows
|
68
|
-
# @return [Symbol, nil]
|
69
|
-
def
|
70
|
-
opts[:
|
60
|
+
# Shows method name or callable object needed to get object identifier for batch load
|
61
|
+
# @return [Symbol, #call, nil] Default method name or callable object to get identifier
|
62
|
+
def id_method
|
63
|
+
opts[:id_method]
|
71
64
|
end
|
72
65
|
|
73
|
-
#
|
66
|
+
# Sets new identifier method name or callable value needed for batch loading
|
67
|
+
#
|
68
|
+
# @param value [Symbol, #call] New :id_method value
|
74
69
|
# @return [Boolean] New option value
|
75
|
-
def
|
76
|
-
|
77
|
-
opts[:
|
70
|
+
def id_method=(value)
|
71
|
+
CheckBatchOptIdMethod.call(value)
|
72
|
+
opts[:id_method] = value
|
78
73
|
end
|
79
74
|
end
|
80
75
|
end
|
@@ -42,22 +42,37 @@ class Serega
|
|
42
42
|
batch = init_opts[:batch]
|
43
43
|
return unless batch
|
44
44
|
|
45
|
-
|
46
|
-
loader = batch[:loader]
|
45
|
+
loader = prepare_batch_loader(batch[:loader])
|
47
46
|
|
48
|
-
|
49
|
-
|
50
|
-
proc_key =
|
51
|
-
if key.is_a?(Symbol)
|
52
|
-
proc { |object| object.public_send(key) }
|
53
|
-
else
|
54
|
-
key
|
55
|
-
end
|
47
|
+
id_method = batch[:id_method] || self.class.serializer_class.config.batch.id_method
|
48
|
+
id_method = prepare_batch_id_method(id_method)
|
56
49
|
|
57
|
-
# take default value
|
58
50
|
default = batch.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
|
59
51
|
|
60
|
-
{loader: loader,
|
52
|
+
{loader: loader, id_method: id_method, default: default}
|
53
|
+
end
|
54
|
+
|
55
|
+
def prepare_batch_id_method(id_method)
|
56
|
+
return proc { |object| object.public_send(id_method) } if id_method.is_a?(Symbol)
|
57
|
+
|
58
|
+
params_count = SeregaUtils::ParamsCount.call(id_method, max_count: 2)
|
59
|
+
case params_count
|
60
|
+
when 0 then proc { id_method.call }
|
61
|
+
when 1 then proc { |object| id_method.call(object) }
|
62
|
+
else id_method
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def prepare_batch_loader(loader)
|
67
|
+
loader = self.class.serializer_class.config.batch.loaders.fetch(loader) if loader.is_a?(Symbol)
|
68
|
+
|
69
|
+
params_count = SeregaUtils::ParamsCount.call(loader, max_count: 3)
|
70
|
+
case params_count
|
71
|
+
when 0 then proc { loader.call }
|
72
|
+
when 1 then proc { |object| loader.call(object) }
|
73
|
+
when 2 then proc { |object, context| loader.call(object, context) }
|
74
|
+
else loader
|
75
|
+
end
|
61
76
|
end
|
62
77
|
end
|
63
78
|
end
|
@@ -19,8 +19,8 @@ class Serega
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def remember_key_for_batch_loading(batch, object, point, container)
|
22
|
-
|
23
|
-
batch_loader(point).remember(
|
22
|
+
id = batch[:id_method].call(object, context)
|
23
|
+
batch_loader(point).remember(id, container)
|
24
24
|
container[point.name] = nil # Reserve attribute place in resulted hash. We will set correct value later
|
25
25
|
end
|
26
26
|
|
@@ -13,25 +13,8 @@ class Serega
|
|
13
13
|
# Returns attribute :batch option with prepared loader
|
14
14
|
# @return [Hash] attribute :batch option
|
15
15
|
#
|
16
|
-
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def set_normalized_vars
|
21
|
-
super
|
22
|
-
@batch = prepare_batch
|
23
|
-
end
|
24
|
-
|
25
|
-
def prepare_batch
|
26
|
-
batch = attribute.batch
|
27
|
-
if batch
|
28
|
-
loader = batch[:loader]
|
29
|
-
if loader.is_a?(Symbol)
|
30
|
-
batch_config = attribute.class.serializer_class.config.batch
|
31
|
-
batch[:loader] = batch_config.fetch_loader(loader)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
batch
|
16
|
+
def batch
|
17
|
+
attribute.batch
|
35
18
|
end
|
36
19
|
end
|
37
20
|
end
|