serega 0.14.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +85 -75
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +2 -2
  5. data/lib/serega/attribute_normalizer.rb +8 -9
  6. data/lib/serega/config.rb +1 -1
  7. data/lib/serega/object_serializer.rb +1 -1
  8. data/lib/serega/plan.rb +11 -11
  9. data/lib/serega/plan_point.rb +5 -5
  10. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +1 -1
  11. data/lib/serega/plugins/batch/batch.rb +15 -15
  12. data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +2 -2
  13. data/lib/serega/plugins/camel_case/camel_case.rb +195 -0
  14. data/lib/serega/plugins/depth_limit/depth_limit.rb +185 -0
  15. data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +2 -5
  16. data/lib/serega/plugins/if/if.rb +4 -4
  17. data/lib/serega/plugins/metadata/metadata.rb +6 -6
  18. data/lib/serega/plugins/preloads/lib/modules/attribute_normalizer.rb +1 -1
  19. data/lib/serega/plugins/preloads/lib/preload_paths.rb +12 -5
  20. data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +1 -1
  21. data/lib/serega/plugins/preloads/preloads.rb +12 -12
  22. data/lib/serega/plugins/root/root.rb +1 -1
  23. data/lib/serega/plugins/string_modifiers/string_modifiers.rb +1 -1
  24. data/lib/serega/validations/attribute/check_opt_const.rb +1 -1
  25. data/lib/serega/validations/attribute/check_opt_delegate.rb +9 -4
  26. data/lib/serega/validations/attribute/check_opt_many.rb +28 -11
  27. data/lib/serega/validations/attribute/{check_opt_key.rb → check_opt_method.rb} +8 -8
  28. data/lib/serega/validations/attribute/check_opt_value.rb +1 -1
  29. data/lib/serega/validations/check_attribute_params.rb +2 -2
  30. data/lib/serega/validations/check_initiate_params.rb +1 -1
  31. data/lib/serega/validations/check_serialize_params.rb +1 -1
  32. data/lib/serega/validations/utils/check_allowed_keys.rb +4 -2
  33. data/lib/serega.rb +24 -11
  34. metadata +5 -6
  35. data/lib/serega/plugins/openapi/lib/modules/config.rb +0 -23
  36. data/lib/serega/plugins/openapi/lib/openapi_config.rb +0 -101
  37. data/lib/serega/plugins/openapi/openapi.rb +0 -245
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ #
6
+ # Plugin :camel_case
7
+ #
8
+ # By default when we add attribute like `attribute :first_name` this means:
9
+ # - adding a `:first_name` key to resulted hash
10
+ # - adding a `#first_name` method call result as value
11
+ #
12
+ # But its often desired to response with *camelCased* keys.
13
+ # Earlier this can be achieved by specifying attribute name and method directly
14
+ # for each attribute: `attribute :firstName, method: first_name`
15
+ #
16
+ # Now this plugin transforms all attribute names automatically.
17
+ # We use simple regular expression to replace `_x` to `X` for the whole string.
18
+ # You can provide your own callable transformation when defining plugin,
19
+ # for example `plugin :camel_case, transform: ->(name) { name.camelize }`
20
+ #
21
+ # For any attribute camelCase-behavior can be skipped when
22
+ # `camel_case: false` attribute option provided.
23
+ #
24
+ # @example Define plugin
25
+ # class AppSerializer < Serega
26
+ # plugin :camel_case
27
+ # end
28
+ #
29
+ # class UserSerializer < AppSerializer
30
+ # attribute :first_name
31
+ # attribute :last_name
32
+ # attribute :full_name, camel_case: false, value: proc { |user| [user.first_name, user.last_name].compact.join(" ") }
33
+ # end
34
+ #
35
+ # require "ostruct"
36
+ # user = OpenStruct.new(first_name: "Bruce", last_name: "Wayne")
37
+ # UserSerializer.to_h(user) # {firstName: "Bruce", lastName: "Wayne", full_name: "Bruce Wayne"}
38
+ #
39
+ module CamelCase
40
+ # Default camel-case transformation
41
+ TRANSFORM_DEFAULT = proc { |attribute_name|
42
+ attribute_name.gsub!(/_[a-z]/) { |m| m[-1].upcase! }
43
+ attribute_name
44
+ }
45
+
46
+ # @return [Symbol] Plugin name
47
+ def self.plugin_name
48
+ :camel_case
49
+ end
50
+
51
+ #
52
+ # Applies plugin code to specific serializer
53
+ #
54
+ # @param serializer_class [Class<Serega>] Current serializer class
55
+ # @param _opts [Hash] Loaded plugins options
56
+ #
57
+ # @return [void]
58
+ #
59
+ def self.load_plugin(serializer_class, **_opts)
60
+ serializer_class::SeregaConfig.include(ConfigInstanceMethods)
61
+ serializer_class::SeregaAttributeNormalizer.include(AttributeNormalizerInstanceMethods)
62
+ serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
63
+ end
64
+
65
+ #
66
+ # Adds config options and runs other callbacks after plugin was loaded
67
+ #
68
+ # @param serializer_class [Class<Serega>] Current serializer class
69
+ # @param opts [Hash] loaded plugins opts
70
+ #
71
+ # @return [void]
72
+ #
73
+ def self.after_load_plugin(serializer_class, **opts)
74
+ config = serializer_class.config
75
+ config.opts[:camel_case] = {}
76
+ config.camel_case.transform = opts[:transform] || TRANSFORM_DEFAULT
77
+
78
+ config.attribute_keys << :camel_case
79
+ end
80
+
81
+ #
82
+ # Config class additional/patched instance methods
83
+ #
84
+ # @see Serega::SeregaConfig
85
+ #
86
+ module ConfigInstanceMethods
87
+ # @return [Serega::SeregaPlugins::CamelCase::CamelCaseConfig] `camel_case` plugin config
88
+ def camel_case
89
+ @camel_case ||= CamelCaseConfig.new(opts.fetch(:camel_case))
90
+ end
91
+ end
92
+
93
+ #
94
+ # Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
95
+ #
96
+ # @see Serega::SeregaValidations::CheckAttributeParams
97
+ #
98
+ module CheckAttributeParamsInstanceMethods
99
+ private
100
+
101
+ def check_opts
102
+ super
103
+ CheckOptCamelCase.call(opts)
104
+ end
105
+ end
106
+
107
+ #
108
+ # Validator for attribute :camel_case option
109
+ #
110
+ class CheckOptCamelCase
111
+ class << self
112
+ #
113
+ # Checks attribute :camel_case option must be boolean
114
+ #
115
+ # @param opts [Hash] Attribute options
116
+ #
117
+ # @raise [SeregaError] Attribute validation error
118
+ #
119
+ # @return [void]
120
+ #
121
+ def call(opts)
122
+ camel_case_option_exists = opts.key?(:camel_case)
123
+ return unless camel_case_option_exists
124
+
125
+ value = opts[:camel_case]
126
+ return if value.equal?(true) || value.equal?(false)
127
+
128
+ raise SeregaError, "Attribute option :camel_case must have a boolean value, but #{value.class} was provided"
129
+ end
130
+ end
131
+ end
132
+
133
+ # CamelCase config object
134
+ class CamelCaseConfig
135
+ attr_reader :opts
136
+
137
+ #
138
+ # Initializes CamelCaseConfig object
139
+ #
140
+ # @param opts [Hash] camel_case plugin options
141
+ # @option opts [#call] :transform Callable object that transforms original attribute name
142
+ #
143
+ # @return [Serega::SeregaPlugins::CamelCase::CamelCaseConfig] CamelCaseConfig object
144
+ #
145
+ def initialize(opts)
146
+ @opts = opts
147
+ end
148
+
149
+ # @return [#call] defined object that transforms name
150
+ def transform
151
+ opts.fetch(:transform)
152
+ end
153
+
154
+ # Sets transformation callable object
155
+ #
156
+ # @param value [#call] transformation
157
+ #
158
+ # @return [#call] camel_case plugin transformation callable object
159
+ def transform=(value)
160
+ raise SeregaError, "Transform value must respond to #call" unless value.respond_to?(:call)
161
+
162
+ params = value.is_a?(Proc) ? value.parameters : value.method(:call).parameters
163
+ if params.count != 1 || !params.all? { |param| (param[0] == :req) || (param[0] == :opt) }
164
+ raise SeregaError, "Transform value must respond to #call and accept 1 regular parameter"
165
+ end
166
+
167
+ opts[:transform] = value
168
+ end
169
+ end
170
+
171
+ #
172
+ # SeregaAttributeNormalizer additional/patched instance methods
173
+ #
174
+ # @see SeregaAttributeNormalizer::AttributeInstanceMethods
175
+ #
176
+ module AttributeNormalizerInstanceMethods
177
+ private
178
+
179
+ #
180
+ # Patch for original `prepare_name` method
181
+ #
182
+ # Makes camelCased name
183
+ #
184
+ def prepare_name
185
+ res = super
186
+ return res if init_opts[:camel_case] == false
187
+
188
+ self.class.serializer_class.config.camel_case.transform.call(res.to_s).to_sym
189
+ end
190
+ end
191
+ end
192
+
193
+ register_plugin(CamelCase.plugin_name, CamelCase)
194
+ end
195
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ #
6
+ # Plugin :depth_limit
7
+ #
8
+ # Helps to secure from malicious queries that require to serialize too much
9
+ # or from accidental serializing of objects with cyclic relations.
10
+ #
11
+ # Depth limit is checked when constructing a serialization plan, that is when
12
+ # `#new` method is called, ex: `SomeSerializer.new(with: params[:with])`.
13
+ # It can be useful to instantiate serializer before any other business logic
14
+ # to get possible errors earlier.
15
+ #
16
+ # Any class-level serialization methods also check depth limit as they also instantiate serializer.
17
+ #
18
+ # When depth limit is exceeded `Serega::DepthLimitError` is raised.
19
+ # Depth limit error details can be found in additional `Serega::DepthLimitError#details` method
20
+ #
21
+ # Limit can be checked or changed with next config options:
22
+ #
23
+ # - config.depth_limit.limit
24
+ # - config.depth_limit.limit=
25
+ #
26
+ # There are no default limit, but it should be set when enabling plugin.
27
+ #
28
+ # @example
29
+ #
30
+ # class AppSerializer < Serega
31
+ # plugin :depth_limit, limit: 10 # set limit for all child classes
32
+ # end
33
+ #
34
+ # class UserSerializer < AppSerializer
35
+ # config.depth_limit.limit = 5 # overrides limit for UserSerializer
36
+ # end
37
+ #
38
+ module DepthLimit
39
+ # @return [Symbol] Plugin name
40
+ def self.plugin_name
41
+ :depth_limit
42
+ end
43
+
44
+ #
45
+ # Applies plugin code to specific serializer
46
+ #
47
+ # @param serializer_class [Class<Serega>] Current serializer class
48
+ # @param _opts [Hash] Loaded plugins options
49
+ #
50
+ # @return [void]
51
+ #
52
+ def self.load_plugin(serializer_class, **_opts)
53
+ serializer_class::SeregaPlan.include(PlanInstanceMethods)
54
+ serializer_class::SeregaConfig.include(ConfigInstanceMethods)
55
+ end
56
+
57
+ #
58
+ # Adds config options and runs other callbacks after plugin was loaded
59
+ #
60
+ # @param serializer_class [Class<Serega>] Current serializer class
61
+ # @param opts [Hash] loaded plugins opts
62
+ #
63
+ # @return [void]
64
+ #
65
+ def self.after_load_plugin(serializer_class, **opts)
66
+ config = serializer_class.config
67
+ limit = opts.fetch(:limit) { raise SeregaError, "Please provide :limit option. Example: `plugin :depth_limit, limit: 10`" }
68
+ config.opts[:depth_limit] = {}
69
+ config.depth_limit.limit = limit
70
+ end
71
+
72
+ # DepthLimit config object
73
+ class DepthLimitConfig
74
+ attr_reader :opts
75
+
76
+ #
77
+ # Initializes DepthLimitConfig object
78
+ #
79
+ # @param opts [Hash] depth_limit plugin options
80
+ #
81
+ # @return [SeregaPlugins::DepthLimit::DepthLimitConfig] DepthLimitConfig object
82
+ #
83
+ def initialize(opts)
84
+ @opts = opts
85
+ end
86
+
87
+ # @return [Integer] defined depth limit
88
+ def limit
89
+ opts.fetch(:limit)
90
+ end
91
+
92
+ #
93
+ # Set depth limit
94
+ #
95
+ # @param value [Integer] depth limit
96
+ #
97
+ # @return [Integer] depth limit
98
+ def limit=(value)
99
+ raise SeregaError, "Depth limit must be an Integer" unless value.is_a?(Integer)
100
+
101
+ opts[:limit] = value
102
+ end
103
+ end
104
+
105
+ #
106
+ # Serega::SeregaConfig additional/patched class methods
107
+ #
108
+ # @see Serega::SeregaConfig
109
+ #
110
+ module ConfigInstanceMethods
111
+ # @return [Serega::SeregaPlugins::DepthLimit::DepthLimitConfig] current depth_limit config
112
+ def depth_limit
113
+ @depth_limit ||= DepthLimitConfig.new(opts.fetch(:depth_limit))
114
+ end
115
+ end
116
+
117
+ #
118
+ # SeregaPlan additional/patched instance methods
119
+ #
120
+ # @see SeregaPlan
121
+ #
122
+ module PlanInstanceMethods
123
+ #
124
+ # Initializes serialization plan
125
+ # Overrides original method (adds depth_limit validation)
126
+ #
127
+ def initialize(parent_plan_point, *)
128
+ check_depth_limit_exceeded(parent_plan_point)
129
+ super
130
+ end
131
+
132
+ private
133
+
134
+ def check_depth_limit_exceeded(current_point)
135
+ plan = self
136
+ depth_level = 1
137
+ point = current_point
138
+
139
+ while point
140
+ depth_level += 1
141
+ plan = point.plan
142
+ point = plan.parent_plan_point
143
+ end
144
+
145
+ root_serializer = plan.class.serializer_class
146
+ root_depth_limit = root_serializer.config.depth_limit.limit
147
+
148
+ if depth_level > root_depth_limit
149
+ fields_chain = [current_point.name]
150
+ fields_chain << current_point.name while (current_point = current_point.plan.parent_plan_point)
151
+ details = "#{root_serializer} (depth limit: #{root_depth_limit}) -> #{fields_chain.reverse!.join(" -> ")}"
152
+ raise DepthLimitError.new("Depth limit was exceeded", details)
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ register_plugin(DepthLimit.plugin_name, DepthLimit)
159
+ end
160
+
161
+ #
162
+ # Special error for depth_limit plugin
163
+ #
164
+ class DepthLimitError < SeregaError
165
+ #
166
+ # Details of why depth limit error happens.
167
+ #
168
+ # @return [String] error details
169
+ #
170
+ attr_reader :details
171
+
172
+ #
173
+ # Initializes new error
174
+ #
175
+ # @param message [String] Error message
176
+ # @param details [String] Error additional details
177
+ #
178
+ # @return [DepthLimitError] error instance
179
+ #
180
+ def initialize(message, details = nil)
181
+ super(message)
182
+ @details = details
183
+ end
184
+ end
185
+ end
@@ -8,10 +8,7 @@ class Serega
8
8
  # Plugin requires to add :many option when adding relationships
