serega 0.1.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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/VERSION +1 -0
  3. data/lib/serega/attribute.rb +130 -0
  4. data/lib/serega/config.rb +48 -0
  5. data/lib/serega/convert.rb +45 -0
  6. data/lib/serega/convert_item.rb +37 -0
  7. data/lib/serega/helpers/serializer_class_helper.rb +11 -0
  8. data/lib/serega/map.rb +49 -0
  9. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +61 -0
  10. data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +95 -0
  11. data/lib/serega/plugins/context_metadata/context_metadata.rb +74 -0
  12. data/lib/serega/plugins/formatters/formatters.rb +49 -0
  13. data/lib/serega/plugins/hide_nil/hide_nil.rb +80 -0
  14. data/lib/serega/plugins/metadata/meta_attribute.rb +74 -0
  15. data/lib/serega/plugins/metadata/metadata.rb +123 -0
  16. data/lib/serega/plugins/metadata/validations/check_block.rb +45 -0
  17. data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +31 -0
  18. data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +31 -0
  19. data/lib/serega/plugins/metadata/validations/check_opts.rb +41 -0
  20. data/lib/serega/plugins/metadata/validations/check_path.rb +53 -0
  21. data/lib/serega/plugins/preloads/lib/enum_deep_freeze.rb +17 -0
  22. data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +53 -0
  23. data/lib/serega/plugins/preloads/lib/main_preload_path.rb +40 -0
  24. data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +60 -0
  25. data/lib/serega/plugins/preloads/preloads.rb +100 -0
  26. data/lib/serega/plugins/preloads/validations/check_opt_preload_path.rb +38 -0
  27. data/lib/serega/plugins/presenter/presenter.rb +111 -0
  28. data/lib/serega/plugins/root/root.rb +65 -0
  29. data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +64 -0
  30. data/lib/serega/plugins/string_modifiers/string_modifiers.rb +32 -0
  31. data/lib/serega/plugins/validate_modifiers/validate.rb +51 -0
  32. data/lib/serega/plugins/validate_modifiers/validate_modifiers.rb +44 -0
  33. data/lib/serega/plugins.rb +51 -0
  34. data/lib/serega/utils/as_json.rb +35 -0
  35. data/lib/serega/utils/enum_deep_dup.rb +43 -0
  36. data/lib/serega/utils/to_hash.rb +52 -0
  37. data/lib/serega/utils/to_json.rb +22 -0
  38. data/lib/serega/validations/attribute/check_block.rb +81 -0
  39. data/lib/serega/validations/attribute/check_name.rb +45 -0
  40. data/lib/serega/validations/attribute/check_opt_hide.rb +20 -0
  41. data/lib/serega/validations/attribute/check_opt_many.rb +20 -0
  42. data/lib/serega/validations/attribute/check_opt_method.rb +25 -0
  43. data/lib/serega/validations/attribute/check_opt_serializer.rb +36 -0
  44. data/lib/serega/validations/attribute/check_opts.rb +36 -0
  45. data/lib/serega/validations/check_allowed_keys.rb +13 -0
  46. data/lib/serega/validations/check_opt_is_bool.rb +14 -0
  47. data/lib/serega/validations/check_opt_is_hash.rb +14 -0
  48. data/lib/serega/version.rb +5 -0
  49. data/lib/serega.rb +265 -0
  50. metadata +94 -0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Plugins
