serega 0.21.0 → 0.30.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +113 -229
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +31 -3
  5. data/lib/serega/attribute_normalizer.rb +143 -42
  6. data/lib/serega/batch/attribute_loader.rb +70 -0
  7. data/lib/serega/batch/attribute_loaders.rb +59 -0
  8. data/lib/serega/batch/auto_resolver.rb +24 -0
  9. data/lib/serega/batch/auto_resolver_factory.rb +48 -0
  10. data/lib/serega/batch/loader.rb +67 -0
  11. data/lib/serega/config.rb +59 -1
  12. data/lib/serega/object_serializer.rb +11 -9
  13. data/lib/serega/plan.rb +16 -0
  14. data/lib/serega/plan_point.rb +31 -5
  15. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +25 -13
  16. data/lib/serega/plugins/activerecord_preloads/lib/active_record_objects.rb +31 -0
  17. data/lib/serega/plugins/camel_case/camel_case.rb +1 -1
  18. data/lib/serega/plugins/formatters/formatters.rb +79 -22
  19. data/lib/serega/plugins/if/if.rb +41 -22
  20. data/lib/serega/plugins/if/validations/check_opt_if.rb +27 -6
  21. data/lib/serega/plugins/if/validations/check_opt_if_value.rb +27 -6
  22. data/lib/serega/plugins/if/validations/check_opt_unless.rb +27 -6
  23. data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +27 -6
  24. data/lib/serega/plugins/metadata/meta_attribute.rb +9 -10
  25. data/lib/serega/plugins/metadata/validations/check_block.rb +2 -4
  26. data/lib/serega/plugins/metadata/validations/check_opt_value.rb +2 -3
  27. data/lib/serega/plugins/presenter/presenter.rb +1 -1
  28. data/lib/serega/utils/format_user_preloads.rb +58 -0
  29. data/lib/serega/utils/method_signature.rb +89 -0
  30. data/lib/serega/utils/preload_paths.rb +53 -0
  31. data/lib/serega/utils/preloads_constructor.rb +77 -0
  32. data/lib/serega/validations/attribute/check_block.rb +38 -6
  33. data/lib/serega/validations/attribute/check_opt_batch.rb +101 -0
  34. data/lib/serega/validations/attribute/check_opt_const.rb +1 -0
  35. data/lib/serega/validations/attribute/check_opt_delegate.rb +1 -0
  36. data/lib/serega/validations/attribute/check_opt_method.rb +1 -0
  37. data/lib/serega/{plugins/preloads/validations → validations/attribute}/check_opt_preload.rb +2 -2
  38. data/lib/serega/{plugins/preloads/validations → validations/attribute}/check_opt_preload_path.rb +3 -3
  39. data/lib/serega/validations/attribute/check_opt_value.rb +35 -9
  40. data/lib/serega/validations/check_attribute_params.rb +3 -2
  41. data/lib/serega/validations/check_batch_loader_params.rb +80 -0
  42. data/lib/serega/validations/initiate/check_modifiers.rb +1 -1
  43. data/lib/serega.rb +98 -7
  44. metadata +17 -37
  45. data/lib/serega/plugins/batch/batch.rb +0 -168
  46. data/lib/serega/plugins/batch/lib/batch_config.rb +0 -77
  47. data/lib/serega/plugins/batch/lib/loader.rb +0 -113
  48. data/lib/serega/plugins/batch/lib/loaders.rb +0 -45
  49. data/lib/serega/plugins/batch/lib/modules/attribute.rb +0 -26
  50. data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +0 -78
  51. data/lib/serega/plugins/batch/lib/modules/check_attribute_params.rb +0 -22
  52. data/lib/serega/plugins/batch/lib/modules/config.rb +0 -23
  53. data/lib/serega/plugins/batch/lib/modules/object_serializer.rb +0 -46
  54. data/lib/serega/plugins/batch/lib/modules/plan_point.rb +0 -22
  55. data/lib/serega/plugins/batch/lib/plugins_extensions/activerecord_preloads.rb +0 -43
  56. data/lib/serega/plugins/batch/lib/plugins_extensions/formatters.rb +0 -54
  57. data/lib/serega/plugins/batch/lib/plugins_extensions/if.rb +0 -31
  58. data/lib/serega/plugins/batch/lib/plugins_extensions/preloads.rb +0 -34
  59. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_id_method.rb +0 -43
  60. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +0 -59
  61. data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +0 -62
  62. data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +0 -60
  63. data/lib/serega/plugins/preloads/lib/modules/attribute.rb +0 -28
  64. data/lib/serega/plugins/preloads/lib/modules/attribute_normalizer.rb +0 -99
  65. data/lib/serega/plugins/preloads/lib/modules/check_attribute_params.rb +0 -22
  66. data/lib/serega/plugins/preloads/lib/modules/config.rb +0 -19
  67. data/lib/serega/plugins/preloads/lib/modules/plan_point.rb +0 -41
  68. data/lib/serega/plugins/preloads/lib/preload_paths.rb +0 -53
  69. data/lib/serega/plugins/preloads/lib/preloads_config.rb +0 -62
  70. data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +0 -79
  71. data/lib/serega/plugins/preloads/preloads.rb +0 -162
  72. data/lib/serega/utils/params_count.rb +0 -50
  73. data/lib/serega/validations/utils/check_extra_keyword_arg.rb +0 -33