9
9
  # (relationships are attributes with :serializer option specified)
10
10
  #
11
- # Adding this plugin makes clearer to find if relationship returns array or single object
12
- #
13
- # Also some plugins like :openapi load this plugin automatically as they need to know if
14
- # relationship is array
11
+ # Adding this plugin makes it clearer to find if relationship returns array or single object
15
12
  #
16
13
  # @example
17
14
  # class BaseSerializer < Serega
@@ -43,7 +40,7 @@ class Serega
43
40
  # @return [void]
44
41
  #
45
42
  def self.load_plugin(serializer_class, **_opts)
46
- require_relative "./validations/check_opt_many"
43
+ require_relative "validations/check_opt_many"
47
44
 
48
45
  serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
49
46
  end
@@ -56,10 +56,10 @@ class Serega
56
56
  # @return [void]
57
57
  #
58
58
  def self.load_plugin(serializer_class, **_opts)
59
- require_relative "./validations/check_opt_if"
60
- require_relative "./validations/check_opt_if_value"
61
- require_relative "./validations/check_opt_unless"
62
- require_relative "./validations/check_opt_unless_value"
59
+ require_relative "validations/check_opt_if"
60
+ require_relative "validations/check_opt_if_value"
61
+ require_relative "validations/check_opt_unless"
62
+ require_relative "validations/check_opt_unless_value"
63
63
 
