serega 0.11.2 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +163 -13
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +9 -4
  5. data/lib/serega/attribute_normalizer.rb +4 -13
  6. data/lib/serega/object_serializer.rb +11 -0
  7. data/lib/serega/plan.rb +20 -25
  8. data/lib/serega/plan_point.rb +13 -16
  9. data/lib/serega/plugins/batch/lib/loader.rb +25 -7
  10. data/lib/serega/plugins/batch/lib/modules/attribute_normalizer.rb +1 -9
  11. data/lib/serega/plugins/explicit_many_option/explicit_many_option.rb +69 -0
  12. data/lib/serega/plugins/explicit_many_option/validations/check_opt_many.rb +35 -0
  13. data/lib/serega/plugins/metadata/metadata.rb +5 -0
  14. data/lib/serega/plugins/openapi/lib/modules/config.rb +23 -0
  15. data/lib/serega/plugins/openapi/lib/openapi_config.rb +101 -0
  16. data/lib/serega/plugins/openapi/openapi.rb +245 -0
  17. data/lib/serega/plugins/preloads/lib/modules/attribute.rb +28 -0
  18. data/lib/serega/plugins/preloads/lib/modules/attribute_normalizer.rb +99 -0
  19. data/lib/serega/plugins/preloads/lib/modules/check_attribute_params.rb +22 -0
  20. data/lib/serega/plugins/preloads/lib/modules/config.rb +19 -0
  21. data/lib/serega/plugins/preloads/lib/modules/plan_point.rb +41 -0
  22. data/lib/serega/plugins/preloads/lib/preload_paths.rb +46 -0
  23. data/lib/serega/plugins/preloads/lib/preloads_config.rb +62 -0
  24. data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +20 -7
  25. data/lib/serega/plugins/preloads/preloads.rb +12 -210
  26. data/lib/serega/plugins/preloads/validations/check_opt_preload_path.rb +54 -15
  27. metadata +18 -7
  28. data/lib/serega/plugins/preloads/lib/main_preload_path.rb +0 -53
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module OpenAPI
6
+ #
7
+ # Config class additional/patched instance methods
8
+ #
9
+ # @see Serega::SeregaConfig
10
+ #
11
+ module ConfigInstanceMethods
12
+ #
13
+ # Returns openapi plugin config
14
+ #
15
+ # @return [Serega::SeregaPlugins::OpenAPI::OpenAPIConfig] configuration for openapi plugin
16
+ #
17
+ def openapi
18
+ @openapi ||= OpenAPIConfig.new(self.class.serializer_class, opts.fetch(:openapi))
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module OpenAPI
6
+ #
7
+ # OpenAPI plugin config
8
+ #
9
+ class OpenAPIConfig
10
+ attr_reader :serializer_class, :opts
11
+
12
+ def initialize(serializer_class, opts)
13
+ @serializer_class = serializer_class
14
+ @opts = opts
15
+ end
16
+
17
+ #
18
+ # Saves new properties
19
+ #
20
+ # @param new_properties [Hash] new properties
21
+ #
22
+ # @return [Hash] OpenAPI properties
23
+ #
24
+ def properties(new_properties = FROZEN_EMPTY_HASH)
25
+ properties = opts[:properties]
26
+ return properties if new_properties.empty?
27
+
28
+ new_properties = SeregaUtils::EnumDeepDup.call(new_properties)
29
+ symbolize_keys!(new_properties)
30
+
31
+ new_properties.each do |attribute_name, new_attribute_properties|
32
+ check_attribute_exists(attribute_name)
33
+ check_properties_is_a_hash(attribute_name, new_attribute_properties)
34
+
35
+ properties[attribute_name] = symbolize_keys!(new_attribute_properties)
36
+ end
37
+ end
38
+
39
+ #
40
+ # @return [#call] builder of `$ref` attribute
41
+ #
42
+ def ref_builder
43
+ opts[:ref_builder]
44
+ end
45
+
46
+ #
47
+ # Sets new $ref option builder
48
+ #
49
+ # @param builder [#call] Callable object that accepts serializer_class and constructs $ref option string
50
+ #
51
+ # @return Specified new builder
52
+ #
53
+ def ref_builder=(builder)
54
+ raise SeregaError, "ref_builder must respond to #call" unless builder.respond_to?(:call)
55
+ opts[:ref_builder] = builder
56
+ end
57
+
58
+ #
59
+ # @return [#call] builder of schema name
60
+ #
61
+ def schema_name_builder
62
+ opts[:schema_name_builder]
63
+ end
64
+
65
+ #
66
+ # Sets new schema_name_builder
67
+ #
68
+ # @param builder [#call] Callable object that accepts serializer_class and
69
+ # constructs schema name to use in schemas list and in $ref option
70
+ #
71
+ # @return Specified new builder
72
+ #
73
+ def schema_name_builder=(builder)
74
+ raise SeregaError, "schema_name_builder must respond to #call" unless builder.respond_to?(:call)
75
+ opts[:schema_name_builder] = builder
76
+ end
77
+
78
+ private
79
+
80
+ def check_attribute_exists(attribute_name)
81
+ return if serializer_class.attributes.key?(attribute_name)
82
+
83
+ raise SeregaError, "No attribute with name :#{attribute_name}"
84
+ end
85
+
86
+ def check_properties_is_a_hash(attribute_name, new_attribute_properties)
87
+ return if new_attribute_properties.is_a?(Hash)
88
+
89
+ raise SeregaError, "Property #{attribute_name} value must be a Hash," \
90
+ " but #{new_attribute_properties.inspect} was provided"
91
+ end
92
+
93
+ def symbolize_keys!(opts)
94
+ opts.transform_keys! do |key|
95
+ key.to_sym
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ #
5
+ # Utility class to build OpenAPI schemas
6
+ #
7
+ class OpenAPI
8
+ #
9
+ # Constructs OpenAPI schemas for multiple serializers
10
+ #
11
+ # @params serializers [Class<Serega>] Serializers tobuild schemas,
12
+ # by default it is all serializers with :openapi plugin enabled
13
+ #
14
+ # @return [Hash] Schemas hash
15
+ #
16
+ def self.schemas(serializers = self.serializers)
17
+ serializers.each_with_object({}) do |serializer_class, schemas|
18
+ schema = serializer_class.openapi_schema
19
+ schema_name = serializer_class.openapi_schema_name
20
+ schemas[schema_name] = schema
21
+ end
22
+ end
23
+
24
+ #
25
+ # Returns list of serializers with :openapi plugin
26
+ #
27
+ def self.serializers
28
+ @serializers ||= []
29
+ end
30
+ end
31
+
32
+ module SeregaPlugins
33
+ #
34
+ # Plugin :openapi
35
+ #
36
+ # Helps to build OpenAPI schemas
37
+ #
38
+ # This schemas can be easielty used with "rswag" gem by adding them to "config.swagger_docs"
39
+ # https://github.com/rswag/rswag#referenced-parameters-and-schema-definitions
40
+ #
41
+ # This plugin only adds type "object" or "array" for relationships and marks
42
+ # attributes as **required** if they have no :hide option set.
43
+ #
44
+ # OpenAPI properties will have no any "type" or other options specified by default,
45
+ # you should provide them in 'YourSerializer.openapi_properties' method.
46
+ # `openapi_properties` can be specified multiple time, in this case they wil be merged.
47
+ #
48
+ # After enabling this plugin attributes with :serializer option will have
49
+ # to have :many option set to construct "object" or "array" openapi type for relationships.
50
+ #
51
+ # OpenAPI `$ref` property will be added automatically for all relationships.
52
+ #
53
+ # Example constructing all serializers schemas:
54
+ # `Serega::OpenAPI.schemas`
55
+ #
56
+ # Example constructing specific serializers schemas:
57
+ # `Serega::OpenAPI.schemas(Serega::OpenAPI.serializers - [MyBaseSerializer])`
58
+ #
59
+ # Example constructing one serializer schema:
60
+ # `SomeSerializer.openapi_schema`
61
+ #
62
+ # @example
63
+ # class BaseSerializer < Serega
64
+ # plugin :openapi
65
+ # end
66
+ #
67
+ # class UserSerializer < BaseSerializer
68
+ # attribute :name
69
+ #
70
+ # openapi_properties(
71
+ # name: { type: :string }
72
+ # )
73
+ # end
74
+ #
75
+ # class PostSerializer < BaseSerializer
76
+ # attribute :text
77
+ # attribute :user, serializer: UserSerializer, many: false
78
+ # attribute :comments, serializer: PostSerializer, many: true, hide: true
79
+ #
80
+ # openapi_properties(
81
+ # text: { type: :string },
82
+ # user: { type: 'object' }, # `$ref` option will be added automatically when constructing schema
83
+ # comments: { type: 'array' } # `items` option with `$ref` will be added automatically when constructing schema
84
+ # )
85
+ # end
86
+ #
87
+ # puts Serega::OpenAPI.schemas
88
+ # =>
89
+ # {
90
+ # "PostSerializer" => {
91
+ # type: "object",
92
+ # properties: {
93
+ # text: {type: "string"},
94
+ # user: {:$ref => "#/components/schemas/UserSerializer"},
95
+ # comments: {type: "array", items: {:$ref => "#/components/schemas/PostSerializer"}}
96
+ # },
97
+ # required: [:text, :comments],
98
+ # additionalProperties: false
99
+ # },
100
+ # "UserSerializer" => {
101
+ # type: "object",
102
+ # properties: {
103
+ # name: {type: "string"}
104
+ # },
105
+ # required: [:name],
106
+ # additionalProperties: false
107
+ # }
108
+ # }
109
+ #
110
+ module OpenAPI
111
+ # Builder for schema name (used is schemas list). Returns serializer class name
112
+ DEFAULT_SCHEMA_NAME_BUILDER = ->(serializer_class) { serializer_class.name }
113
+
114
+ # Builder for $ref openapi property
115
+ DEFAULT_REF_BUILDER = ->(serializer_class) { "#/components/schemas/#{serializer_class.openapi_schema_name}" }
116
+
117
+ # @return [Symbol] Plugin name
118
+ def self.plugin_name
119
+ :openapi
120
+ end
121
+
122
+ # Checks requirements and loads additional plugins
123
+ #
124
+ # @param serializer_class [Class<Serega>] Current serializer class
125
+ # @param opts [Hash] loaded plugins opts
126
+ #
127
+ # @return [void]
128
+ #
129
+ def self.before_load_plugin(serializer_class, **opts)
130
+ unless serializer_class.plugin_used?(:explicit_many_option)
131
+ serializer_class.plugin :explicit_many_option
132
+ end
133
+ end
134
+
135
+ #
136
+ # Applies plugin code to specific serializer
137
+ #
138
+ # @param serializer_class [Class<Serega>] Current serializer class
139
+ # @param _opts [Hash] Loaded plugins options
140
+ #
141
+ # @return [void]
142
+ #
143
+ def self.load_plugin(serializer_class, **opts)
144
+ require_relative "./lib/modules/config"
145
+ require_relative "./lib/openapi_config"
146
+
147
+ serializer_class.extend(ClassMethods)
148
+ serializer_class::SeregaConfig.include(ConfigInstanceMethods)
149
+
150
+ config = serializer_class.config
151
+ config.opts[:openapi] = {properties: {}}
152
+ openapi_config = serializer_class.config.openapi
153
+ openapi_config.schema_name_builder = opts[:schema_name_builder] || DEFAULT_SCHEMA_NAME_BUILDER
154
+ openapi_config.ref_builder = opts[:ref_builder] || DEFAULT_REF_BUILDER
155
+ end
156
+
157
+ #
158
+ # Adds config options and runs other callbacks after plugin was loaded
159
+ #
160
+ # @param serializer_class [Class<Serega>] Current serializer class
161
+ # @param opts [Hash] loaded plugins opts
162
+ #
163
+ # @return [void]
164
+ #
165
+ def self.after_load_plugin(serializer_class, **opts)
166
+ Serega::OpenAPI.serializers << serializer_class unless serializer_class.equal?(Serega)
167
+ end
168
+
169
+ #
170
+ # Serega additional/patched class methods
171
+ #
172
+ # @see Serega
173
+ #
174
+ module ClassMethods
175
+ #
176
+ # OpenAPI schema for current serializer
177
+ #
178
+ def openapi_schema
179
+ properties = SeregaUtils::EnumDeepDup.call(openapi_properties)
180
+ required_properties = []
181
+
182
+ attributes.each do |attribute_name, attribute|
183
+ add_openapi_property(properties, attribute_name, attribute)
184
+ add_openapi_required_property(required_properties, attribute_name, attribute)
185
+ end
186
+
187
+ {
188
+ type: "object",
189
+ properties: properties,
190
+ required: required_properties,
191
+ additionalProperties: false
192
+ }
193
+ end
194
+
195
+ #
196
+ # Adds new OpenAPI properties and returns all properties
197
+ #
198
+ # @param props [Hash] Specifies new properties
199
+ #
200
+ # @return [Hash] Specified OpenAPI properties
201
+ #
202
+ def openapi_properties(props = FROZEN_EMPTY_HASH)
203
+ config.openapi.properties(props)
204
+ end
205
+
206
+ #
207
+ # Builds OpenAPI schema name using configured builder
208
+ #
209
+ # @return [String] OpenAPI schema name
210
+ #
211
+ def openapi_schema_name
212
+ config.openapi.schema_name_builder.call(self)
213
+ end
214
+
215
+ private
216
+
217
+ def inherited(subclass)
218
+ super
219
+ Serega::OpenAPI.serializers << subclass
220
+ end
221
+
222
+ def add_openapi_property(properties, attribute_name, attribute)
223
+ property = properties[attribute_name] ||= {}
224
+ return unless attribute.relation?
225
+
226
+ ref = attribute.serializer.config.openapi.ref_builder.call(attribute.serializer)
227
+
228
+ if attribute.many
229
+ property[:type] = "array"
230
+ property[:items] ||= {}
231
+ property[:items][:$ref] ||= ref
232
+ else
233
+ property[:$ref] ||= ref
234
+ end
235
+ end
236
+
237
+ def add_openapi_required_property(required_properties, attribute_name, attribute)
238
+ required_properties << attribute_name unless attribute.hide
239
+ end
240
+ end
241
+ end
242
+
243
+ register_plugin(OpenAPI.plugin_name, OpenAPI)
244
+ end
245
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Preloads
6
+ #
7
+ # Serega::SeregaAttribute additional/patched instance methods
8
+ #
9
+ # @see Serega::SeregaAttribute::AttributeInstanceMethods
10
+ #
11
+ module AttributeInstanceMethods
12
+ # @return [Hash, nil] normalized preloads of current attribute
13
+ attr_reader :preloads
14
+
15
+ # @return [Array] normalized preloads_path of current attribute
16
+ attr_reader :preloads_path
17
+
18
+ private
19
+
20
+ def set_normalized_vars(normalizer)
21
+ super
22
+ @preloads = normalizer.preloads
23
+ @preloads_path = normalizer.preloads_path
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Preloads
6
+ #
7
+ # Serega::SeregaAttributeNormalizer additional/patched instance methods
8
+ #
9
+ # @see SeregaAttributeNormalizer::AttributeNormalizerInstanceMethods
10
+ #
11
+ module AttributeNormalizerInstanceMethods
12
+ # @return [Hash,nil] normalized attribute preloads
13
+ def preloads
14
+ return @preloads if instance_variable_defined?(:@preloads)
15
+
16
+ @preloads = prepare_preloads
17
+ end
18
+
19
+ # @return [Array, nil] normalized attribute preloads path
20
+ def preloads_path
21
+ return @preloads_path if instance_variable_defined?(:@preloads_path)
22
+
23
+ @preloads_path = prepare_preloads_path
24
+ end
25
+
26
+ private
27
+
28
+ #
29
+ # Patched in:
30
+ # - plugin :batch (extension :preloads - skips auto preloads when batch option provided)
31
+ #
32
+ def prepare_preloads
33
+ opts = init_opts
34
+ preloads_provided = opts.key?(:preload)
35
+ preloads =
36
+ if preloads_provided
37
+ opts[:preload]
38
+ elsif opts.key?(:serializer) && self.class.serializer_class.config.preloads.auto_preload_attributes_with_serializer
39
+ key
40
+ elsif opts.key?(:delegate) && self.class.serializer_class.config.preloads.auto_preload_attributes_with_delegate
41
+ opts[:delegate].fetch(:to)
42
+ end
43
+
44
+ # Nil and empty hash differs as we can preload nested results to
45
+ # empty hash, but we will skip nested preloading if nil or false provided
46
+ return if preloads_provided && !preloads
47
+
48
+ FormatUserPreloads.call(preloads)
49
+ end
50
+
51
+ def prepare_preloads_path
52
+ path = init_opts.fetch(:preload_path) { default_preload_path(preloads) }
53
+
54
+ if path && path[0].is_a?(Array)
55
+ prepare_many_preload_paths(path)
56
+ else
57
+ prepare_one_preload_path(path)
58
+ end
59
+ end
60
+
61
+ def prepare_one_preload_path(path)
62
+ return unless path
63
+
64
+ case path
65
+ when Array
66
+ path.map(&:to_sym).freeze
67
+ else
68
+ [path.to_sym].freeze
69
+ end
70
+ end
71
+
72
+ def prepare_many_preload_paths(paths)
73
+ paths.map { |path| prepare_one_preload_path(path) }.freeze
74
+ end
75
+
76
+ def default_preload_path(preloads)
77
+ return FROZEN_EMPTY_ARRAY if !preloads || preloads.empty?
78
+
79
+ [preloads.keys.first]
80
+ end
81
+
82
+ #
83
+ # Patch for original `prepare_hide` method
84
+ # @see
85
+ #
86
+ # Marks attribute hidden if auto_hide_attribute_with_preloads option was set and attribute has preloads
87
+ #
88
+ def prepare_hide
89
+ res = super
90
+ return res unless res.nil?
91
+
92
+ if preloads && !preloads.empty?
93
+ self.class.serializer_class.config.preloads.auto_hide_attributes_with_preload || nil
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Preloads
6
+ #
7
+ # Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
8
+ #
9
+ # @see Serega::SeregaValidations::CheckAttributeParams
10
+ #
11
+ module CheckAttributeParamsInstanceMethods
12
+ private
13
+
14
+ def check_opts
15
+ super
16
+ CheckOptPreload.call(opts)
17
+ CheckOptPreloadPath.call(opts)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Preloads
6
+ #
7
+ # Config class additional/patched instance methods
8
+ #
9
+ # @see Serega::SeregaConfig
10
+ #
11
+ module ConfigInstanceMethods
12
+ # @return [Serega::SeregaPlugins::Preloads::PreloadsConfig] `preloads` plugin config
13
+ def preloads
14
+ @preloads ||= PreloadsConfig.new(opts.fetch(:preloads))
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Preloads
6
+ #
7
+ # Serega::SeregaPlanPoint additional/patched instance methods
8
+ #
9
+ # @see Serega::SeregaPlanPoint::InstanceMethods
10
+ #
11
+ module PlanPointInstanceMethods
12
+ #
13
+ # @return [Hash] preloads for nested attributes
14
+ #
15
+ attr_reader :preloads
16
+
17
+ #
18
+ # @return [Array<Symbol>] preloads path for current attribute
19
+ #
20
+ attr_reader :preloads_path
21
+
22
+ private
23
+
24
+ def set_normalized_vars
25
+ super
26
+
27
+ @preloads = prepare_preloads
28
+ @preloads_path = prepare_preloads_path
29
+ end
30
+
31
+ def prepare_preloads
32
+ PreloadsConstructor.call(child_plan)
33
+ end
34
+
35
+ def prepare_preloads_path
36
+ attribute.preloads_path
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Preloads
6
+ #
7
+ # Utility that helps to transform preloads to array of paths
8
+ #
9
+ # Example:
10
+ #
11
+ # call({ a: { b: { c: {}, d: {} } }, e: {} })
12
+ #
13
+ # => [
14
+ # [:a],
15
+ # [:a, :b],
16
+ # [:a, :b, :c],
17
+ # [:a, :b, :d],
18
+ # [:e]
19
+ # ]
20
+ class PreloadPaths
21
+ class << self
22
+ #
23
+ # Transforms user provided preloads to array of paths
24
+ #
25
+ # @param value [Array,Hash,String,Symbol,nil,false] preloads
26
+ #
27
+ # @return [Hash] preloads transformed to hash
28
+ #
29
+ def call(preloads, path = [], result = [])
30
+ preloads = FormatUserPreloads.call(preloads)
31
+
32
+ preloads.each do |key, nested_preloads|
33
+ path << key
34
+ result << path.dup
35
+
36
+ call(nested_preloads, path, result)
37
+ path.pop
38
+ end
39
+
40
+ result
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Preloads
6
+ #
7
+ # Config for `preloads` plugin
8
+ #
9
+ class PreloadsConfig
10
+ # @return [Hash] preloads plugin options
11
+ attr_reader :opts
12
+
13
+ #
14
+ # Initializes PreloadsConfig object
15
+ #
16
+ # @param opts [Hash] options
17
+ #
18
+ # @return [Serega::SeregaPlugins::Preloads::PreloadsConfig]
19
+ #
20
+ def initialize(opts)
21
+ @opts = opts
22
+ end
23
+
24
+ # @!method auto_preload_attributes_with_delegate
25
+ # @return [Boolean, nil] option value
26
+ #
27
+ # @!method auto_preload_attributes_with_delegate=(value)
28
+ # @param value [Boolean] New option value
29
+ # @return [Boolean] New option value
30
+ #
31
+ # @!method auto_preload_attributes_with_serializer
32
+ # @return [Boolean, nil] option value
33
+ #
34
+ # @!method auto_preload_attributes_with_serializer=(value)
35
+ # @param value [Boolean] New option value
36
+ # @return [Boolean] New option value
37
+ #
38
+ # @!method auto_hide_attributes_with_preload
39
+ # @return [Boolean, nil] option value
40
+ #
41
+ # @!method auto_hide_attributes_with_preload=(value)
42
+ # @param value [Boolean] New option value
43
+ # @return [Boolean] New option value
44
+ #
45
+ %i[
46
+ auto_preload_attributes_with_delegate
47
+ auto_preload_attributes_with_serializer
48
+ auto_hide_attributes_with_preload
49
+ ].each do |method_name|
50
+ define_method(method_name) do
51
+ opts.fetch(method_name)
52
+ end
53
+
54
+ define_method("#{method_name}=") do |value|
55
+ raise SeregaError, "Must have boolean value, #{value.inspect} provided" if (value != true) && (value != false)
56
+ opts[method_name] = value
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end