5
+ module Preloads
6
+ class CheckOptPreloadPath
7
+ class << self
8
+ def call(opts)
9
+ return unless opts.key?(:preload_path)
10
+
11
+ value = opts[:preload_path]
12
+ raise Error, "Invalid option :preload_path => #{value.inspect}. Can be provided only when :preload option provided" unless opts[:preload]
13
+ raise Error, "Invalid option :preload_path => #{value.inspect}. Can be provided only when :serializer option provided" unless opts[:serializer]
14
+
15
+ path = Array(value).map!(&:to_sym)
16
+ preloads = FormatUserPreloads.call(opts[:preload])
17
+ allowed_paths = paths(preloads)
18
+ raise Error, "Invalid option :preload_path => #{value.inspect}. Can be one of #{allowed_paths.inspect[1..-2]}" unless allowed_paths.include?(path)
19
+ end
20
+
21
+ private
22
+
23
+ def paths(preloads, path = [], result = [])
24
+ preloads.each do |key, nested_preloads|
25
+ path << key
26
+ result << path.dup
27
+
28
+ paths(nested_preloads, path, result)
29
+ path.pop
30
+ end
31
+
32
+ result
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "forwardable"
5
+
6
+ class Serega
7
+ module Plugins
8
+ #
9
+ # Plugin Presenter adds possibility to use declare Presenter for your objects inside serializer
10
+ #
11
+ # class User < Serega
12
+ # plugin :presenter
13
+ #
14
+ # attribute :name
15
+ #
16
+ # class Presenter
17
+ # def name
18
+ # [first_name, last_name].compact_blank.join(' ')
19
+ # end
20
+ # end
21
+ # end
22
+ module Presenter
23
+ # @return [Symbol] plugin name
24
+ def self.plugin_name
25
+ :presenter
26
+ end
27
+
28
+ #
29
+ # Loads plugin
30
+ #
31
+ # @param serializer_class [Class<Serega>] Current serializer class
32
+ # @param _opts [Hash] Loaded plugins options
33
+ #
34
+ # @return [void]
35
+ #
36
+ def self.load_plugin(serializer_class, **_opts)
37
+ serializer_class.extend(ClassMethods)
38
+ serializer_class::ConvertItem.extend(ConvertItemClassMethods)
39
+ end
40
+
41
+ #
42
+ # Adds Presenter to current serializer
43
+ #
44
+ # @param serializer_class [Class<Serega>] Current serializer class
45
+ # @param _opts [Hash] Loaded plugins options
46
+ #
47
+ # @return [void]
48
+ #
49
+ def self.after_load_plugin(serializer_class, **_opts)
50
+ presenter_class = Class.new(Presenter)
51
+ presenter_class.serializer_class = serializer_class
52
+ serializer_class.const_set(:Presenter, presenter_class)
53
+ end
54
+
55
+ # Presenter class
56
+ class Presenter < SimpleDelegator
57
+ # Presenter instance methods
58
+ module InstanceMethods
59
+ #
60
+ # Delegates all missing methods to serialized object.
61
+ #
62
+ # Creates delegator method after first #method_missing hit to improve
63
+ # performance of following serializations.
64
+ #
65
+ def method_missing(name, *_args, &_block) # rubocop:disable Style/MissingRespondToMissing (base SimpleDelegator class has this method)
66
+ super.tap do
67
+ self.class.def_delegator :__getobj__, name
68
+ end
69
+ end
70
+ end
71
+
72
+ extend Helpers::SerializerClassHelper
73
+ extend Forwardable
74
+ include InstanceMethods
75
+ end
76
+
77
+ # Overrides class methods of included class
78
+ module ClassMethods
79
+ private def inherited(subclass)
80
+ super
81
+
82
+ presenter_class = Class.new(self::Presenter)
83
+ presenter_class.serializer_class = subclass
84
+ subclass.const_set(:Presenter, presenter_class)
85
+ end
86
+
87
+ # Overrides {Serega::ClassMethods#attribute} method, additionally adds method
88
+ # to Presenter to not hit {Serega::Plugins::Presenter::Presenter#method_missing}
89
+ # @see Serega::ClassMethods#attribute
90
+ def attribute(_name, **_opts, &_block)
91
+ super.tap do |attribute|
92
+ self::Presenter.def_delegator(:__getobj__, attribute.key) unless attribute.block
93
+ end
94
+ end
95
+ end
96
+
97
+ # Includes methods to override ConvertItem class
98
+ module ConvertItemClassMethods
99
+ #
100
+ # Replaces serialized object with Presenter.new(object)
101
+ #
102
+ def call(object, *)
103
+ object = serializer_class::Presenter.new(object)
104
+ super
105
+ end
106
+ end
107
+ end
108
+
109
+ register_plugin(Presenter.plugin_name, Presenter)
110
+ end
111
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Plugins
5
+ module Root
6
+ # @return [Symbol] Default response root key
7
+ ROOT_DEFAULT = :data
8
+
9
+ def self.plugin_name
10
+ :root
11
+ end
12
+
13
+ def self.load_plugin(serializer_class, **_opts)
14
+ serializer_class.extend(ClassMethods)
15
+ serializer_class::Convert.include(ConvertInstanceMethods)
16
+ end
17
+
18
+ def self.after_load_plugin(serializer_class, **opts)
19
+ serializer_class.root(opts[:root] || ROOT_DEFAULT, one: opts[:root_one], many: opts[:root_many])
20
+ serializer_class.config[:serialize_keys] << :root
21
+ end
22
+
23
+ module ClassMethods
24
+ #
25
+ # Configures response root key
26
+ #
27
+ # @param root [String, Symbol] Specifies common root when serializing one or multiple objects
28
+ # @param root_one [String, Symbol] Specifies root when serializing one object
29
+ # @param root_many [String, Symbol] Specifies root when serializing multiple objects
30
+ #
31
+ # @return [Hash] Configured root names
32
+ #
33
+ def root(root = nil, one: nil, many: nil)
34
+ one ||= root
35
+ many ||= root
36
+
37
+ one = one.to_sym if one
38
+ many = many.to_sym if many
39
+
40
+ config[:root] = {one: one, many: many}
41
+ end
42
+ end
43
+
44
+ module ConvertInstanceMethods
45
+ def to_h
46
+ hash = super
47
+ root = build_root(opts)
48
+ hash = {root => hash} if root
49
+ hash
50
+ end
51
+
52
+ private
53
+
54
+ def build_root(opts)
55
+ return opts[:root] if opts.key?(:root)
56
+
57
+ root_config = self.class.serializer_class.config[:root]
58
+ many? ? root_config[:many] : root_config[:one]
59
+ end
60
+ end
61
+ end
62
+
63
+ register_plugin(Root.plugin_name, Root)
64
+ end
65
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Plugins
5
+ module StringModifiers
6
+ class ParseStringModifiers
7
+ COMMA = ","
8
+ OPEN_BRACKET = "("
9
+ CLOSE_BRACKET = ")"
10
+
11
+ def self.call(fields)
12
+ return fields unless fields.is_a?(String)
13
+
14
+ new.parse(fields)
15
+ end
16
+
17
+ # user => { user: {} }
18
+ # user(id) => { user: { id: {} } }
19
+ # user(id,name) => { user: { id: {}, name: {} } }
20
+ # user,comments => { user: {}, comments: {} }
21
+ # user(comments(text)) => { user: { comments: { text: {} } } }
22
+ def parse(fields)
23
+ res = {}
24
+ attribute = +""
25
+ path_stack = nil
26
+
27
+ fields.each_char do |char|
28
+ case char
29
+ when COMMA
30
+ add_attribute(res, path_stack, attribute, FROZEN_EMPTY_HASH)
31
+ when CLOSE_BRACKET
32
+ add_attribute(res, path_stack, attribute, FROZEN_EMPTY_HASH)
33
+ path_stack&.pop
34
+ when OPEN_BRACKET
35
+ name = add_attribute(res, path_stack, attribute, {})
36
+ (path_stack ||= []).push(name) if name
37
+ else
38
+ attribute.insert(-1, char)
39
+ end
40
+ end
41
+
42
+ add_attribute(res, path_stack, attribute, FROZEN_EMPTY_HASH)
43
+
44
+ res
45
+ end
46
+
47
+ private
48
+
49
+ def add_attribute(res, path_stack, attribute, nested_attributes = FROZEN_EMPTY_HASH)
50
+ attribute.strip!
51
+ return if attribute.empty?
52
+
53
+ name = attribute.to_sym
54
+ attribute.clear
55
+
56
+ current_attrs = !path_stack || path_stack.empty? ? res : res.dig(*path_stack)
57
+ current_attrs[name] = nested_attributes
58
+
59
+ name
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Plugins
5
+ module StringModifiers
6
+ def self.plugin_name
7
+ :string_modifiers
8
+ end
9
+
10
+ def self.load_plugin(serializer_class, **_opts)
11
+ serializer_class.include(InstanceMethods)
12
+ require_relative "./parse_string_modifiers"
13
+ end
14
+
15
+ module InstanceMethods
16
+ private
17
+
18
+ def prepare_modifiers(opts)
19
+ opts = {
20
+ only: ParseStringModifiers.call(opts[:only]),
21
+ except: ParseStringModifiers.call(opts[:except]),
22
+ with: ParseStringModifiers.call(opts[:with])
23
+ }
24
+
25
+ super
26
+ end
27
+ end
28
+ end
29
+
30
+ register_plugin(StringModifiers.plugin_name, StringModifiers)
31
+ end
32
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Plugins
5
+ module ValidateModifiers
6
+ class Validate
7
+ class << self
8
+ def call(serializer_class, fields)
9
+ return unless fields
10
+
11
+ validate(serializer_class, fields, [])
12
+ end
13
+
14
+ private
15
+
16
+ def validate(serializer_class, fields, prev_names)
17
+ fields.each do |name, nested_fields|
18
+ attribute = serializer_class.attributes[name]
19
+
20
+ raise_error(name, prev_names) unless attribute
21
+ next if nested_fields.empty?
22
+
23
+ raise_nested_error(name, prev_names, nested_fields) unless attribute.relation?
24
+ nested_serializer = attribute.serializer
25
+ validate(nested_serializer, nested_fields, prev_names + [name])
26
+ end
27
+ end
28
+
29
+ def raise_error(name, prev_names)
30
+ field_name = field_name(name, prev_names)
31
+
32
+ raise Serega::Error, "Attribute #{field_name} not exists"
33
+ end
34
+
35
+ def raise_nested_error(name, prev_names, nested_fields)
36
+ field_name = field_name(name, prev_names)
37
+ first_nested = nested_fields.keys.first
38
+
39
+ raise Serega::Error, "Attribute #{field_name} is not a relation to add '#{first_nested}' attribute"
40
+ end
41
+
42
+ def field_name(name, prev_names)
43
+ res = "'#{name}'"
44
+ res += " ('#{prev_names.join(".")}.#{name}')" if prev_names.any?
45
+ res
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Plugins
5
+ module ValidateModifiers
6
+ def self.plugin_name
7
+ :validate_modifiers
8
+ end
9
+
10
+ def self.load_plugin(serializer_class, **_opts)
11
+ serializer_class.include(InstanceMethods)
12
+ require_relative "./validate"
13
+ end
14
+
15
+ def self.after_load_plugin(serializer_class, **opts)
16
+ serializer_class.config[:validate_modifiers] = {auto: opts.fetch(:auto, true)}
17
+ end
18
+
19
+ module InstanceMethods
20
+ # Raises error if some modifiers are invalid
21
+ def validate_modifiers
22
+ @modifiers_validated ||= begin
23
+ Validate.call(self.class, opts[:only])
24
+ Validate.call(self.class, opts[:except])
25
+ Validate.call(self.class, opts[:with])
26
+ true
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def initialize(opts)
33
+ super
34
+ validate_modifiers if self.class.config[:validate_modifiers][:auto]
35
+ end
36
+ end
37
+
38
+ module InstanceMethods
39
+ end
40
+ end
41
+
42
+ register_plugin(ValidateModifiers.plugin_name, ValidateModifiers)
43
+ end
44
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ # Module in which all Serega plugins should be stored
5
+ module Plugins
6
+ @plugins = {}
7
+
8
+ class << self
9
+ #
10
+ # Registers given plugin to be able to load it using symbol name.
11
+ #
12
+ # @example Register plugin
13
+ # Serega::Plugins.register_plugin(:plugin_name, PluginModule)
14
+ def register_plugin(name, mod)
15
+ @plugins[name] = mod
16
+ end
17
+
18
+ #
19
+ # Loads plugin code and returns plugin core module.
20
+ #
21
+ # @param name [Symbol, Module] plugin name or plugin itself
22
+ #
23
+ # @raise [Error] Raises Error when plugin was not found
24
+ #
25
+ # @example Find plugin when providing name
26
+ # Serega::Plugins.find_plugin(:presenter) # => Serega::Plugins::Presenter
27
+ #
28
+ # @example Find plugin when providing plugin itself
29
+ # Serega::Plugins.find_plugin(Presenter) # => Presenter
30
+ #
31
+ # @return [Class<Module>] Plugin core module
32
+ #
33
+ def find_plugin(name)
34
+ return name if name.is_a?(Module)
35
+ return @plugins[name] if @plugins.key?(name)
36
+
37
+ require_plugin(name)
38
+
39
+ @plugins[name] || raise(Error, "Plugin '#{name}' did not register itself correctly")
40
+ end
41
+
42
+ private
43
+
44
+ def require_plugin(name)
45
+ require "serega/plugins/#{name}/#{name}"
46
+ rescue LoadError
47
+ raise Error, "Plugin '#{name}' does not exist"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Utils
5
+ class AsJSON
6
+ DOUBLE_QUOTE = '"'
7
+
8
+ class << self
9
+ def call(data, to_json:)
10
+ case data
11
+ when Hash
12
+ data.each_with_object({}) do |(key, value), new_data|
13
+ new_key = key.to_s
14
+ new_value = call(value, to_json: to_json)
15
+ new_data[new_key] = new_value
16
+ end
17
+ when Array
18
+ data.map { |value| call(value, to_json: to_json) }
19
+ when NilClass, Integer, Float, String, TrueClass, FalseClass
20
+ data
21
+ when Symbol
22
+ data.to_s
23
+ else
24
+ res = to_json.call(data)
25
+ if res.start_with?(DOUBLE_QUOTE) && res.end_with?(DOUBLE_QUOTE)
26
+ res.delete_prefix!(DOUBLE_QUOTE)
27
+ res.delete_suffix!(DOUBLE_QUOTE)
28
+ end
29
+ res
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Utils
5
+ # Duplicates nested hashes and arrays
6
+ class EnumDeepDup
7
+ DUP = {
8
+ Hash => ->(data) { dup_hash_values(data) },
9
+ Array => ->(data) { dup_array_values(data) }
10
+ }.freeze
11
+ private_constant :DUP
12
+
13
+ class << self
14
+ #
15
+ # Deeply duplicate provided data
16
+ #
17
+ # @param data [Hash, Array] Data to duplicate
18
+ #
19
+ # @return [Hash, Array] Duplicated data
20
+ #
21
+ def call(data)
22
+ duplicate_data = data.dup
23
+ DUP.fetch(duplicate_data.class).call(duplicate_data)
24
+ duplicate_data
25
+ end
26
+
27
+ private
28
+
29
+ def dup_hash_values(duplicate_data)
30
+ duplicate_data.each do |key, value|
31
+ duplicate_data[key] = call(value) if value.is_a?(Enumerable)
32
+ end
33
+ end
34
+
35
+ def dup_array_values(duplicate_data)
36
+ duplicate_data.each_with_index do |value, index|
37
+ duplicate_data[index] = call(value) if value.is_a?(Enumerable)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Utils
5
+ class ToHash
6
+ module ClassMethods
7
+ def call(value)
8
+ case value
9
+ when Array then array_to_hash(value)
10
+ when Hash then hash_to_hash(value)
11
+ when NilClass, FalseClass then nil_to_hash(value)
12
+ when String then string_to_hash(value)
13
+ when Symbol then symbol_to_hash(value)
14
+ else raise Error, "Cant convert #{value.class} class object to hash"
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def array_to_hash(values)
21
+ return Serega::FROZEN_EMPTY_HASH if values.empty?
22
+
23
+ values.each_with_object({}) do |value, obj|
24
+ obj.merge!(call(value))
25
+ end
26
+ end
27
+
28
+ def hash_to_hash(values)
29
+ return Serega::FROZEN_EMPTY_HASH if values.empty?
30
+
31
+ values.each_with_object({}) do |(key, value), obj|
32
+ obj[key.to_sym] = call(value)
33
+ end
34
+ end
35
+
36
+ def nil_to_hash(_value)
37
+ Serega::FROZEN_EMPTY_HASH
38
+ end
39
+
40
+ def string_to_hash(value)
41
+ symbol_to_hash(value.to_sym)
42
+ end
43
+
44
+ def symbol_to_hash(value)
45
+ {value => Serega::FROZEN_EMPTY_HASH}
46
+ end
47
+ end
48
+
49
+ extend ClassMethods
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Utils
5
+ class ToJSON
6
+ class << self
7
+ def call(data)
8
+ json_adapter.dump(data)
9
+ end
10
+
11
+ private
12
+
13
+ def json_adapter
14
+ @json_adapter ||= begin
15
+ require "json"
16
+ ::JSON
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ class Attribute
5
+ class CheckBlock
6
+ class << self
7
+ #
8
+ # Checks :value option or a block provided with attribute
9
+ # Must have up to two arguments - object and context.
10
+ # It should not have any *rest or **key arguments
11
+ #
12
+ # @example without arguments
13
+ # attribute(:email) { CONSTANT_EMAIL }
14
+ #
15
+ # @example with one argument
16
+ # attribute(:email) { |obj| obj.confirmed_email }
17
+ #
18
+ # @example with two arguments
19
+ # attribute(:email) { |obj, context| context['is_current'] ? obj.email : nil }
20
+ #
21
+ # @param opts [Proc] Attribute opts, we will check :value option
22
+ # @param block [Proc] Block that returns serialized attribute value
23
+ #
24
+ # @raise [Error] Error that block has invalid arguments
25
+ #
26
+ # @return [void]
27
+ #
28
+ def call(opts, block)
29
+ check_both_provided(opts, block)
30
+
31
+ if block
32
+ check_block(block)
33
+ elsif opts.key?(:value)
34
+ check_value(opts[:value])
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def check_both_provided(opts, block)
41
+ if opts.key?(:value) && block
42
+ raise Error, both_error
43
+ end
44
+ end
45
+
46
+ def check_block(block)
47
+ params = block.parameters
48
+ return if (params.count <= 2) && params.all? { |par| par[0] == :opt }
49
+
50
+ raise Error, block_error
51
+ end
52
+
53
+ def check_value(value)
54
+ raise Error, value_error unless value.is_a?(Proc)
55
+
56
+ params = value.parameters
57
+
58
+ if value.lambda?
59
+ return if (params.count == 2) && params.all? { |par| par[0] == :req }
60
+ elsif (params.count <= 2) && params.all? { |par| par[0] == :opt }
61
+ return
62
+ end
63
+
64
+ raise Error, value_error
65
+ end
66
+
67
+ def block_error
68
+ "Block can have maximum two regular parameters (no **keyword or *array args)"
69
+ end
70
+
71
+ def value_error
72
+ "Option :value must be a Proc that is able to accept two parameters (no **keyword or *array args)"
73
+ end
74
+
75
+ def both_error
76
+ "Block and a :value option can not be provided together"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end