64
64
  serializer_class::SeregaAttribute.include(SeregaAttributeInstanceMethods)
65
65
  serializer_class::SeregaPlanPoint.include(PlanPointInstanceMethods)
@@ -62,12 +62,12 @@ class Serega
62
62
  serializer_class.include(InstanceMethods)
63
63
  serializer_class::SeregaConfig.include(ConfigInstanceMethods)
64
64
 
65
- require_relative "./meta_attribute"
66
- require_relative "./validations/check_block"
67
- require_relative "./validations/check_opt_hide_nil"
68
- require_relative "./validations/check_opt_hide_empty"
69
- require_relative "./validations/check_opts"
70
- require_relative "./validations/check_path"
65
+ require_relative "meta_attribute"
66
+ require_relative "validations/check_block"
67
+ require_relative "validations/check_opt_hide_nil"
68
+ require_relative "validations/check_opt_hide_empty"
69
+ require_relative "validations/check_opts"
70
+ require_relative "validations/check_path"
71
71
 
72
72
  meta_attribute_class = Class.new(MetaAttribute)
73
73
  meta_attribute_class.serializer_class = serializer_class
@@ -36,7 +36,7 @@ class Serega
36
36
  if preloads_provided
37
37
  opts[:preload]
38
38
  elsif opts.key?(:serializer) && self.class.serializer_class.config.preloads.auto_preload_attributes_with_serializer
