serega 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +145 -86
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +3 -3
  5. data/lib/serega/attribute_normalizer.rb +21 -2
  6. data/lib/serega/object_serializer.rb +1 -1
  7. data/lib/serega/plugins/batch/batch.rb +11 -56
  8. data/lib/serega/plugins/batch/lib/batch_config.rb +22 -27
  9. data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +27 -12
  10. data/lib/serega/plugins/batch/lib/modules/object_serializer.rb +2 -2
  11. data/lib/serega/plugins/batch/lib/modules/plan_point.rb +2 -19
  12. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_id_method.rb +43 -0
  13. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +17 -31
  14. data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +10 -11
  15. data/lib/serega/plugins/formatters/formatters.rb +88 -14
  16. data/lib/serega/plugins/if/if.rb +43 -19
  17. data/lib/serega/plugins/if/validations/check_opt_if.rb +4 -36
  18. data/lib/serega/plugins/if/validations/check_opt_if_value.rb +7 -39
  19. data/lib/serega/plugins/if/validations/check_opt_unless.rb +4 -45
  20. data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +7 -39
  21. data/lib/serega/plugins/metadata/meta_attribute.rb +21 -5
  22. data/lib/serega/plugins/metadata/metadata.rb +16 -7
  23. data/lib/serega/plugins/metadata/validations/check_block.rb +10 -10
  24. data/lib/serega/plugins/metadata/validations/check_opt_const.rb +38 -0
  25. data/lib/serega/plugins/metadata/validations/check_opt_value.rb +61 -0
  26. data/lib/serega/plugins/metadata/validations/check_opts.rb +24 -10
  27. data/lib/serega/utils/params_count.rb +50 -0
  28. data/lib/serega/utils/to_hash.rb +1 -1
  29. data/lib/serega/validations/attribute/check_block.rb +4 -5
  30. data/lib/serega/validations/attribute/check_opt_value.rb +16 -12
  31. data/lib/serega/validations/check_attribute_params.rb +1 -0
  32. data/lib/serega/validations/utils/check_extra_keyword_arg.rb +33 -0
  33. data/lib/serega.rb +2 -0
  34. metadata +8 -4
  35. 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: b291b12115b8ca239642972b103f896f13f02874acdc5663a8f404e02673ca8d
4
- data.tar.gz: 18bd88ca4bd77ca147161189efe57b2c544da6cc1b460fc7c694855d4b22edc9
3
+ metadata.gz: 60cc0f4e593d4600bf316e9c18bd93804008e28deebd5f51c231b632bb3a4eff
4
+ data.tar.gz: e3bdc8e6a0489a14916d84781dcf61c6a82dedb246a0b5366f324d78ca81d9b2
5
5
  SHA512:
6
- metadata.gz: d5c5114ed1c1b8b5a0714a340aca263983f5f8cdb24d15c8138124d64520ebafe03f6c64a7985f131ee399fab287ee578889b1affa1e2aa6f4ddacfbb9c8517e
7
- data.tar.gz: 4d8191c7081b4650e3328141ec3cc2dc9128a7c4da3a38107e5f2c107d1d70446b67e718a3ffa2ff76e700f80dbdf975da9512d089c86ec552c3dc8a390345d1
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
- Adds ability to load nested attributes values in batches.
542
+ Must be used to omit N+1 when loading attributes values.
542
543
 
543
- It can be used to find value for attributes in optimal way:
544
+ User must provide batch loader object to attribute -
545
+ `attribute :foo, batch: {loader: SomeLoader, id_method: :id}`.
544
546
 
545
- - load associations for multiple objects
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
- attribute :name, batch: { loader: :name_loader, key: :id, default: nil }
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
- `:batch` option must be a hash with this keys:
564
+ #### Option :loader
556
565
 
557
- - `loader` (required) [Symbol, Proc, callable] - Defines how to fetch values for
558
- batch of keys. Receives 3 parameters: keys, context, plan_point.
559
- - `key` (required) [Symbol, Proc, callable] - Defines current object identifier.
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
- If `:loader` was defined using name (as Symbol) then batch loader must be
566
- defined using `config.batch.define(:loader_name) { ... }` method.
570
+ Loader can accept 1 to 3 arguments:
567
571
 
568
- Result of this `:loader` callable must be a **Hash** where:
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
- - keys - provided keys
571
- - values - values for according keys
580
+ ```ruby
581
+ class AppSerializer < Serega
582
+ plugin :batch, id_method: :id
583
+ end
572
584
 
573
- `Batch` plugin can be defined with two specific attributes:
585
+ class UserSerializer < Serega
586
+ # Define loader as callable object
587
+ attribute :comments_count,
588
+ batch: { loader: CountLoader }
574
589
 
575
- - `auto_hide: true` - Marks attributes with defined :batch as hidden, so it
576
- will not be serialized by default
577
- - `default_key: :id` - Set default object key (in this case :id) that will be
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
- ```ruby
581
- plugin :batch, auto_hide: true, default_key: :id
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
- Options `auto_hide` and `default_key` can be overwritten in nested serializers.
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 AppSerializer
588
- plugin :batch, auto_hide: true, default_key: :id
612
+ class SomeSerializer
613
+ plugin :batch, id_method: :id
589
614
  end