@@ -62,7 +62,7 @@ class Serega
62
62
  # Creates delegator method after first #method_missing hit to improve
63
63
  # performance of following serializations.
64
64
  #
65
- def method_missing(name, *_args, &_block) # rubocop:disable Style/MissingRespondToMissing (base SimpleDelegator class has this method)
65
+ def method_missing(name, *_args, &_block) # rubocop:disable Style/MissingRespondToMissing -- base SimpleDelegator class has this method
66
66
  super.tap do
67
67
  self.class.def_delegator :__getobj__, name
68
68
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaUtils
5
+ #
6
+ # Utility that helps to transform user provided preloads to hash
7
+ #
8
+ class FormatUserPreloads
9
+ class << self
10
+ #
11
+ # Transforms user provided preloads to hash
12
+ #
13
+ # @param value [Array,Hash,String,Symbol,nil,false] preloads
14
+ #
15
+ # @return [Hash] preloads transformed to hash
16
+ #
17
+ def call(value)
18
+ case value
19
+ when Array then array_to_hash(value)
20
+ when FalseClass then nil_to_hash(value)
21
+ when Hash then hash_to_hash(value)
22
+ when NilClass then nil_to_hash(value)
23
+ when String then string_to_hash(value)
24
+ when Symbol then symbol_to_hash(value)
25
+ else raise Serega::SeregaError,
26
+ "Preload option value can consist from Symbols, Arrays, Hashes (#{value.class} #{value.inspect} was provided)"
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def array_to_hash(values)
33
+ values.each_with_object({}) do |value, obj|
34
+ obj.merge!(call(value))
35
+ end
36
+ end
37
+
38
+ def hash_to_hash(values)
39
+ values.each_with_object({}) do |(key, value), obj|
40
+ obj[key.to_sym] = call(value)
41
+ end
42
+ end
43
+
44
+ def nil_to_hash(_value)
45
+ {}
46
+ end
47
+
48
+ def string_to_hash(value)
49
+ {value.to_sym => {}}
50
+ end
51
+
52
+ def symbol_to_hash(value)
53
+ {value => {}}
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ #
5
+ # Utilities
6
+ #
7
+ module SeregaUtils
8
+ #
9
+ # Utility to make method arguments signature
10
+ #
11
+ class MethodSignature
12
+ SYMBOL_TO_PROC_SIGNATURE_RUBY2 = [[:rest]]
13
+ SYMBOL_TO_PROC_SIGNATURE_RUBY3 = [[:req], [:rest]]
14
+ private_constant :SYMBOL_TO_PROC_SIGNATURE_RUBY2
15
+ private_constant :SYMBOL_TO_PROC_SIGNATURE_RUBY3
16
+
17
+ class << self
18
+ #
19
+ # Generates method arguments signature
20
+ #
21
+ # @param callable [#call] callable object
22
+ # @param pos_limit [Integer] Max count of positional parameters
23
+ # @param keyword_args [Array<Symbol>] List of accepted keyword argument names
24
+ #
25
+ # @return [String] Method signature which consist of number of positional parameters and
26
+ # keyword parameter names joined with undescrore.
27
+ #
28
+ def call(callable, pos_limit:, keyword_args: [])
29
+ params = callable.is_a?(Proc) ? callable.parameters : callable.method(:call).parameters
30
+
31
+ # Procs (but not lambdas) can accept all provided parameters
32
+ return full_signature(pos_limit, keyword_args) if params.empty? && callable.is_a?(Proc) && !callable.lambda?
33
+
34
+ # Return single positional argument for Symbol#to_proc
35
+ return "1" if (params == SYMBOL_TO_PROC_SIGNATURE_RUBY2) || (params == SYMBOL_TO_PROC_SIGNATURE_RUBY3)
36
+
37
+ keyword_args = keyword_args.dup
38
+
39
+ # signature parts
40
+ positional_parameters = 0
41
+ keyword_parameters = []
42
+
43
+ params.each do |type, name|
44
+ case type
45
+ when :req
46
+ positional_parameters += 1
47
+ pos_limit -= 1
48
+ when :opt
49
+ next if pos_limit <= 0
50
+
51
+ positional_parameters += 1
52
+ pos_limit -= 1
53
+ when :rest
54
+ next if pos_limit <= 0
55
+
56
+ positional_parameters += pos_limit
57
+ pos_limit = 0
58
+ when :keyreq
59
+ keyword_parameters << name
60
+ keyword_args.delete(name)
61
+ when :key
62
+ next unless keyword_args.include?(name)
63
+
64
+ keyword_parameters << name
65
+ keyword_args.delete(name)
66
+ when :keyrest
67
+ keyword_parameters.concat(keyword_args)
68
+ keyword_args.clear
69
+ end
70
+ end
71
+
72
+ build_signature_string(positional_parameters, keyword_parameters)
73
+ end
74
+
75
+ private
76
+
77
+ def full_signature(pos_limit, keyword_args)
78
+ build_signature_string(pos_limit, keyword_args)
79
+ end
80
+
81
+ def build_signature_string(positional_parameters, keyword_parameters)
82
+ sorted_signature_parts = keyword_parameters.sort
83
+ sorted_signature_parts.unshift(positional_parameters)
84
+ sorted_signature_parts.join("_")
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaUtils
5
+ #
6
+ # Utility that helps to transform preloads to array of paths
7
+ # It is used to validate manually set `:preload_path` attribute option has one of allowed values.
8
+ # `:preload_path` option can be used to specify where nested preloads must be attached.
9
+ #
10
+ # Example:
11
+ #
12
+ # call({ a: { b: { c: {}, d: {} } }, e: {} })
13
+ #
14
+ # => [
15
+ # [:a],
16
+ # [:a, :b],
17
+ # [:a, :b, :c],
18
+ # [:a, :b, :d],
19
+ # [:e]
20
+ # ]
21
+ class PreloadPaths
22
+ class << self
23
+ #
24
+ # Transforms user provided preloads to array of paths
25
+ #
26
+ # @param preloads [Array,Hash,String,Symbol,nil,false] association(s) to preload
27
+ #
28
+ # @return [Array] transformed preloads
29
+ #
30
+ def call(preloads)
31
+ formatted_preloads = FormatUserPreloads.call(preloads)
32
+ return FROZEN_EMPTY_ARRAY if formatted_preloads.empty?
33
+
34
+ paths(formatted_preloads, [], [])
35
+ end
36
+
37
+ private
38
+
39
+ def paths(formatted_preloads, path, result)
40
+ formatted_preloads.each do |key, nested_preloads|
41
+ path << key
42
+ result << path.dup
43
+
44
+ paths(nested_preloads, path, result)
45
+ path.pop
46
+ end
47
+
48
+ result
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaUtils
5
+ #
6
+ # Finds preloads for provided attributes plan
7
+ #
8
+ class PreloadsConstructor
9
+ class << self
10
+ #
11
+ # Constructs preloads hash for given attributes plan
12
+ #
13
+ # @param plan [Array<SeregaPlanPoint>] Serialization plan
14
+ #
15
+ # @return [Hash]
16
+ #
17
+ def call(plan)
18
+ return FROZEN_EMPTY_HASH unless plan
19
+
20
+ preloads = {}
21
+ append_many(preloads, plan)
22
+ preloads
23
+ end
24
+
25
+ private
26
+
27
+ def append_many(preloads, plan)
28
+ plan.points.each do |point|
29
+ current_preloads = point.attribute.preloads
30
+ next unless current_preloads
31
+
32
+ child_plan = point.child_plan
33
+ current_preloads = SeregaUtils::EnumDeepDup.call(current_preloads) if child_plan
34
+ append_current(preloads, current_preloads)
35
+ next unless child_plan
36
+
37
+ each_child_preloads(preloads, point.preloads_path) do |child_preloads|
38
+ append_many(child_preloads, child_plan)
39
+ end
40
+ end
41
+ end
42
+
43
+ def append_current(preloads, current_preloads)
44
+ merge(preloads, current_preloads) unless current_preloads.empty?
45
+ end
46
+
47
+ def merge(preloads, current_preloads)
48
+ preloads.merge!(current_preloads) do |_key, value_one, value_two|
49
+ merge(value_one, value_two)
50
+ end
51
+ end
52
+
53
+ def each_child_preloads(preloads, preloads_path)
54
+ return yield(preloads) if preloads_path.nil?
55
+
56
+ if preloads_path[0].is_a?(Array)
57
+ preloads_path.each do |path|
58
+ yield dig_fetch(preloads, path)
59
+ end
60
+ else
61
+ yield dig_fetch(preloads, preloads_path)
62
+ end
63
+ end
64
+
65
+ def dig_fetch(preloads, preloads_path)
66
+ return preloads if !preloads_path || preloads_path.empty?
67
+
68
+ preloads_path.each do |path|
69
+ preloads = preloads.fetch(path)
70
+ end
71
+
72
+ preloads
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -13,7 +13,8 @@ class Serega
13
13
  class << self
