serega 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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