serega 0.20.1 → 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.
- checksums.yaml +4 -4
- data/README.md +113 -226
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +31 -3
- data/lib/serega/attribute_normalizer.rb +143 -42
- data/lib/serega/batch/attribute_loader.rb +70 -0
- data/lib/serega/batch/attribute_loaders.rb +59 -0
- data/lib/serega/batch/auto_resolver.rb +24 -0
- data/lib/serega/batch/auto_resolver_factory.rb +48 -0
- data/lib/serega/batch/loader.rb +67 -0
- data/lib/serega/config.rb +59 -1
- data/lib/serega/object_serializer.rb +11 -9
- data/lib/serega/plan.rb +16 -0
- data/lib/serega/plan_point.rb +31 -5
- data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +42 -23
- data/lib/serega/plugins/activerecord_preloads/lib/active_record_objects.rb +31 -0
- data/lib/serega/plugins/camel_case/camel_case.rb +1 -1
- data/lib/serega/plugins/formatters/formatters.rb +79 -22
- data/lib/serega/plugins/if/if.rb +41 -22
- data/lib/serega/plugins/if/validations/check_opt_if.rb +27 -6
- data/lib/serega/plugins/if/validations/check_opt_if_value.rb +27 -6
- data/lib/serega/plugins/if/validations/check_opt_unless.rb +27 -6
- data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +27 -6
- data/lib/serega/plugins/metadata/meta_attribute.rb +9 -10
- data/lib/serega/plugins/metadata/validations/check_block.rb +2 -4
- data/lib/serega/plugins/metadata/validations/check_opt_value.rb +2 -3
- data/lib/serega/plugins/presenter/presenter.rb +1 -1
- data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +43 -30
- data/lib/serega/utils/format_user_preloads.rb +58 -0
- data/lib/serega/utils/method_signature.rb +89 -0
- data/lib/serega/utils/preload_paths.rb +53 -0
- data/lib/serega/utils/preloads_constructor.rb +77 -0
- data/lib/serega/validations/attribute/check_block.rb +38 -6
- data/lib/serega/validations/attribute/check_opt_batch.rb +101 -0
- data/lib/serega/validations/attribute/check_opt_const.rb +1 -0
- data/lib/serega/validations/attribute/check_opt_delegate.rb +1 -0
- data/lib/serega/validations/attribute/check_opt_method.rb +1 -0
- data/lib/serega/{plugins/preloads/validations → validations/attribute}/check_opt_preload.rb +2 -2
- data/lib/serega/{plugins/preloads/validations → validations/attribute}/check_opt_preload_path.rb +3 -3
- data/lib/serega/validations/attribute/check_opt_value.rb +35 -9
- data/lib/serega/validations/check_attribute_params.rb +3 -2
- data/lib/serega/validations/check_batch_loader_params.rb +80 -0
- data/lib/serega/validations/initiate/check_modifiers.rb +1 -1
- data/lib/serega.rb +110 -11
- metadata +17 -37
- data/lib/serega/plugins/batch/batch.rb +0 -168
- data/lib/serega/plugins/batch/lib/batch_config.rb +0 -77
- data/lib/serega/plugins/batch/lib/loader.rb +0 -113
- data/lib/serega/plugins/batch/lib/loaders.rb +0 -45
- data/lib/serega/plugins/batch/lib/modules/attribute.rb +0 -26
- data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +0 -78
- data/lib/serega/plugins/batch/lib/modules/check_attribute_params.rb +0 -22
- data/lib/serega/plugins/batch/lib/modules/config.rb +0 -23
- data/lib/serega/plugins/batch/lib/modules/object_serializer.rb +0 -46
- data/lib/serega/plugins/batch/lib/modules/plan_point.rb +0 -22
- data/lib/serega/plugins/batch/lib/plugins_extensions/activerecord_preloads.rb +0 -43
- data/lib/serega/plugins/batch/lib/plugins_extensions/formatters.rb +0 -54
- data/lib/serega/plugins/batch/lib/plugins_extensions/if.rb +0 -31
- data/lib/serega/plugins/batch/lib/plugins_extensions/preloads.rb +0 -34
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_id_method.rb +0 -43
- data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +0 -59
- data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +0 -62
- data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +0 -60
- data/lib/serega/plugins/preloads/lib/modules/attribute.rb +0 -28
- data/lib/serega/plugins/preloads/lib/modules/attribute_normalizer.rb +0 -99
- data/lib/serega/plugins/preloads/lib/modules/check_attribute_params.rb +0 -22
- data/lib/serega/plugins/preloads/lib/modules/config.rb +0 -19
- data/lib/serega/plugins/preloads/lib/modules/plan_point.rb +0 -41
- data/lib/serega/plugins/preloads/lib/preload_paths.rb +0 -53
- data/lib/serega/plugins/preloads/lib/preloads_config.rb +0 -62
- data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +0 -79
- data/lib/serega/plugins/preloads/preloads.rb +0 -162
- data/lib/serega/utils/params_count.rb +0 -50
- data/lib/serega/validations/utils/check_extra_keyword_arg.rb +0 -33
@@ -40,6 +40,8 @@ class Serega
|
|
40
40
|
@opts = SeregaUtils::EnumDeepDup.call(opts)
|
41
41
|
@block = block
|
42
42
|
@normalized_block = normalize_block(opts[:value], opts[:const], block)
|
43
|
+
@normalized_block_signature =
|
44
|
+
SeregaUtils::MethodSignature.call(@normalized_block, pos_limit: 2, keyword_args: [])
|
43
45
|
end
|
44
46
|
|
45
47
|
#
|
@@ -51,7 +53,11 @@ class Serega
|
|
51
53
|
# @return [Object] Serialized meta attribute value
|
52
54
|
#
|
53
55
|
def value(object, context)
|
54
|
-
|
56
|
+
case normalized_block_signature
|
57
|
+
when "0" then normalized_block.call
|
58
|
+
when "1" then normalized_block.call(object)
|
59
|
+
else normalized_block.call(object, context)
|
60
|
+
end
|
55
61
|
end
|
56
62
|
|
57
63
|
def hide?(value)
|
@@ -60,19 +66,12 @@ class Serega
|
|
60
66
|
|
61
67
|
private
|
62
68
|
|
63
|
-
attr_reader :normalized_block
|
69
|
+
attr_reader :normalized_block, :normalized_block_signature
|
64
70
|
|
65
71
|
def normalize_block(value, const, block)
|
66
72
|
return proc { const } if const
|
67
73
|
|
68
|
-
|
69
|
-
params_count = SeregaUtils::ParamsCount.call(callable, max_count: 2)
|
70
|
-
|
71
|
-
case params_count
|
72
|
-
when 0 then proc { callable.call }
|
73
|
-
when 1 then proc { |obj| callable.call(obj) }
|
74
|
-
else callable
|
75
|
-
end
|
74
|
+
value || block
|
76
75
|
end
|
77
76
|
|
78
77
|
def check(path, opts, block)
|
@@ -29,10 +29,8 @@ class Serega
|
|
29
29
|
# @return [void]
|
30
30
|
#
|
31
31
|
def call(block)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
raise SeregaError, block_error if params_count > 2
|
32
|
+
signature = SeregaUtils::MethodSignature.call(block, pos_limit: 2, keyword_args: [])
|
33
|
+
raise SeregaError, block_error unless %w[0 1 2].include?(signature)
|
36
34
|
end
|
37
35
|
|
38
36
|
private
|
@@ -36,10 +36,9 @@ class Serega
|
|
36
36
|
def check_value(value)
|
37
37
|
check_value_type(value)
|
38
38
|
|
39
|
-
|
40
|
-
params_count = SeregaUtils::ParamsCount.call(value, max_count: 2)
|
39
|
+
signature = SeregaUtils::MethodSignature.call(value, pos_limit: 2, keyword_args: [])
|
41
40
|
|
42
|
-
raise SeregaError, params_count_error
|
41
|
+
raise SeregaError, params_count_error unless %w[0 1 2].include?(signature)
|
43
42
|
end
|
44
43
|
|
45
44
|
def check_value_type(value)
|
@@ -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
|
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
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "stringio"
|
4
|
-
|
5
3
|
class Serega
|
6
4
|
module SeregaPlugins
|
7
5
|
#
|
@@ -25,6 +23,14 @@ class Serega
|
|
25
23
|
# Modifiers parser
|
26
24
|
#
|
27
25
|
class ParseStringModifiers
|
26
|
+
COMMA = ","
|
27
|
+
COMMA_CODEPOINT = COMMA.ord
|
28
|
+
LPAREN = "("
|
29
|
+
LPAREN_CODEPOINT = LPAREN.ord
|
30
|
+
RPAREN = ")"
|
31
|
+
RPAREN_CODEPOINT = RPAREN.ord
|
32
|
+
private_constant :COMMA, :LPAREN, :RPAREN, :COMMA_CODEPOINT, :LPAREN_CODEPOINT, :RPAREN_CODEPOINT
|
33
|
+
|
28
34
|
class << self
|
29
35
|
#
|
30
36
|
# Parses string modifiers
|
@@ -40,45 +46,52 @@ class Serega
|
|
40
46
|
# parse("user,comments") => { user: {}, comments: {} }
|
41
47
|
# parse("user(comments(text))") => { user: { comments: { text: {} } } }
|
42
48
|
def parse(fields)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
path_stack = nil
|
47
|
-
fields = StringIO.new(fields)
|
49
|
+
result = {}
|
50
|
+
attribute_storage = result
|
51
|
+
path_stack = (fields.include?(LPAREN) || fields.include?(RPAREN)) ? [] : nil
|
48
52
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
when
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
53
|
+
start_index = 0
|
54
|
+
end_index = 0
|
55
|
+
fields.each_codepoint do |codepoint|
|
56
|
+
case codepoint
|
57
|
+
when COMMA_CODEPOINT
|
58
|
+
attribute = extract_attribute(fields, start_index, end_index)
|
59
|
+
add_attribute(attribute_storage, attribute, FROZEN_EMPTY_HASH) if attribute
|
60
|
+
start_index = end_index + 1
|
61
|
+
when LPAREN_CODEPOINT
|
62
|
+
attribute = extract_attribute(fields, start_index, end_index)
|
63
|
+
if attribute
|
64
|
+
attribute_storage = add_attribute(attribute_storage, attribute, {})
|
65
|
+
path_stack.push(attribute)
|
66
|
+
end
|
67
|
+
start_index = end_index + 1
|
68
|
+
when RPAREN_CODEPOINT
|
69
|
+
attribute = extract_attribute(fields, start_index, end_index)
|
70
|
+
add_attribute(attribute_storage, attribute, FROZEN_EMPTY_HASH) if attribute
|
71
|
+
path_stack.pop
|
72
|
+
attribute_storage = dig?(result, path_stack)
|
73
|
+
start_index = end_index + 1
|
61
74
|
end
|
75
|
+
|
76
|
+
end_index += 1
|
62
77
|
end
|
63
78
|
|
64
|
-
|
79
|
+
attribute = extract_attribute(fields, start_index, end_index)
|
80
|
+
add_attribute(attribute_storage, attribute, FROZEN_EMPTY_HASH) if attribute
|
65
81
|
|
66
|
-
|
82
|
+
result
|
67
83
|
end
|
68
84
|
|
69
85
|
private
|
70
86
|
|
71
|
-
def
|
87
|
+
def extract_attribute(fields, start_index, end_index)
|
88
|
+
attribute = fields[start_index, end_index - start_index]
|
72
89
|
attribute.strip!
|
73
|
-
|
74
|
-
|
75
|
-
name = attribute.to_sym
|
76
|
-
attribute.clear
|
77
|
-
|
78
|
-
current_attrs = dig?(res, path_stack)
|
79
|
-
current_attrs[name] = nested_attributes
|
90
|
+
attribute.empty? ? nil : attribute.to_sym
|
91
|
+
end
|
80
92
|
|
81
|
-
|
93
|
+
def add_attribute(storage, attribute, nested_attributes = FROZEN_EMPTY_HASH)
|
94
|
+
storage[attribute] = nested_attributes
|
82
95
|
end
|
83
96
|
|
84
97
|
def dig?(hash, path)
|
@@ -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
|
-
|
43
|
-
|
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
|
-
|
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
|
49
|
-
|
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
|