14
14
  #
15
15
  # Checks block parameter provided with attribute.
16
- # Must have up to two arguments - object and context.
16
+ # Must have up to two arguments - object and context. Context can be
17
+ # also provided as keyword argument :ctx.
17
18
  #
18
19
  # @example without arguments
19
20
  # attribute(:email) { CONSTANT_EMAIL }
@@ -24,6 +25,9 @@ class Serega
24
25
  # @example with two arguments
25
26
  # attribute(:email) { |obj, context| context['is_current'] ? obj.email : nil }
26
27
  #
28
+ # @example with one argument and keyword context
29
+ # attribute(:email) { |obj, ctx:| obj.email if ctx[:show] }
30
+ #
27
31
  # @param block [Proc] Block that returns serialized attribute value
28
32
  #
29
33
  # @raise [SeregaError] SeregaError that block has invalid arguments
@@ -39,14 +43,42 @@ class Serega
39
43
  private
40
44
 
41
45
  def check_block(block)
42
- SeregaValidations::Utils::CheckExtraKeywordArg.call(block, "block")
43
- params_count = SeregaUtils::ParamsCount.call(block, max_count: 2)
46
+ signature = SeregaUtils::MethodSignature.call(block, pos_limit: 2, keyword_args: [:ctx, :batches])
47
+
48
+ raise SeregaError, signature_error unless valid_signature?(signature)
49
+ end
44
50
 