39
- key
39
+ method_name
40
40
  elsif opts.key?(:delegate) && self.class.serializer_class.config.preloads.auto_preload_attributes_with_delegate
41
41
  opts[:delegate].fetch(:to)
42
42
  end
@@ -22,18 +22,25 @@ class Serega
22
22
  #
23
23
  # Transforms user provided preloads to array of paths
24
24
  #
25
- # @param value [Array,Hash,String,Symbol,nil,false] preloads
25
+ # @param preloads [Array,Hash,String,Symbol,nil,false] association(s) to preload
26
26
  #
27
27
  # @return [Hash] preloads transformed to hash
28
28
  #
29
- def call(preloads, path = [], result = [])
30
- preloads = FormatUserPreloads.call(preloads)
29
+ def call(preloads)
30
+ formatted_preloads = FormatUserPreloads.call(preloads)
31
+ return FROZEN_EMPTY_ARRAY if formatted_preloads.empty?
31
32
 
32
- preloads.each do |key, nested_preloads|
33
+ paths(formatted_preloads, [], [])
34
+ end
35
+
36
+ private
37
+
38
+ def paths(formatted_preloads, path, result)
39
+ formatted_preloads.each do |key, nested_preloads|
33
40
  path << key
34
41
  result << path.dup
35
42
 
