serega 0.42.0 → 0.50.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +124 -1207
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +186 -131
  5. data/lib/serega/attribute_normalizer.rb +164 -345
  6. data/lib/serega/attribute_normalizers/batch.rb +45 -0
  7. data/lib/serega/attribute_normalizers/block.rb +81 -0
  8. data/lib/serega/attribute_normalizers/delegate.rb +34 -0
  9. data/lib/serega/attribute_normalizers/hash_access.rb +37 -0
  10. data/lib/serega/attribute_normalizers/preloads.rb +46 -0
  11. data/lib/serega/attribute_value_resolvers/batch.rb +5 -54
  12. data/lib/serega/attribute_value_resolvers/const.rb +2 -24
  13. data/lib/serega/attribute_value_resolvers/delegate.rb +17 -28
  14. data/lib/serega/attribute_value_resolvers/hash_access.rb +34 -52
  15. data/lib/serega/attribute_value_resolvers/keyword.rb +10 -25
  16. data/lib/serega/attribute_values.rb +46 -0
  17. data/lib/serega/batch_loader.rb +37 -0
  18. data/lib/serega/conditions.rb +56 -0
  19. data/lib/serega/config.rb +105 -144
  20. data/lib/serega/engine/pulls.rb +64 -0
  21. data/lib/serega/engine/run.rb +106 -0
  22. data/lib/serega/errors.rb +36 -8
  23. data/lib/serega/formatter.rb +98 -0
  24. data/lib/serega/plan/cache.rb +96 -0
  25. data/lib/serega/plan/per_ractor_cache.rb +23 -0
  26. data/lib/serega/plan.rb +101 -128
  27. data/lib/serega/plan_point.rb +44 -151
  28. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +13 -80
  29. data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +8 -75
  30. data/lib/serega/plugins/context_metadata/context_metadata.rb +40 -123
  31. data/lib/serega/plugins/metadata/check_params.rb +80 -0
  32. data/lib/serega/plugins/metadata/meta_attribute.rb +40 -79
  33. data/lib/serega/plugins/metadata/metadata.rb +40 -168
  34. data/lib/serega/plugins/root/root.rb +91 -235
  35. data/lib/serega/plugins.rb +16 -49
  36. data/lib/serega/presenter.rb +61 -19
  37. data/lib/serega/result_builder.rb +68 -0
  38. data/lib/serega/result_code.rb +203 -0
  39. data/lib/serega/shareable.rb +52 -0
  40. data/lib/serega/source_group.rb +187 -0
  41. data/lib/serega/utils/collection_detector.rb +19 -13
  42. data/lib/serega/utils/deep_merge.rb +26 -0
  43. data/lib/serega/utils/enum_deep_dup.rb +10 -16
  44. data/lib/serega/utils/enum_deep_freeze.rb +8 -14
  45. data/lib/serega/utils/method_signature.rb +6 -15
  46. data/lib/serega/utils/parse_string_modifiers.rb +63 -0
  47. data/lib/serega/utils/ractor_local.rb +36 -0
  48. data/lib/serega/utils/serialized_attribute_error.rb +60 -20
  49. data/lib/serega/utils/to_hash.rb +5 -26
  50. data/lib/serega/validations/attribute/check_block.rb +3 -15
  51. data/lib/serega/validations/attribute/check_name.rb +4 -15
  52. data/lib/serega/validations/attribute/check_opt_base_serializer.rb +3 -15
  53. data/lib/serega/validations/attribute/check_opt_batch.rb +26 -56
  54. data/lib/serega/validations/attribute/check_opt_condition.rb +55 -0
  55. data/lib/serega/validations/attribute/check_opt_delegate.rb +10 -63
  56. data/lib/serega/validations/attribute/check_opt_format.rb +36 -0
  57. data/lib/serega/validations/attribute/check_opt_hash_access.rb +11 -59
  58. data/lib/serega/validations/attribute/check_opt_hide.rb +3 -14
  59. data/lib/serega/validations/attribute/check_opt_many.rb +18 -20
  60. data/lib/serega/validations/attribute/check_opt_method.rb +3 -25
  61. data/lib/serega/validations/attribute/check_opt_preload.rb +3 -26
  62. data/lib/serega/validations/attribute/check_opt_serializer.rb +4 -18
  63. data/lib/serega/validations/attribute/check_opt_value.rb +3 -21
  64. data/lib/serega/validations/check_attribute_params.rb +92 -71
  65. data/lib/serega/validations/check_batch_loader_params.rb +43 -62
  66. data/lib/serega/validations/check_formatter.rb +80 -0
  67. data/lib/serega/validations/check_serialize_keywords.rb +23 -0
  68. data/lib/serega/validations/initiate/check_modifiers.rb +14 -20
  69. data/lib/serega/validations/utils/check_allowed_keys.rb +3 -13
  70. data/lib/serega/validations/utils/check_hash_access_value.rb +44 -0
  71. data/lib/serega/validations/utils/check_opt_is_bool.rb +3 -13
  72. data/lib/serega/validations/utils/check_opt_is_hash.rb +3 -13
  73. data/lib/serega/validations/utils/check_opt_is_string_or_symbol.rb +3 -13
  74. data/lib/serega/version.rb +1 -4
  75. data/lib/serega.rb +459 -388
  76. data/sig/serega/plugins.rbs +49 -0
  77. data/sig/serega.rbs +142 -0
  78. metadata +42 -38
  79. data/lib/serega/data_builder.rb +0 -54
  80. data/lib/serega/engine/level.rb +0 -59
  81. data/lib/serega/engine/level_queue.rb +0 -56
  82. data/lib/serega/engine/loader.rb +0 -70
  83. data/lib/serega/helpers/serializer_class_helper.rb +0 -19
  84. data/lib/serega/object_serializer.rb +0 -125
  85. data/lib/serega/plugins/camel_case/camel_case.rb +0 -213
  86. data/lib/serega/plugins/depth_limit/depth_limit.rb +0 -206
  87. data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +0 -69
  88. data/lib/serega/plugins/explicit_many_option/validations/check_opt_many.rb +0 -38
  89. data/lib/serega/plugins/formatters/formatters.rb +0 -322
  90. data/lib/serega/plugins/if/if.rb +0 -287
  91. data/lib/serega/plugins/if/validations/check_opt_if.rb +0 -72
  92. data/lib/serega/plugins/if/validations/check_opt_if_value.rb +0 -77
  93. data/lib/serega/plugins/if/validations/check_opt_unless.rb +0 -72
  94. data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +0 -77
  95. data/lib/serega/plugins/metadata/validations/check_block.rb +0 -47
  96. data/lib/serega/plugins/metadata/validations/check_opt_const.rb +0 -39
  97. data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +0 -35
  98. data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +0 -35
  99. data/lib/serega/plugins/metadata/validations/check_opt_value.rb +0 -61
  100. data/lib/serega/plugins/metadata/validations/check_opts.rb +0 -59
  101. data/lib/serega/plugins/metadata/validations/check_path.rb +0 -50
  102. data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +0 -111
  103. data/lib/serega/plugins/string_modifiers/string_modifiers.rb +0 -63
  104. data/lib/serega/utils/symbol_name.rb +0 -39
  105. data/lib/serega/validations/attribute/check_opt_const.rb +0 -40
  106. data/lib/serega/validations/check_initiate_params.rb +0 -57
  107. data/lib/serega/validations/check_serialize_params.rb +0 -54