45
- raise SeregaError, block_error if params_count > 2
51
+ def valid_signature?(signature)
52
+ case signature
53
+ when "0" # no parameters
54
+ true
55
+ when "1" # call(object)
56
+ true
57
+ when "1_ctx" # call(object, ctx:)
58
+ true
59
+ when "1_batches" # call(object, batches:)
60
+ true
61
+ when "1_batches_ctx" # call(object, batches:, ctx:)
62
+ true
63
+ when "2" # call(object, context)
64
+ true
65
+ when "2_batches_ctx" # call(object, context, batches:, ctx:) (proc with no params)
66
+ true
67
+ else
68
+ false
69
+ end
46
70
  end
47
71
 
48
- def block_error
49
- "Block can have maximum two parameters (object, context)"
72
+ def signature_error
73
+ <<~ERROR.strip
74
+ Invalid attribute block parameters, valid parameters signatures:
75
+ - () # no parameters
76
+ - (object) # one positional parameter
77
+ - (object, ctx:) # one positional parameter and :ctx keyword
78
+ - (object, batches:) # one positional parameter and :batches keyword
79
+ - (object, ctx:, batches:) # one positional parameter, :ctx, and :batches keywords
80
+ - (object, context) # two positional parameters
81
+ ERROR
50
82
  end
