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
@@ -1,79 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Serega
|
4
|
-
module SeregaPlugins
|
5
|
-
module Preloads
|
6
|
-
#
|
7
|
-
# Finds preloads for provided attributes plan
|
8
|
-
#
|
9
|
-
class PreloadsConstructor
|
10
|
-
class << self
|
11
|
-
#
|
12
|
-
# Constructs preloads hash for given attributes plan
|
13
|
-
#
|
14
|
-
# @param plan [Array<SeregaPlanPoint>] Serialization plan
|
15
|
-
#
|
16
|
-
# @return [Hash]
|
17
|
-
#
|
18
|
-
def call(plan)
|
19
|
-
return FROZEN_EMPTY_HASH unless plan
|
20
|
-
|
21
|
-
preloads = {}
|
22
|
-
append_many(preloads, plan)
|
23
|
-
preloads
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def append_many(preloads, plan)
|
29
|
-
plan.points.each do |point|
|
30
|
-
current_preloads = point.attribute.preloads
|
31
|
-
next unless current_preloads
|
32
|
-
|
33
|
-
child_plan = point.child_plan
|
34
|
-
current_preloads = SeregaUtils::EnumDeepDup.call(current_preloads) if child_plan
|
35
|
-
append_current(preloads, current_preloads)
|
36
|
-
next unless child_plan
|
37
|
-
|
38
|
-
each_child_preloads(preloads, point.preloads_path) do |child_preloads|
|
39
|
-
append_many(child_preloads, child_plan)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def append_current(preloads, current_preloads)
|
45
|
-
merge(preloads, current_preloads) unless current_preloads.empty?
|
46
|
-
end
|
47
|
-
|
48
|
-
def merge(preloads, current_preloads)
|
49
|
-
preloads.merge!(current_preloads) do |_key, value_one, value_two|
|
50
|
-
merge(value_one, value_two)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def each_child_preloads(preloads, preloads_path)
|
55
|
-
return yield(preloads) if preloads_path.nil?
|
56
|
-
|
57
|
-
if preloads_path[0].is_a?(Array)
|
58
|
-
preloads_path.each do |path|
|
59
|
-
yield dig_fetch(preloads, path)
|
60
|
-
end
|
61
|
-
else
|
62
|
-
yield dig_fetch(preloads, preloads_path)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def dig_fetch(preloads, preloads_path)
|
67
|
-
return preloads if !preloads_path || preloads_path.empty?
|
68
|
-
|
69
|
-
preloads_path.each do |path|
|
70
|
-
preloads = preloads.fetch(path)
|
71
|
-
end
|
72
|
-
|
73
|
-
preloads
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
@@ -1,162 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Serega
|
4
|
-
module SeregaPlugins
|
5
|
-
#
|
6
|
-
# Plugin `:preloads`
|
7
|
-
#
|
8
|
-
# Allows to define `:preloads` to attributes and then allows to merge preloads
|
9
|
-
# from serialized attributes and return single associations hash.
|
10
|
-
#
|
11
|
-
# Plugin accepts options:
|
12
|
-
# - `auto_preload_attributes_with_delegate` - default false
|
13
|
-
# - `auto_preload_attributes_with_serializer` - default false
|
14
|
-
# - `auto_hide_attributes_with_preload` - default false
|
15
|
-
#
|
16
|
-
# This options are very handy if you want to forget about finding preloads manually.
|
17
|
-
#
|
18
|
-
# Preloads can be disabled with `preload: false` attribute option.
|
19
|
-
# Also automatically added preloads can be overwritten with manually specified `preload: :another_value`.
|
20
|
-
#
|
21
|
-
# Some examples, **please read comments in the code below**
|
22
|
-
#
|
23
|
-
# @example
|
24
|
-
# class AppSerializer < Serega
|
25
|
-
# plugin :preloads,
|
26
|
-
# auto_preload_attributes_with_delegate: true,
|
27
|
-
# auto_preload_attributes_with_serializer: true,
|
28
|
-
# auto_hide_attributes_with_preload: true
|
29
|
-
# end
|
30
|
-
#
|
31
|
-
# class UserSerializer < AppSerializer
|
32
|
-
# # No preloads
|
33
|
-
# attribute :username
|
34
|
-
#
|
35
|
-
# # Specify `preload: :user_stats` manually
|
36
|
-
# attribute :followers_count, preload: :user_stats, value: proc { |user| user.user_stats.followers_count }
|
37
|
-
#
|
38
|
-
# # Automatically `preloads: :user_stats` as `auto_preload_attributes_with_delegate` option is true
|
39
|
-
# attribute :comments_count, delegate: { to: :user_stats }
|
40
|
-
#
|
41
|
-
# # Automatically `preloads: :albums` as `auto_preload_attributes_with_serializer` option is true
|
42
|
-
# attribute :albums, serializer: 'AlbumSerializer'
|
43
|
-
# end
|
44
|
-
#
|
45
|
-
# class AlbumSerializer < AppSerializer
|
46
|
-
# attribute :images_count, delegate: { to: :album_stats }
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# # By default preloads are empty, as we specify `auto_hide_attributes_with_preload = true`,
|
50
|
-
# # and attributes with preloads will be not serialized
|
51
|
-
# UserSerializer.new.preloads # => {}
|
52
|
-
# UserSerializer.new.to_h(OpenStruct.new(username: 'foo')) # => {:username=>"foo"}
|
53
|
-
#
|
54
|
-
# UserSerializer.new(with: :followers_count).preloads # => {:user_stats=>{}}
|
55
|
-
# UserSerializer.new(with: %i[followers_count comments_count]).preloads # => {:user_stats=>{}}
|
56
|
-
# UserSerializer.new(with: [:followers_count, :comments_count, { albums: :images_count }]).preloads # => {:user_stats=>{}, :albums=>{:album_stats=>{}}}
|
57
|
-
#
|
58
|
-
|
59
|
-
module Preloads
|
60
|
-
DEFAULT_CONFIG = {
|
61
|
-
auto_preload_attributes_with_delegate: false,
|
62
|
-
auto_preload_attributes_with_serializer: false,
|
63
|
-
auto_hide_attributes_with_preload: false
|
64
|
-
}.freeze
|
65
|
-
|
66
|
-
private_constant :DEFAULT_CONFIG
|
67
|
-
|
68
|
-
# @return [Symbol] Plugin name
|
69
|
-
def self.plugin_name
|
70
|
-
:preloads
|
71
|
-
end
|
72
|
-
|
73
|
-
# Checks requirements to load plugin
|
74
|
-
#
|
75
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
76
|
-
# @param opts [Hash] plugin options
|
77
|
-
#
|
78
|
-
# @return [void]
|
79
|
-
#
|
80
|
-
def self.before_load_plugin(serializer_class, **opts)
|
81
|
-
allowed_keys = DEFAULT_CONFIG.keys
|
82
|
-
opts.each_key do |key|
|
83
|
-
next if allowed_keys.include?(key)
|
84
|
-
|
85
|
-
raise SeregaError,
|
86
|
-
"Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
|
87
|
-
" - :auto_preload_attributes_with_delegate [Boolean] - Automatically adds `preload: <delegate_to>` option to attributes with :delegate option specified\n" \
|
88
|
-
" - :auto_preload_attributes_with_serializer [Boolean] - Automatically adds `preload: <attribute_name>` option to attributes with :serializer option specified\n" \
|
89
|
-
" - :auto_hide_attributes_with_preload [Boolean] - Automatically adds `hide: true` option to attributes with :preload option (specified manually or added automatically)"
|
90
|
-
end
|
91
|
-
|
92
|
-
if serializer_class.plugin_used?(:batch)
|
93
|
-
raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded before the :batch plugin"
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
#
|
98
|
-
# Applies plugin code to specific serializer
|
99
|
-
#
|
100
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
101
|
-
# @param _opts [Hash] Plugin options
|
102
|
-
#
|
103
|
-
# @return [void]
|
104
|
-
#
|
105
|
-
def self.load_plugin(serializer_class, **_opts)
|
106
|
-
require_relative "lib/format_user_preloads"
|
107
|
-
require_relative "lib/modules/attribute"
|
108
|
-
require_relative "lib/modules/attribute_normalizer"
|
109
|
-
require_relative "lib/modules/check_attribute_params"
|
110
|
-
require_relative "lib/modules/config"
|
111
|
-
require_relative "lib/modules/plan_point"
|
112
|
-
require_relative "lib/preload_paths"
|
113
|
-
require_relative "lib/preloads_config"
|
114
|
-
require_relative "lib/preloads_constructor"
|
115
|
-
require_relative "validations/check_opt_preload"
|
116
|
-
require_relative "validations/check_opt_preload_path"
|
117
|
-
|
118
|
-
serializer_class.include(InstanceMethods)
|
119
|
-
serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
|
120
|
-
serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
|
121
|
-
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
|
122
|
-
serializer_class::SeregaPlanPoint.include(PlanPointInstanceMethods)
|
123
|
-
|
124
|
-
serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
|
125
|
-
end
|
126
|
-
|
127
|
-
#
|
128
|
-
# Adds config options and runs other callbacks after plugin was loaded
|
129
|
-
#
|
130
|
-
# @param serializer_class [Class<Serega>] Current serializer class
|
131
|
-
# @param opts [Hash] Plugin options
|
132
|
-
#
|
133
|
-
# @return [void]
|
134
|
-
#
|
135
|
-
def self.after_load_plugin(serializer_class, **opts)
|
136
|
-
config = serializer_class.config
|
137
|
-
config.attribute_keys << :preload << :preload_path
|
138
|
-
|
139
|
-
preloads_opts = DEFAULT_CONFIG.merge(opts.slice(*DEFAULT_CONFIG.keys))
|
140
|
-
config.opts[:preloads] = {}
|
141
|
-
preloads_config = config.preloads
|
142
|
-
preloads_config.auto_preload_attributes_with_delegate = preloads_opts[:auto_preload_attributes_with_delegate]
|
143
|
-
preloads_config.auto_preload_attributes_with_serializer = preloads_opts[:auto_preload_attributes_with_serializer]
|
144
|
-
preloads_config.auto_hide_attributes_with_preload = preloads_opts[:auto_hide_attributes_with_preload]
|
145
|
-
end
|
146
|
-
|
147
|
-
#
|
148
|
-
# Serega additional/patched instance methods
|
149
|
-
#
|
150
|
-
# @see Serega
|
151
|
-
#
|
152
|
-
module InstanceMethods
|
153
|
-
# @return [Hash] merged preloads of all serialized attributes
|
154
|
-
def preloads
|
155
|
-
@preloads ||= PreloadsConstructor.call(plan)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
register_plugin(Preloads.plugin_name, Preloads)
|
161
|
-
end
|
162
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Serega
|
4
|
-
#
|
5
|
-
# Utilities
|
6
|
-
#
|
7
|
-
module SeregaUtils
|
8
|
-
#
|
9
|
-
# Utility to count regular parameters of callable object
|
10
|
-
#
|
11
|
-
class ParamsCount
|
12
|
-
NO_NAMED_REST_PARAM = [:rest].freeze
|
13
|
-
private_constant :NO_NAMED_REST_PARAM
|
14
|
-
|
15
|
-
class << self
|
16
|
-
#
|
17
|
-
# Count parameters for callable object
|
18
|
-
#
|
19
|
-
# @param object [#call] callable object
|
20
|
-
#
|
21
|
-
# @return [Integer] count of regular parameters
|
22
|
-
#
|
23
|
-
def call(object, max_count:)
|
24
|
-
# Procs (but not lambdas) can accept all provided parameters
|
25
|
-
parameters = object.is_a?(Proc) ? object.parameters : object.method(:call).parameters
|
26
|
-
return 1 if parameters[0] == NO_NAMED_REST_PARAM
|
27
|
-
return max_count if object.is_a?(Proc) && !object.lambda?
|
28
|
-
|
29
|
-
count = 0
|
30
|
-
|
31
|
-
# If all we have is no-name *rest parameters, then we assume we need to provide
|
32
|
-
# 1 argument. It is now always correct, but in serialization context it's most common that
|
33
|
-
# only one argument is needed.
|
34
|
-
parameters.each do |parameter|
|
35
|
-
next if parameter == NO_NAMED_REST_PARAM # Workaround for procs like :odd?.to_proc
|
36
|
-
param_type = parameter[0]
|
37
|
-
|
38
|
-
case param_type
|
39
|
-
when :req then count += 1
|
40
|
-
when :opt then count += 1 if count < max_count
|
41
|
-
when :rest then count += max_count - count if max_count > count
|
42
|
-
end # else :opt, :keyreq, :key, :keyrest, :block - do nothing
|
43
|
-
end
|
44
|
-
|
45
|
-
count
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Serega
|
4
|
-
module SeregaValidations
|
5
|
-
#
|
6
|
-
# Validations Utilities
|
7
|
-
#
|
8
|
-
module Utils
|
9
|
-
#
|
10
|
-
# Utility to check that callable object has no required keyword arguments
|
11
|
-
#
|
12
|
-
class CheckExtraKeywordArg
|
13
|
-
# Checks hash keys are allowed
|
14
|
-
#
|
15
|
-
# @param callable [#call] Callable object
|
16
|
-
# @param callable_description [Symbol] Callable object description
|
17
|
-
#
|
18
|
-
# @raise [Serega::SeregaError] error if callable accepts required keyword argument
|
19
|
-
#
|
20
|
-
# @return [void]
|
21
|
-
def self.call(callable, callable_description)
|
22
|
-
parameters = callable.is_a?(Proc) ? callable.parameters : callable.method(:call).parameters
|
23
|
-
|
24
|
-
parameters.each do |parameter|
|
25
|
-
next unless parameter[0] == :keyreq
|
26
|
-
|
27
|
-
raise Serega::SeregaError, "Invalid #{callable_description}. It should not have any required keyword arguments"
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|