36
- call(nested_preloads, path, result)
43
+ paths(nested_preloads, path, result)
37
44
  path.pop
38
45
  end
39
46
 
@@ -11,7 +11,7 @@ class Serega
11
11
  #
12
12
  # Constructs preloads hash for given attributes plan
13
13
  #
14
- # @param plan [Array<Serega::PlanPoint>] Serialization plan
14
+ # @param plan [Array<SeregaPlanPoint>] Serialization plan
15
15
  #
16
16
  # @return [Hash]
17
17
  #
@@ -15,7 +15,7 @@ class Serega
15
15
  #
16
16
  # This options are very handy if you want to forget about finding preloads manually.
17
17
  #
18
- # Preloads can be disabled with `preload: false` attribute option option.
18
+ # Preloads can be disabled with `preload: false` attribute option.
19
19
  # Also automatically added preloads can be overwritten with manually specified `preload: :another_value`.
20
20
  #
21
21
  # Some examples, **please read comments in the code below**
@@ -79,17 +79,17 @@ class Serega
79
79
  # @return [void]
80
80
  #
81
81
  def self.load_plugin(serializer_class, **_opts)
82
- require_relative "./lib/format_user_preloads"
83
- require_relative "./lib/modules/attribute"
84
- require_relative "./lib/modules/attribute_normalizer"
85
- require_relative "./lib/modules/check_attribute_params"
86
- require_relative "./lib/modules/config"
87
- require_relative "./lib/modules/plan_point"
88
- require_relative "./lib/preload_paths"
89
- require_relative "./lib/preloads_config"
90
- require_relative "./lib/preloads_constructor"
91
- require_relative "./validations/check_opt_preload"
92
- require_relative "./validations/check_opt_preload_path"
82
+ require_relative "lib/format_user_preloads"
83
+ require_relative "lib/modules/attribute"
84
+ require_relative "lib/modules/attribute_normalizer"
85
+ require_relative "lib/modules/check_attribute_params"
86
+ require_relative "lib/modules/config"
87
+ require_relative "lib/modules/plan_point"
88
+ require_relative "lib/preload_paths"
89
+ require_relative "lib/preloads_config"
90
+ require_relative "lib/preloads_constructor"
91
+ require_relative "validations/check_opt_preload"
92
+ require_relative "validations/check_opt_preload_path"
93
93
 