51
83
  end
52
84
  end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaValidations
5
+ module Attribute
6
+ #
7
+ # Attribute `:batch` option validator
8
+ #
9
+ class CheckOptBatch
10
+ class << self
11
+ #
12
+ # Checks attribute :batch option
13
+ #
14
+ # @param opts [Hash] Attribute options
15
+ #
16
+ # @raise [SeregaError] Attribute validation error
17
+ #
18
+ # @return [void]
19
+ #
20
+ def call(serializer_class, opts, block)
21
+ return unless opts.key?(:batch)
22
+
23
+ check_opt_batch(opts, serializer_class)
24
+ check_usage_with_other_params(opts, block)
25
+ end
26
+
27
+ private
28
+
29
+ def check_opt_batch(opts, serializer_class)
30
+ batch_opts = opts[:batch]
31
+ return if batch_opts == true
32
+ return if batch_opts.respond_to?(:call)
33
+
34
+ if batch_opts.is_a?(Symbol) || batch_opts.is_a?(String)
35
+ check_loader_exists?(serializer_class, batch_opts)
36
+ else
37
+ Utils::CheckOptIsHash.call(opts, :batch)
38
+ check_opt_batch_use(serializer_class, batch_opts)
39
+ check_opt_batch_id(batch_opts)
40
+ check_opt_batch_extra_opts(batch_opts)
41
+ end
42
+ end
43
+
44
+ def check_opt_batch_use(serializer_class, batch_opts)
45
+ return unless batch_opts.key?(:use)
46
+
47
+ batch_loader_name = batch_opts[:use]
48
+ return if batch_loader_name.respond_to?(:call)
49
+
50
+ check_loader_exists?(serializer_class, batch_loader_name)
51
+ end
52
+
53
+ def check_opt_batch_id(batch_opts)
54
+ return unless batch_opts.key?(:id)
55
+
56
+ id_method_name = batch_opts[:id]
57
+ return if id_method_name.is_a?(Symbol) || id_method_name.is_a?(String)
58
+
59
+ raise SeregaError, "Invalid batch option `:id` value, it can be a Symbol or a String"
60
+ end
61
+
62
+ def check_loader_exists?(serializer_class, value)
63
+ values = Array(value)
64
+ values.each do |batch_loader_name|
65
+ next if serializer_class.batch_loaders.key?(batch_loader_name.to_sym)
66
+
67
+ raise SeregaError, "Batch loader with name `#{batch_loader_name.inspect}` is not defined"
68
+ end
69
+ end
70
+
71
+ def check_opt_batch_extra_opts(batch_opts)
72
+ Utils::CheckAllowedKeys.call(batch_opts, %i[use id], :batch)
73
+ end
74
+
75
+ def check_usage_with_other_params(opts, block)
76
+ batch = opts[:batch]
77
+ use_id = batch.is_a?(Hash) && batch.key?(:id)
78
+ use_multiple = batch.is_a?(Hash) && (Array(batch[:use]).size > 1)
79
+ value_added = opts.key?(:value) || block
80
+
81
+ if use_multiple && use_id
82
+ raise SeregaError, "Option `batch.id` should not be used with multiple loaders provided in `batch.use`"
83
+ end
84
+
85
+ if use_multiple && !value_added
86
+ raise SeregaError, "Attribute :value option or block should be provided when selecting multiple batch loaders"
87
+ end
88
+
89
+ if use_id && value_added
90
+ raise SeregaError, "Option `batch.id` should not be used when :value or block provided directly"
91
+ end
92
+
93
+ raise SeregaError, "Option :batch can not be used together with option :method" if opts.key?(:method)
94
+ raise SeregaError, "Option :batch can not be used together with option :const" if opts.key?(:const)
95
+ raise SeregaError, "Option :batch can not be used together with option :delegate" if opts.key?(:delegate)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -28,6 +28,7 @@ class Serega
28
28
  def check_usage_with_other_params(opts, block)