590
615
 
591
616
  class UserSerializer < AppSerializer
592
- config.batch.auto_hide = false
593
- config.batch.default_key = :user_id
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
- ⚠️ ATTENTION: `Batch` plugin must be added to serializers which have no
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 AppSerializer < Serega
607
- plugin :batch, auto_hide: true, default_key: :id
631
+ class SomeSerializer
632
+ plugin :batch, id_method: :id # global id_method is `:id`
608
633
  end
609
634
 
610
- class PostSerializer < AppSerializer
611
- attribute :comments_count,
612
- batch: {
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
- # Define batch loader via Symbol, later we should define this loader via
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: :posts_comments_counter }
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
- # Resulted block must return hash like { key => value(s) }
632
- config.batch.define(:posts_comments_counter) do |keys|
633
- Comment.group(:post_id).where(post_id: keys).count
634
- end
644
+ # id_method is :user_id
645
+ attribute :company,
646
+ batch: { loader: UserCompanyBatchLoader }, serializer: CompanySerializer
635
647
 
636
- # We can return objects that will be automatically serialized if attribute
637
- # defined with :serializer
638
- # Parameter `context` can be used when loading batch
639
- # Parameter `point` can be used to find nested attributes to serialize
640
- config.batch.define(:posts_comments) do |keys, context, point|
641
- # point.child_plan - if you need to manually check all nested attributes
642
- # point.preloads - nested preloads (works with :preloads plugin only)
643
-
644
- Comment
645
- .preload(point.preloads) # Skip if :activerecord_preloads plugin used
646
- .where(post_id: keys)
647
- .where(is_spam: false)
648
- .group_by(&:post_id)
649
- end
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
- - *path [Array of Symbols] - nested hash keys.
719
- - **options [Hash] - defaults are `hide_nil: false, hide_empty: false`
720
- - &block [Proc] - describes value for current meta attribute
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) { '1.2.3' }
728
- meta_attribute(:ab_tests, :names) { %i[foo bar] }
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=>[:foo, :bar]}}
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` now can be used with name of formatter or with
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.round(2) }
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.16.0
1
+ 0.18.0
@@ -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
- ser = @serializer
65
- return ser if (ser.is_a?(Class) && (ser < Serega)) || !ser
64
+ serializer = @serializer
65
+ return serializer if (serializer.is_a?(Class) && (serializer < Serega)) || !serializer
66
66
 
67
- @serializer = ser.is_a?(String) ? Object.const_get(ser, false) : ser.call
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
- init_block ||
101
- init_opts[:value] ||
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
@@ -29,7 +29,7 @@ class Serega
29
29
  #
30
30
  # @param object [Object] Serialized object
31
31
  #
32
- # @return [Hash, Array<Hash>] Serialized object(s)
32
+ # @return [Hash, Array<Hash>, nil] Serialized object(s)
33
33
  def serialize(object)
34
34
  return if object.nil?
35
35
 
@@ -5,62 +5,17 @@ class Serega
5
5
  #
6
6
  # Plugin `:batch`
7
7
  #
8
- # Adds ability to load nested attributes values in batches.
8
+ # Must be used to omit N+1 when loading attributes values.
9
9
  #
10
- # It can be used to find value for attributes in optimal way:
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
- # After including plugin, attributes gain new `:batch` option.
16
- #
17
- # `:batch` option must be a hash with this keys:
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
- # # We can return objects that will be automatically serialized if attribute defined with :serializer
59
- # # Parameter `context` can be used when loading batch
60
- # # Parameter `plan_point` can be used to find nested attributes that will be serialized (`plan_point.preloads`)
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/check_batch_opt_key"
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: {}, default_key: nil, auto_hide: false}
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.default_key = opts[:default_key] if opts.key?(:default_key)
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 - keys, context, plan_point
21
- # and returns hash where ids are keys and values are batch loaded objects/
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
- unless block
27
- raise SeregaError, "Block must be given to #define method"
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
- params = block.parameters
31
- if params.count > 3 || !params.all? { |param| (param[0] == :req) || (param[0] == :opt) }
32
- raise SeregaError, "Block can have maximum 3 regular parameters"
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] = block
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 default key for :batch option
68
- # @return [Symbol, nil] default key for :batch option
69
- def default_key
70
- opts[:default_key]
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
- # @param value [Symbol] New :default_key option value
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 default_key=(value)
76
- raise SeregaError, "Must be a Symbol, #{value.inspect} provided" unless value.is_a?(Symbol)
77
- opts[:default_key] = value
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
- # take loader
46
- loader = batch[:loader]
45
+ loader = prepare_batch_loader(batch[:loader])
47
46
 
48
- # take key
49
- key = batch[:key] || self.class.serializer_class.config.batch.default_key
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, key: proc_key, default: default}
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
- key = batch[:key].call(object, context)
23
- batch_loader(point).remember(key, container)
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
- attr_reader :batch
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