94
94
  serializer_class.include(InstanceMethods)
95
95
  serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
@@ -127,7 +127,7 @@ class Serega
127
127
  # @option opts [Symbol, String, nil] :one root for single-object serialization
128
128
  # @option opts [Symbol, String, nil] :many root for many-objects serialization
129
129
  #
130
- # @return [Serega::SeregaPlugins::Root::RootConfig] RootConfig object
130
+ # @return [SeregaPlugins::Root::RootConfig] RootConfig object
131
131
  #
132
132
  def initialize(opts)
133
133
  @opts = opts
@@ -18,7 +18,7 @@ class Serega
18
18
  #
19
19
  def self.load_plugin(serializer_class, **_opts)
20
20
  serializer_class.include(InstanceMethods)
21
- require_relative "./parse_string_modifiers"
21
+ require_relative "parse_string_modifiers"
22
22
  end
23
23
 
24
24
  #
@@ -26,7 +26,7 @@ class Serega
26
26
  private
27
27
 
28
28
  def check_usage_with_other_params(opts, block)
29
- raise SeregaError, "Option :const can not be used together with option :key" if opts.key?(:key)
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
31
  raise SeregaError, "Option :const can not be used together with block" if block
32
32
  end
@@ -32,8 +32,9 @@ class Serega
32
32
 
33
33
  delegate_opts = opts[:delegate]
34
34
  check_opt_delegate_to(delegate_opts)
35
- check_opt_delegate_key(delegate_opts)
35
+ check_opt_delegate_method(delegate_opts)
36
36
  check_opt_delegate_allow_nil(delegate_opts)
37
+ check_opt_delegate_extra_opts(delegate_opts)
37
38
  end
38
39
 
39
40
  def check_opt_delegate_to(delegate_opts)
@@ -43,16 +44,20 @@ class Serega
43
44
  Utils::CheckOptIsStringOrSymbol.call(delegate_opts, :to)
44
45
  end
45
46
 