29
29
  raise SeregaError, "Option :const can not be used together with option :method" if opts.key?(:method)
30
30
  raise SeregaError, "Option :const can not be used together with option :value" if opts.key?(:value)
31
+ raise SeregaError, "Option :const can not be used together with option :batch" if opts.key?(:batch)
31
32
  raise SeregaError, "Option :const can not be used together with block" if block
32
33
  end
33
34
  end
@@ -60,6 +60,7 @@ class Serega
60
60
  raise SeregaError, "Option :delegate can not be used together with option :method" if opts.key?(:method)
61
61
  raise SeregaError, "Option :delegate can not be used together with option :const" if opts.key?(:const)
62
62
  raise SeregaError, "Option :delegate can not be used together with option :value" if opts.key?(:value)
63
+ raise SeregaError, "Option :delegate can not be used together with option :batch" if opts.key?(:batch)
63
64
  raise SeregaError, "Option :delegate can not be used together with block" if block
64
65
  end
65
66
  end
@@ -29,6 +29,7 @@ class Serega
29
29
  def check_usage_with_other_params(opts, block)
30
30
  raise SeregaError, "Option :method can not be used together with option :const" if opts.key?(:const)
31
31
  raise SeregaError, "Option :method can not be used together with option :value" if opts.key?(:value)
32
+ raise SeregaError, "Option :method can not be used together with option :batch" if opts.key?(:batch)
32
33
  raise SeregaError, "Option :method can not be used together with block" if block
33
34
  end
34
35
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Serega
4
- module SeregaPlugins
5
- module Preloads
4
+ module SeregaValidations
5
+ module Attribute
6
6
  #
7
7
  # Validator for attribute :preload option
8
8
  #
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Serega
4
- module SeregaPlugins
5
- module Preloads
4
+ module SeregaValidations
5
+ module Attribute
6
6
  #
7
7
  # Validator for attribute :preload_path option
8
8
  #
@@ -34,7 +34,7 @@ class Serega
34
34
  end
35
35
 
36
36
  def check_allowed(path, opts)
37
- allowed_paths = PreloadPaths.call(opts[:preload])
37
+ allowed_paths = SeregaUtils::PreloadPaths.call(opts[:preload])
38
38
  check_required_when_many_allowed(path, allowed_paths)
39
39
  check_in_allowed(path, allowed_paths)
40
40
  end
@@ -35,23 +35,49 @@ class Serega
35
35
 
36
36
  def check_value(value)
37
37
  check_value_type(value)
38
-
39
- SeregaValidations::Utils::CheckExtraKeywordArg.call(value, ":value option")
40
- params_count = SeregaUtils::ParamsCount.call(value, max_count: 2)
41
-
42
- raise SeregaError, params_count_error if params_count > 2
38
+ signature = SeregaUtils::MethodSignature.call(value, pos_limit: 2, keyword_args: %i[ctx batches])
39
+ raise SeregaError, signature_error unless valid_signature?(signature)
43
40
  end
44
41
 
45
42
  def check_value_type(value)
46
43
  raise SeregaError, type_error if !value.is_a?(Proc) && !value.respond_to?(:call)
47
44
  end
48
45
 
49
- def type_error
50
- "Option :value value must be a Proc or respond to #call"
46
+ def valid_signature?(signature)
47
+ case signature
48
+ when "0" # no parameters
49
+ true
50
+ when "1" # call(object)
51
+ true
52
+ when "1_ctx" # call(object, ctx:)
53
+ true
54
+ when "1_batches" # call(object, batches:)
55
+ true
56
+ when "1_batches_ctx" # call(object, ctx:, batches:)
57
+ true
58
+ when "2" # call(object, context)
59
+ true
60
+ when "2_batches_ctx" # call(object, context, ctx:, batches:) (proc with no params)
61
+ true
62
+ else
63
+ false
64
+ end
51
65
  end
52
66
 
