serega 0.16.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
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