@@ -1,125 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- #
5
- # Low-level class used by the serializer to construct the serialized response.
6
- #
7
- # Serialization is level-by-level. `#serialize` builds the result container(s)
8
- # and enqueues this level; the batch queue later calls `#process` for each level,
9
- # which resolves every attribute for every object and enqueues child levels for
10
- # relations.
11
- #
12
- # @private
13
- class SeregaObjectSerializer
14
- #
15
- # SeregaObjectSerializer instance methods
16
- #
17
- # @private
18
- module InstanceMethods
19
- attr_reader :context, :plan, :many, :opts, :level_queue, :presenter
20
-
21
- # @param plan [SeregaPlan] Serialization plan
22
- # @param context [Hash] Serialization context
23
- # @param many [Boolean] is object is enumerable
24
- # @param opts [Hash] Any custom options
25
- #
26
- # @return [SeregaObjectSerializer] New SeregaObjectSerializer
27
- def initialize(context:, plan:, many: nil, **opts)
28
- @context = context
29
- @plan = plan
30
- @many = many
31
- @opts = opts
32
- @level_queue = opts[:level_queue]
33
- # Looked up once here and reused for every enqueued chunk of the level.
34
- @presenter = self.class.serializer_class.presenter
35
- end
36
-
37
- # Enqueues this level and returns its result container(s). The containers are
38
- # returned immediately but filled in place once the queue is processed.
39
- #
40
- # @param object [Object] Serialized object(s)
41
- #
42
- # @return [Hash, Array<Hash>, nil] Serialized object(s)
43
- def serialize(object)
44
- return if object.nil?
45
-
46
- case serialize_mode(object)
47
- when :many then enqueue(object.to_a)
48
- when :many_for_one then enqueue([object]) # `many` on, but a sole object was given — wrap it, don't raise
49
- else enqueue([object])[0] # :one
50
- end
51
- end
52
-
53
- # Resolves every attribute of one level onto its containers. For each point,
54
- # preloads and batch loaders run once over all of the level's objects; the
55
- # value is then resolved and assigned per object, in attribute order.
56
- #
57
- # @param level [SeregaEngine::Level] level to resolve
58
- # @return [void]
59
- def process(level)
60
- objects = level.objects
61
-
62
- plan.points.each do |point|
63
- point.run_preloads(objects)
64
- batches = point.load_batches(level)
65
- # One child serializer per relation point per level (reused for every object),
66
- # instead of one per object — child objects are grouped into one child level anyway.
67
- child_serializer = point.child_serializer(context: context, **opts)
68
-
69
- objects.each_with_index do |object, index|
70
- resolve_point(object, point, level.containers[index], batches, child_serializer)
71
- rescue => error
72
- SeregaUtils::SerializedAttributeError.call(error, point)
73
- end
74
- end
75
- end
76
-
77
- private
78
-
79
- # Enqueues this chunk of objects onto the queue and returns their result
80
- # containers. This is where objects enter their level, so every object a
81
- # point resolves against and a batch loader receives has the same shape.
82
- #
83
- # Each object is wrapped in the serializer's presenter before it is enqueued,
84
- # so the whole level — value resolution and batch loaders alike — sees
85
- # presenters. Serializers without a presenter enqueue the objects as they
86
- # are — wrapping would only add overhead and break class checks
87
- # (object.is_a?, Hash === object) without changing anything.
88
- def enqueue(objects)
89
- objects = objects.map { |object| presenter.new(object, context) } if presenter
90
-
91
- level_queue.enqueue(self, objects)
92
- end
93
-
94
- # Patched in:
95
- # - plugin :if (skips the point for objects failing an :if/:unless condition)
96
- def resolve_point(object, point, container, batches, child_serializer)
97
- value = point.value(object, context, batches: batches)
98
- final_value = child_serializer ? child_serializer.serialize(value) : value
99
- write_value(final_value, point, container)
100
- end
101
-
102
- # Patched in:
103
- # - plugin :if (skips assigning when an :if_value/:unless_value condition fails)
104
- def write_value(final_value, point, container)
105
- container[point.name] = final_value
106
- end
107
-
108
- # How to serialize `object`, deciding whether the result is a collection or a
109
- # single object and checking the object type only once:
110
- # - :many — `many` is on and the object is a collection
111
- # - :many_for_one — `many` is on but a sole object was given (wrap it, don't raise)
112
- # - :one — serialize the object on its own
113
- def serialize_mode(object)
114
- case many
115
- when NilClass then SeregaUtils::CollectionDetector.call(object) ? :many : :one
116
- when TrueClass then SeregaUtils::CollectionDetector.call(object) ? :many : :many_for_one
117
- else :one # many == false
118
- end
119
- end
120
- end
121
-
122
- extend Serega::SeregaHelpers::SerializerClassHelper
123
- include InstanceMethods
124
- end
125
- end
@@ -1,213 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- module SeregaPlugins
5
- #
6
- # Plugin :camel_case
7
- #
8
- # CamelCases every attribute name automatically, replacing `_x` with `X`
9
- # throughout the string. Runs once, when the attribute is defined, not on
10
- # every serialization.
11
- #
12
- # Provide a custom transformation with
13
- # `plugin :camel_case, transform: ->(name) { name.camelize }`.
14
- #
15
- # Skip camelCase for a single attribute with `camel_case: false`.
16
- #
17
- # @example Define plugin
18
- # class AppSerializer < Serega
19
- # plugin :camel_case
20
- # end
21
- #
22
- # class UserSerializer < AppSerializer
23
- # attribute :first_name
24
- # attribute :last_name
25
- # attribute :full_name, camel_case: false, value: proc { |user| [user.first_name, user.last_name].compact.join(" ") }
26
- # end
27
- #
28
- # require "ostruct"
29
- # user = OpenStruct.new(first_name: "Bruce", last_name: "Wayne")
30
- # UserSerializer.to_h(user) # {firstName: "Bruce", lastName: "Wayne", full_name: "Bruce Wayne"}
31
- #
32
- module CamelCase
33
- # Default camel-case transformation
34
- TRANSFORM_DEFAULT = proc { |attribute_name|
35
- attribute_name.gsub!(/_[a-z]/) { |m| m[-1].upcase! }
36
- attribute_name
37
- }
38
-
39
- # @return [Symbol] Plugin name
40
- # @private
41
- def self.plugin_name
42
- :camel_case
43
- end
44
-
45
- # Checks requirements to load plugin
46
- #
47
- # @param serializer_class [Class<Serega>] Current serializer class
48
- # @param opts [Hash] plugin options
49
- #
50
- # @return [void]
51
- #
52
- # @private
53
- def self.before_load_plugin(serializer_class, **opts)
54
- allowed_keys = %i[transform]
55
- opts.each_key do |key|
56
- next if allowed_keys.include?(key)
57
-
58
- raise SeregaError,
59
- "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
60
- " - :transform [#call] - Custom transformation"
61
- end
62
- end
63
-
64
- #
65
- # Applies plugin code to specific serializer
66
- #
67
- # @param serializer_class [Class<Serega>] Current serializer class
68
- # @param _opts [Hash] Plugin options
69
- #
70
- # @return [void]
71
- #
72
- # @private
73
- def self.load_plugin(serializer_class, **_opts)
74
- serializer_class::SeregaConfig.include(ConfigInstanceMethods)
75
- serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
76
- serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
77
- end
78
-
79
- #
80
- # Adds config options and runs other callbacks after plugin was loaded
81
- #
82
- # @param serializer_class [Class<Serega>] Current serializer class
83
- # @param opts [Hash] Plugin options
84
- #
85
- # @return [void]
86
- #
87
- # @private
88
- def self.after_load_plugin(serializer_class, **opts)
89
- config = serializer_class.config
90
- config.opts[:camel_case] = {}
91
- config.camel_case.transform = opts[:transform] || TRANSFORM_DEFAULT
92
-
93
- config.attribute_keys << :camel_case
94
- end
95
-
96
- #
97
- # Config class additional/patched instance methods
98
- #
99
- # @see Serega::SeregaConfig
100
- #
101
- module ConfigInstanceMethods
102
- # @return [Serega::SeregaPlugins::CamelCase::CamelCaseConfig] `camel_case` plugin config
103
- def camel_case
104
- @camel_case ||= CamelCaseConfig.new(opts.fetch(:camel_case))
105
- end
106
- end
107
-
108
- #
109
- # Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
110
- #
111
- # @see Serega::SeregaValidations::CheckAttributeParams
112
- #
113
- # @private
114
- module CheckAttributeParamsInstanceMethods
115
- private
116
-
117
- def check_opts
118
- super
119
- CheckOptCamelCase.call(opts)
120
- end
121
- end
122
-
123
- #
124
- # Validator for attribute :camel_case option
125
- #
126
- # @private
127
- class CheckOptCamelCase
128
- class << self
129
- #
130
- # Checks attribute :camel_case option must be boolean
131
- #
132
- # @param opts [Hash] Attribute options
133
- #
134
- # @raise [SeregaError] Attribute validation error
135
- #
136
- # @return [void]
137
- #
138
- def call(opts)
139
- camel_case_option_exists = opts.key?(:camel_case)
140
- return unless camel_case_option_exists
141
-
142
- value = opts[:camel_case]
143
- return if value.equal?(true) || value.equal?(false)
144
-
145
- raise SeregaError, "Attribute option :camel_case must have a boolean value, but #{value.class} was provided"
146
- end
147
- end
148
- end
149
-
150
- # CamelCase config object
151
- class CamelCaseConfig
152
- attr_reader :opts
153
-
154
- #
155
- # Initializes CamelCaseConfig object
156
- #
157
- # @param opts [Hash] camel_case plugin options
158
- # @option opts [#call] :transform Callable object that transforms original attribute name
159
- #
160
- # @return [Serega::SeregaPlugins::CamelCase::CamelCaseConfig] CamelCaseConfig object
161
- #
162
- def initialize(opts)
163
- @opts = opts
164
- end
165
-
166
- # @return [#call] defined object that transforms name
167
- def transform
168
- opts.fetch(:transform)
169
- end
170
-
171
- # Sets transformation callable object
172
- #
173
- # @param value [#call] transformation
174
- #
175
- # @return [#call] camel_case plugin transformation callable object
176
- def transform=(value)
177
- raise SeregaError, "Transform value must respond to #call" unless value.respond_to?(:call)
178
-
179
- params = value.is_a?(Proc) ? value.parameters : value.method(:call).parameters
180
- if params.count != 1 || !params.all? { |param| (param[0] == :req) || (param[0] == :opt) }
181
- raise SeregaError, "Transform value must respond to #call and accept 1 regular parameter"
182
- end
183
-
184
- opts[:transform] = value
185
- end
186
- end
187
-
188
- #
189
- # SeregaAttributeNormalizer additional/patched instance methods
190
- #
191
- # @see SeregaAttributeNormalizer::AttributeInstanceMethods
192
- #
193
- # @private
194
- module AttributeNormalizerInstanceMethods
195
- private
196
-
197
- #
198
- # Patch for original `prepare_name` method
199
- #
200
- # Makes camelCased name
201
- #
202
- def prepare_name
203
- res = super
204
- return res if init_opts[:camel_case] == false
205
-
206
- self.class.serializer_class.config.camel_case.transform.call(+res.to_s).to_sym
207
- end
208
- end
209
- end
210
-
211
- register_plugin(CamelCase.plugin_name, CamelCase)
212
- end
213
- end
@@ -1,206 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- module SeregaPlugins
5
- #
6
- # Plugin :depth_limit
7
- #
8
- # Guards against malicious queries that serialize too much, or against
9
- # accidentally serializing objects with cyclic relations.
10
- #
11
- # Depth limit is checked when a serialization plan is built, i.e. when
12
- # `#new` is called (`SomeSerializer.new(with: params[:with])`) — including
13
- # internally by every class-level serialization method. Instantiating the
14
- # serializer early surfaces depth errors sooner.
15
- #
16
- # When the limit is exceeded, `Serega::DepthLimitError` is raised;
17
- # details are available via `Serega::DepthLimitError#details`.
18
- #
19
- # Limit can be checked or changed with config options:
20
- #
21
- # - config.depth_limit.limit
22
- # - config.depth_limit.limit=
23
- #
24
- # There is no default limit — it must be set when enabling the plugin.
25
- #
26
- # @example
27
- #
28
- # class AppSerializer < Serega
29
- # plugin :depth_limit, limit: 10 # set limit for all child classes
30
- # end
31
- #
32
- # class UserSerializer < AppSerializer
33
- # config.depth_limit.limit = 5 # overrides limit for UserSerializer
34
- # end
35
- #
36
- module DepthLimit
37
- # @return [Symbol] Plugin name
38
- # @private
39
- def self.plugin_name
40
- :depth_limit
41
- end
42
-
43
- # Checks requirements
44
- #
45
- # @param serializer_class [Class<Serega>] Current serializer class
46
- # @param opts [Hash] Plugin options
47
- #
48
- # @return [void]
49
- #
50
- # @private
51
- def self.before_load_plugin(serializer_class, **opts)
52
- allowed_keys = %i[limit]
53
- opts.each_key do |key|
54
- next if allowed_keys.include?(key)
55
-
56
- raise SeregaError,
57
- "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
58
- " - :limit [Integer] - Maximum serialization depth."
59
- end
60
- end
61
-
62
- #
63
- # Applies plugin code to specific serializer
64
- #
65
- # @param serializer_class [Class<Serega>] Current serializer class
66
- # @param _opts [Hash] Plugin options
67
- #
68
- # @return [void]
69
- #
70
- # @private
71
- def self.load_plugin(serializer_class, **_opts)
72
- serializer_class::SeregaPlan.include(PlanInstanceMethods)
73
- serializer_class::SeregaConfig.include(ConfigInstanceMethods)
74
- end
75
-
76
- #
77
- # Adds config options and runs other callbacks after plugin was loaded
78
- #
79
- # @param serializer_class [Class<Serega>] Current serializer class
80
- # @param opts [Hash] Plugin options
81
- #
82
- # @return [void]
83
- #
84
- # @private
85
- def self.after_load_plugin(serializer_class, **opts)
86
- config = serializer_class.config
87
- limit = opts.fetch(:limit) { raise SeregaError, "Please provide :limit option. Example: `plugin :depth_limit, limit: 10`" }
88
- config.opts[:depth_limit] = {}
89
- config.depth_limit.limit = limit
90
- end
91
-
92
- # DepthLimit config object
93
- class DepthLimitConfig
94
- attr_reader :opts
95
-
96
- #
97
- # Initializes DepthLimitConfig object
98
- #
99
- # @param opts [Hash] depth_limit plugin options
100
- #
101
- # @return [SeregaPlugins::DepthLimit::DepthLimitConfig] DepthLimitConfig object
102
- #
103
- def initialize(opts)
104
- @opts = opts
105
- end
106
-
107
- # @return [Integer] defined depth limit
108
- def limit
109
- opts.fetch(:limit)
110
- end
111
-
112
- #
113
- # Set depth limit
114
- #
115
- # @param value [Integer] depth limit
116
- #
117
- # @return [Integer] depth limit
118
- def limit=(value)
119
- raise SeregaError, "Depth limit must be an Integer" unless value.is_a?(Integer)
120
-
121
- opts[:limit] = value
122
- end
123
- end
124
-
125
- #
126
- # Serega::SeregaConfig additional/patched class methods
127
- #
128
- # @see Serega::SeregaConfig
129
- #
130
- module ConfigInstanceMethods
131
- # @return [Serega::SeregaPlugins::DepthLimit::DepthLimitConfig] current depth_limit config
132
- def depth_limit
133
- @depth_limit ||= DepthLimitConfig.new(opts.fetch(:depth_limit))
134
- end
135
- end
136
-
137
- #
138
- # SeregaPlan additional/patched instance methods
139
- #
140
- # @see SeregaPlan
141
- #
142
- # @private
143
- module PlanInstanceMethods
144
- #
145
- # Initializes serialization plan
146
- # Overrides original method (adds depth_limit validation)
147
- #
148
- def initialize(parent_plan_point, *)
149
- check_depth_limit_exceeded(parent_plan_point)
150
- super
151
- end
152
-
153
- private
154
-
155
- def check_depth_limit_exceeded(current_point)
156
- plan = self
157
- depth_level = 1
158
- point = current_point
159
-
160
- while point
161
- depth_level += 1
162
- plan = point.plan
163
- point = plan.parent_plan_point
164
- end
165
-
166
- root_serializer = plan.class.serializer_class
167
- root_depth_limit = root_serializer.config.depth_limit.limit
168
-
169
- if depth_level > root_depth_limit
170
- fields_chain = [current_point.name]
171
- fields_chain << current_point.name while (current_point = current_point.plan.parent_plan_point)
172
- details = "#{root_serializer} (depth limit: #{root_depth_limit}) -> #{fields_chain.reverse!.join(" -> ")}"
173
- raise DepthLimitError.new("Depth limit was exceeded", details)
174
- end
175
- end
176
- end
177
- end
178
-
179
- register_plugin(DepthLimit.plugin_name, DepthLimit)
180
- end
181
-
182
- #
183
- # Special error for depth_limit plugin
184
- #
185
- class DepthLimitError < SeregaError
186
- #
187
- # Details of why depth limit error happens.
188
- #
189
- # @return [String] error details
190
- #
191
- attr_reader :details
192
-
193
- #
194
- # Initializes new error
195
- #
196
- # @param message [String] Error message
197
- # @param details [String] Error additional details
198
- #
199
- # @return [DepthLimitError] error instance
200
- #
201
- def initialize(message, details = nil)
202
- super(message)
203
- @details = details
204
- end
205
- end
206
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- module SeregaPlugins
5
- #
6
- # Plugin :explicit_many_option
7
- #
8
- # Requires the `:many` option on every relationship attribute (an
9
- # attribute with the `:serializer` option or a block defining a nested
10
- # serializer), so it's always explicit whether it returns one object or many.
11
- #
12
- # @example
13
- # class BaseSerializer < Serega
14
- # plugin :explicit_many_option
15
- # config.base_serializer = self
16
- # end
17
- #
18
- # class PostSerializer < BaseSerializer
19
- # attribute :text
20
- #
21
- # attribute :user, many: false do
22
- # attribute :name
23
- # end
24
- #
25
- # attribute :comments, serializer: PostSerializer, many: true
26
- # end
27
- #
28
- module ExplicitManyOption
29
- # @return [Symbol] Plugin name
30
- # @private
31
- def self.plugin_name
32
- :explicit_many_option
33
- end
34
-
35
- #
36
- # Applies plugin code to specific serializer
37
- #
38
- # @param serializer_class [Class<Serega>] Current serializer class
39
- # @param _opts [Hash] Plugin options
40
- #
41
- # @return [void]
42
- #
43
- # @private
44
- def self.load_plugin(serializer_class, **_opts)
45
- require_relative "validations/check_opt_many"
46
-
47
- serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
48
- end
49
-
50
- #
51
- # Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
52
- #
53
- # @see Serega::SeregaValidations::CheckAttributeParams
54
- #
55
- # @private
56
- module CheckAttributeParamsInstanceMethods
57
- private
58
-
59
- def check_opts
60
- super
61
-
62
- CheckOptMany.call(opts, block)
63
- end
64
- end
65
- end
66
-
67
- register_plugin(ExplicitManyOption.plugin_name, ExplicitManyOption)
68
- end
69
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- module SeregaPlugins
5
- module ExplicitManyOption
6
- #
7
- # Validator for attribute :many option
8
- #
9
- # @private
10
- class CheckOptMany
11
- class << self
12
- #
13
- # Checks attribute :many option must be provided with relations
14
- # (attributes with the :serializer option or a block defining
15
- # a nested serializer)
16
- #
17
- # @param opts [Hash] Attribute options
18
- # @param block [nil, Proc] Attribute block
19
- #
20
- # @raise [SeregaError] Attribute validation error
21
- #
22
- # @return [void]
23
- #
24
- def call(opts, block = nil)
25
- return if !opts[:serializer] && !block
26
-
27
- many_option_exists = opts.key?(:many)
28
- return if many_option_exists
29
-
30
- raise SeregaError,
31
- "Attribute option :many [Boolean] must be provided" \
32
- " for attributes with :serializer option or a block"
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end