46
- def check_opt_delegate_key(delegate_opts)
47
- Utils::CheckOptIsStringOrSymbol.call(delegate_opts, :key)
47
+ def check_opt_delegate_method(delegate_opts)
48
+ Utils::CheckOptIsStringOrSymbol.call(delegate_opts, :method)
48
49
  end
49
50
 
50
51
  def check_opt_delegate_allow_nil(delegate_opts)
51
52
  Utils::CheckOptIsBool.call(delegate_opts, :allow_nil)
52
53
  end
53
54
 
55
+ def check_opt_delegate_extra_opts(delegate_opts)
56
+ Utils::CheckAllowedKeys.call(delegate_opts, %i[to method allow_nil], :delegate)
57
+ end
58
+
54
59
  def check_usage_with_other_params(opts, block)
55
- raise SeregaError, "Option :delegate can not be used together with option :key" if opts.key?(:key)
60
+ raise SeregaError, "Option :delegate can not be used together with option :method" if opts.key?(:method)
56
61
  raise SeregaError, "Option :delegate can not be used together with option :const" if opts.key?(:const)
57
62
  raise SeregaError, "Option :delegate can not be used together with option :value" if opts.key?(:value)
58
63
  raise SeregaError, "Option :delegate can not be used together with block" if block
@@ -7,17 +7,34 @@ class Serega
7
7
  # Attribute `:many` option validator
8
8
  #
9
9
  class CheckOptMany
10
- #
11
- # Checks attribute :many option
12
- #
13
- # @param opts [Hash] Attribute options
14
- #
15
- # @raise [SeregaError] SeregaError that option has invalid value
16
- #
17
- # @return [void]
18
- #
19
- def self.call(opts)
20
- Utils::CheckOptIsBool.call(opts, :many)
10
+ class << self
11
+ #
12
+ # Checks attribute :many option
13
+ #
14
+ # @param opts [Hash] Attribute options
15
+ #
16
+ # @raise [SeregaError] SeregaError that option has invalid value
17
+ #
18
+ # @return [void]
19
+ #
20
+ def call(opts)
21
+ return unless opts.key?(:many)
22
+
23
+ check_many_option_makes_sence(opts)
24
+ Utils::CheckOptIsBool.call(opts, :many)
25
+ end
26
+
27
+ private
28
+
29
+ def check_many_option_makes_sence(opts)
30
+ return if many_option_makes_sence?(opts)
31
+
32
+ raise SeregaError, "Option :many can be provided only together with :serializer or :batch option"
33
+ end
34
+
35
+ def many_option_makes_sence?(opts)
36
+ opts[:serializer] || opts[:batch]
37
+ end
21
38
  end
22
39
  end
23
40
  end
@@ -4,12 +4,12 @@ class Serega
4
4
  module SeregaValidations
5
5
  module Attribute
6
6
  #
7
- # Attribute `:key` option validator
7
+ # Attribute `:method` option validator
8
8
  #
9
- class CheckOptKey
9
+ class CheckOptMethod
10
10
  class << self
11
11
  #
12
- # Checks attribute :key option
12
+ # Checks attribute :method option
13
13
  #
14
14
  # @param opts [Hash] Attribute options
15
15
  #
@@ -18,18 +18,18 @@ class Serega
18
18
  # @return [void]
19
19
  #
20
20
  def call(opts, block = nil)
21
- return unless opts.key?(:key)
21
+ return unless opts.key?(:method)
22
22
 
23
23
  check_usage_with_other_params(opts, block)
24
- Utils::CheckOptIsStringOrSymbol.call(opts, :key)
24
+ Utils::CheckOptIsStringOrSymbol.call(opts, :method)
25
25
  end
26
26
 
27
27
  private
28
28
 
29
29
  def check_usage_with_other_params(opts, block)
30
- raise SeregaError, "Option :key can not be used together with option :const" if opts.key?(:const)
31
- raise SeregaError, "Option :key can not be used together with option :value" if opts.key?(:value)
32
- raise SeregaError, "Option :key can not be used together with block" if block
30
+ raise SeregaError, "Option :method can not be used together with option :const" if opts.key?(:const)
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 block" if block
33
33
  end
34
34
  end
35
35
  end