53
- def params_count_error
54
- "Option :value value can have maximum 2 parameters (object, context)"
67
+ def signature_error
68
+ <<~ERROR.strip
69
+ Invalid attribute :value option parameters, valid parameters signatures:
70
+ - () # no parameters
71
+ - (object) # one positional parameter
72
+ - (object, ctx:) # one positional parameter and :ctx keyword
73
+ - (object, batches:) # one positional parameter and :batches keyword
74
+ - (object, ctx:, batches:) # one positional parameter, :ctx, and :batches keywords
75
+ - (object, context) # two positional parameters
76
+ ERROR
77
+ end
78
+
79
+ def type_error
80
+ "Option :value value must be a Proc or respond to #call"
55
81
  end
56
82
  end
57
83
  end
@@ -53,11 +53,9 @@ class Serega
53
53
  end
54
54
 
55
55
  # Patched in:
56
- # - plugin :batch (checks :batch option)
57
56
  # - plugin :context_metadata (checks context metadata option which is :meta by default)
58
57
  # - plugin :formatters (checks :format option)
59
58
  # - plugin :if (checks :if, :if_value, :unless, :unless_value options)
60
- # - plugin :preloads (checks :preload option)
61
59
  def check_opts
62
60
  Utils::CheckAllowedKeys.call(opts, allowed_opts_keys, :attribute)
63
61
 
@@ -66,8 +64,11 @@ class Serega
66
64
  Attribute::CheckOptHide.call(opts)
67
65
  Attribute::CheckOptMethod.call(opts, block)
68
66
  Attribute::CheckOptMany.call(opts)
67
+ Attribute::CheckOptPreload.call(opts)
68
+ Attribute::CheckOptPreloadPath.call(opts)
69
69
  Attribute::CheckOptSerializer.call(opts)
70
70
  Attribute::CheckOptValue.call(opts, block)
71
+ Attribute::CheckOptBatch.call(self.class.serializer_class, opts, block)
71
72
  end
72
73
 
73
74
  def check_block
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaValidations
5
+ #
6
+ # Batch loader parameters validators
7
+ #
8
+ class CheckBatchLoaderParams
9
+ #
10
+ # batch_loader parameters validation instance methods
11
+ #
12
+ module InstanceMethods
13
+ # @return [Symbol] validated batch_loader name
14
+ attr_reader :name
15
+
16
+ # @return [nil, #call] validated batch_loader value or block
17
+ attr_reader :batch_loader
18
+
19
+ # Instantiates batch loader params checker
20
+ # @param name [Symbol, String] Batch loader name
21
+ # @param batch_loader [Proc, #call] Batch loader
22
+ def initialize(name, batch_loader)
23
+ @name = name
24
+ @batch_loader = batch_loader
25
+ end
26
+
27
+ #
28
+ # Checks batch loader parameters
29
+ #
30
+ # @raise [SeregaError] SeregaError that batch loader has invalid arguments
31
+ #
32
+ # @return [void]
33
+ #
34
+ def validate
35
+ check_name
36
+ check_loader
37
+ end
38
+
39
+ private
40
+
41
+ def check_name
42
+ raise SeregaError, name_type_error if !name.is_a?(Symbol) && !name.is_a?(String)
43
+ end
44
+
45
+ def check_loader
46
+ check_batch_loader_type
47
+ check_batch_loader_args
48
+ end
49
+
50
+ def check_batch_loader_type
51
+ raise SeregaError, type_error if !batch_loader.is_a?(Proc) && !batch_loader.respond_to?(:call)
52
+ end
53
+
54
+ def check_batch_loader_args
55
+ signature = SeregaUtils::MethodSignature.call(batch_loader, pos_limit: 2, keyword_args: [:ctx])
56
+ raise SeregaError, arguments_error unless %w[1 2 1_ctx].include?(signature)
57
+ end
58
+
59
+ def name_type_error
60
+ "Batch loader name must be a Symbol or String"
61
+ end
62
+
63
+ def type_error
64
+ "Batch loader value must be a Proc or respond to #call"
65
+ end
66
+
67
+ def arguments_error
68
+ <<~ERR.strip
69
+ Batch loader arguments should have one of this signatures:
70
+ - (objects) # one argument
71
+ - (objects, :ctx) # one argument and one :ctx keyword argument
72
+ ERR
73
+ end
74
+ end
75
+
76
+ include InstanceMethods
77
+ extend Serega::SeregaHelpers::SerializerClassHelper
78
+ end
79
+ end
80
+ end