boof-not-naughty 0.6.2
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.
- data/CHANGELOG.rdoc +57 -0
- data/COPYING +18 -0
- data/README.rdoc +59 -0
- data/Rakefile +126 -0
- data/lib/core_extensions.rb +20 -0
- data/lib/not_naughty.rb +86 -0
- data/lib/not_naughty/class_methods.rb +78 -0
- data/lib/not_naughty/error_handler.rb +48 -0
- data/lib/not_naughty/instance_methods.rb +20 -0
- data/lib/not_naughty/validation.rb +132 -0
- data/lib/not_naughty/validations/acceptance_validation.rb +46 -0
- data/lib/not_naughty/validations/confirmation_validation.rb +49 -0
- data/lib/not_naughty/validations/format_validation.rb +71 -0
- data/lib/not_naughty/validations/length_validation.rb +91 -0
- data/lib/not_naughty/validations/numericality_validation.rb +43 -0
- data/lib/not_naughty/validations/presence_validation.rb +32 -0
- data/lib/not_naughty/validator.rb +125 -0
- data/lib/not_naughty/violation.rb +36 -0
- data/spec/error_handler_spec.rb +21 -0
- data/spec/not_naughty_spec.rb +82 -0
- data/spec/rcov.opts +4 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/validation_spec.rb +117 -0
- data/spec/validations_spec.rb +267 -0
- data/spec/validator_spec.rb +132 -0
- data/spec/violation_spec.rb +36 -0
- metadata +102 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
module Tree #:nodoc:
|
2
|
+
class TreeNode #:nodoc:
|
3
|
+
|
4
|
+
def closest(obj)
|
5
|
+
if @name == obj then self
|
6
|
+
elsif @name > obj
|
7
|
+
closest = self
|
8
|
+
@children.any? { |child| node = child.closest(obj) and closest = node }
|
9
|
+
|
10
|
+
closest
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module NotNaughty
|
18
|
+
class ErrorHandler
|
19
|
+
|
20
|
+
def initialize(handler = Kernel)
|
21
|
+
@handles = Tree::TreeNode.new Exception, proc { |e| handler.raise e }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Calls closest handle with exception.
|
25
|
+
def raise(exception)
|
26
|
+
handle = @handles.closest exception.class
|
27
|
+
handle.content.call exception
|
28
|
+
end
|
29
|
+
|
30
|
+
# Inserts handle into the ordered tree.
|
31
|
+
def handle(exception_class, &block)
|
32
|
+
closest_handle = @handles.closest exception_class
|
33
|
+
|
34
|
+
if closest_handle == exception_class then closest_handle.content = block
|
35
|
+
else
|
36
|
+
new_handle = Tree::TreeNode.new exception_class, block
|
37
|
+
|
38
|
+
closest_handle.children do |child|
|
39
|
+
exception_class > child.name and
|
40
|
+
new_handle << closest_handle.remove!(child)
|
41
|
+
end
|
42
|
+
|
43
|
+
closest_handle << new_handle
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
module InstanceMethods
|
3
|
+
|
4
|
+
# Returns instance of Errors.
|
5
|
+
def errors() @errors ||= ::NotNaughty::Violation.new end
|
6
|
+
|
7
|
+
# Returns true if all validations passed
|
8
|
+
def valid?
|
9
|
+
validate
|
10
|
+
errors.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
# Clears errors and validates.
|
14
|
+
def validate
|
15
|
+
errors.clear
|
16
|
+
self.class.validator.invoke self
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == The superclass for Validations.
|
4
|
+
#
|
5
|
+
# See new for more information.
|
6
|
+
class Validation
|
7
|
+
|
8
|
+
# Returns array of paths which are scanned for validations by load.
|
9
|
+
def self.load_paths
|
10
|
+
@load_paths
|
11
|
+
end
|
12
|
+
@load_paths = [File.join(%W[#{ File.dirname __FILE__ } validations])]
|
13
|
+
PATTERN = File.join %w[%s ** %s_validation.rb]
|
14
|
+
|
15
|
+
# Loads validations from load_paths.
|
16
|
+
def self.load(*validations)
|
17
|
+
validations.each do |validation|
|
18
|
+
@load_paths.each do |load_path|
|
19
|
+
pattern = PATTERN % [load_path, validation]
|
20
|
+
Dir[pattern].each { |validation_path| require validation_path }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
extend Observable
|
26
|
+
def self.inherited(descendant) #:nodoc:
|
27
|
+
changed and notify_observers(descendant)
|
28
|
+
|
29
|
+
descendant.
|
30
|
+
instance_variable_set :@observer_peers, @observer_peers.clone
|
31
|
+
end
|
32
|
+
|
33
|
+
# Builds validations.
|
34
|
+
#
|
35
|
+
# <b>Example:</b>
|
36
|
+
# NotNaughty::Validation.new :temp, :if => :water? do |obj, attr, val|
|
37
|
+
# obj.errors.add attr, 'too hot' unless val < 100
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# <b>Like:</b>
|
41
|
+
# class TempValidation < NotNaughty::Validation
|
42
|
+
# def initialize(opts, attributes)
|
43
|
+
# super opts, attributes method(:temp_validation)
|
44
|
+
# end
|
45
|
+
# def temp_validation(obj, attr, val)
|
46
|
+
# obj.errors.add attr, 'too hot' unless val < 100
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# Validation.new TempValidation, :temp, :if => :water?
|
51
|
+
#
|
52
|
+
# The last one also notifies all Observers of Validation (see
|
53
|
+
# Builder#update). If Builder#update is called because <Name>Validation
|
54
|
+
# is inherited from Validation the ValidationBuilder gets the method
|
55
|
+
# validates_<name>_of and so does the classes that included the Builder.
|
56
|
+
def self.new(*params, &block)
|
57
|
+
attributes = if params.first.is_a? Class and params.first < self
|
58
|
+
klass = params.shift
|
59
|
+
klass.new(*params, &block)
|
60
|
+
else
|
61
|
+
options = params.extract_options!
|
62
|
+
instance = allocate
|
63
|
+
instance.send :initialize, options, params.map {|p|p.to_sym}, &block
|
64
|
+
instance
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
attr_reader :attributes
|
69
|
+
|
70
|
+
def initialize(opts, attributes, &block) #:nodoc:
|
71
|
+
build_conditions opts[:if], opts[:unless]
|
72
|
+
@attributes, @block, @opts = attributes, block, opts
|
73
|
+
end
|
74
|
+
|
75
|
+
def call_without_conditions(obj, attr, value) #:nodoc:
|
76
|
+
@block.call obj, attr, value
|
77
|
+
end
|
78
|
+
alias_method :call, :call_without_conditions
|
79
|
+
|
80
|
+
def call_with_conditions(obj, attr, value) #:nodoc:
|
81
|
+
if @conditions.all? { |c| c.evaluate obj }
|
82
|
+
call_without_conditions obj, attr, value
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
def build_conditions(p, n) #:nodoc:
|
88
|
+
@conditions = []
|
89
|
+
[p].flatten.each {|c| @conditions << Condition.new(c) if c }
|
90
|
+
[n].flatten.each {|c| @conditions << Condition.new(c, false) if c }
|
91
|
+
|
92
|
+
(class << self; self; end).module_eval do
|
93
|
+
alias_method :call, :call_with_conditions
|
94
|
+
end unless @conditions.empty?
|
95
|
+
end
|
96
|
+
|
97
|
+
# Conditions for use in Validations are usually used with Validations.
|
98
|
+
class Condition
|
99
|
+
|
100
|
+
# An instance of Condition accepts Symbols, UnboundMethods or anything
|
101
|
+
# that responds to :call.
|
102
|
+
#
|
103
|
+
# The following examples are similiar to each other:
|
104
|
+
#
|
105
|
+
# NotNaughty::Validation::Condition.new proc {|o| o.nil?}
|
106
|
+
# NotNaughty::Validation::Condition.new :nil?
|
107
|
+
# NotNaughty::Validation::Condition.new Object.instance_method(:nil?)
|
108
|
+
def self.new(condition, positive = true)
|
109
|
+
instance = allocate
|
110
|
+
instance.instance_variable_set :@condition, condition
|
111
|
+
|
112
|
+
block = case condition
|
113
|
+
when Symbol then positive ?
|
114
|
+
proc { |o| o.send @condition } :
|
115
|
+
proc { |o| not o.send @condition }
|
116
|
+
when UnboundMethod then positive ?
|
117
|
+
proc { |o| @condition.bind(o).call } :
|
118
|
+
proc { |o| not @condition.bind(o).call }
|
119
|
+
else positive ?
|
120
|
+
proc { |o| @condition.call o } :
|
121
|
+
proc { |o| not @condition.call o }
|
122
|
+
end
|
123
|
+
|
124
|
+
(class << instance; self; end).
|
125
|
+
module_eval { define_method(:evaluate, &block) }
|
126
|
+
|
127
|
+
instance
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Validates acceptance of obj's attribute against a fixed value via <tt>:eql?</tt> call.
|
4
|
+
#
|
5
|
+
# Unless the validation succeeds an error hash (:attribute => :message)
|
6
|
+
# is added to the obj's instance of Errors.
|
7
|
+
#
|
8
|
+
# <b>Options:</b>
|
9
|
+
# <tt>:accept</tt>:: object to check against (via :eql?)
|
10
|
+
# <tt>:message</tt>:: see NotNaughty::Errors for details
|
11
|
+
# <tt>:if</tt>:: see NotNaughty::Validation::Condition for details
|
12
|
+
# <tt>:unless</tt>:: see NotNaughty::Validation::Condition for details
|
13
|
+
#
|
14
|
+
# <b>Example:</b>
|
15
|
+
#
|
16
|
+
# invalid = 'abc'
|
17
|
+
# valid = '1'
|
18
|
+
# def invalid.errors() @errors ||= NotNauthy::Errors.new end
|
19
|
+
# def valid.errors() @errors ||= NotNauthy::Errors.new end
|
20
|
+
#
|
21
|
+
# AcceptanceValidation.new({}, :to_s).call invalid, :to_s, 'abc'
|
22
|
+
# invalid.errors.on(:to_s).any? # => true
|
23
|
+
#
|
24
|
+
# AcceptanceValidation.new({}, :to_s).call valid, :to_s, '1'
|
25
|
+
# valid.errors.on(:to_s).any? # => false
|
26
|
+
class AcceptanceValidation < Validation
|
27
|
+
|
28
|
+
def initialize(opts, attributes) #:nodoc:
|
29
|
+
__message, __accept =
|
30
|
+
opts[:message] || '#{"%s".humanize} not accepted.',
|
31
|
+
opts[:accept] || '1'
|
32
|
+
|
33
|
+
if opts[:allow_blank] or opts.fetch(:allow_nil, true)
|
34
|
+
__allow = if opts[:allow_blank] then :blank? else :nil? end
|
35
|
+
super opts, attributes do |o, a, v|
|
36
|
+
o.errors.add a, __message unless v.send __allow or __accept.eql? v
|
37
|
+
end
|
38
|
+
else
|
39
|
+
super opts, attributes do |o, a, v|
|
40
|
+
o.errors.add a, __message unless __accept.eql? v
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Validates confirmaton of obj's attribute via <tt>:eql?</tt> call against the _appropiate_ confirmation attribute.
|
4
|
+
#
|
5
|
+
# Unless the validation succeeds an error hash (:attribute => :message)
|
6
|
+
# is added to the obj's instance of Errors.
|
7
|
+
#
|
8
|
+
# <b>Options:</b>
|
9
|
+
# <tt>:message</tt>:: see NotNaughty::Errors for details
|
10
|
+
# <tt>:if</tt>:: see NotNaughty::Validation::Condition for details
|
11
|
+
# <tt>:unless</tt>:: see NotNaughty::Validation::Condition for details
|
12
|
+
#
|
13
|
+
# <b>Example:</b>
|
14
|
+
#
|
15
|
+
# obj = 'abc'
|
16
|
+
# def obj.errors() @errors ||= NotNauthy::Errors.new end
|
17
|
+
# def obj.to_s_confirmation() '123 end
|
18
|
+
#
|
19
|
+
# ConfirmationValidation.new({}, :to_s).call obj, :to_s, 'abc'
|
20
|
+
# obj.errors.on(:to_s).any? # => true
|
21
|
+
class ConfirmationValidation < Validation
|
22
|
+
|
23
|
+
def initialize(valid, attributes) #:nodoc:
|
24
|
+
valid[:message] ||= '%s could not be confirmed.'
|
25
|
+
|
26
|
+
if valid[:allow_blank] || valid[:allow_nil]
|
27
|
+
valid[:allow] = valid[:allow_blank] ? :blank? : :nil?
|
28
|
+
super valid, attributes, &confirmation_block_with_exception(valid)
|
29
|
+
else
|
30
|
+
super valid, attributes, &confirmation_block(valid)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
def confirmation_block_with_exception(valid)
|
36
|
+
proc do |obj, attr, value|
|
37
|
+
value.send valid[:allow] or
|
38
|
+
obj.send(:"#{ attr }_confirmation") == value or
|
39
|
+
obj.errors.add attr, valid[:message]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
def confirmation_block(valid)
|
43
|
+
proc do |obj, attr, value|
|
44
|
+
obj.send(:"#{ attr }_confirmation") == value or
|
45
|
+
obj.errors.add attr, valid[:message]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Validates format of obj's attribute via the <tt>:match</tt> method.
|
4
|
+
#
|
5
|
+
# Unless the validation succeeds an error hash (:attribute => :message)
|
6
|
+
# is added to the obj's instance of Errors.
|
7
|
+
#
|
8
|
+
# <b>Options:</b>
|
9
|
+
# <tt>:with</tt>:: object that checks via a <tt>:match</tt> call
|
10
|
+
# <tt>:message</tt>:: see NotNaughty::Errors for details
|
11
|
+
# <tt>:if</tt>:: see NotNaughty::Validation::Condition for details
|
12
|
+
# <tt>:unless</tt>:: see NotNaughty::Validation::Condition for details
|
13
|
+
#
|
14
|
+
# <b>Example:</b>
|
15
|
+
#
|
16
|
+
# obj = 'abc'
|
17
|
+
# def obj.errors() @errors ||= NotNauthy::Errors.new end
|
18
|
+
#
|
19
|
+
# FormatValidation.new({:with => /[a-z]/}, :to_s).call obj, :to_s, obj
|
20
|
+
# obj.errors.on(:to_s).any? # => false
|
21
|
+
#
|
22
|
+
# FormatValidation.new({:with => /[A-Z]/}, :to_s).call obj, :to_s, obj
|
23
|
+
# obj.errors.on(:to_s) # => ["Format of to_s does not match."]
|
24
|
+
#
|
25
|
+
# <b>Use predefined format expressions:</b>
|
26
|
+
#
|
27
|
+
# obj = 'Valid Address <foo@bar.baz>'
|
28
|
+
# def obj.errors() @errors ||= NotNauthy::Errors.new end
|
29
|
+
#
|
30
|
+
# FormatValidation.new({:with => :email}, :to_s).call obj, :to_s, obj
|
31
|
+
# obj.errors.on(:to_s).any? # => false
|
32
|
+
class FormatValidation < Validation
|
33
|
+
|
34
|
+
# Predefined matchers.
|
35
|
+
PREDEFINED = {
|
36
|
+
:email => /[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/,
|
37
|
+
:ip => /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/
|
38
|
+
}
|
39
|
+
|
40
|
+
def initialize(valid, attributes) #:nodoc:
|
41
|
+
valid[:with] = PREDEFINED.fetch valid[:with] if valid[:with].is_a? Symbol
|
42
|
+
valid[:with].respond_to? :match or
|
43
|
+
raise ArgumentError, "#{ valid[:with].inspect } doesn't :match"
|
44
|
+
|
45
|
+
valid[:message] ||= '%s does not match format.'
|
46
|
+
|
47
|
+
if valid[:allow_blank] || valid[:allow_nil]
|
48
|
+
valid[:allow] = valid[:allow_blank] ? :blank? : :nil?
|
49
|
+
super valid, attributes, &matching_block_with_exception(valid)
|
50
|
+
else
|
51
|
+
super valid, attributes, &matching_block(valid)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
def matching_block_with_exception(valid)
|
57
|
+
proc do |obj, attr, value|
|
58
|
+
value.send valid[:allow] or
|
59
|
+
valid[:with].match value or
|
60
|
+
obj.errors.add attr, valid[:message]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
def matching_block(valid)
|
64
|
+
proc do |obj, attr, value|
|
65
|
+
valid[:with].match value or
|
66
|
+
obj.errors.add attr, valid[:message]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Validates length of obj's attribute via the <tt>:length</tt> method.
|
4
|
+
#
|
5
|
+
# Unless the validation succeeds an error hash (:attribute => :message)
|
6
|
+
# is added to the obj's instance of Errors.
|
7
|
+
#
|
8
|
+
# <b>Options:</b>
|
9
|
+
# <tt>:message</tt>:: see NotNaughty::Errors for details
|
10
|
+
# <tt>:if</tt>:: see NotNaughty::Validation::Condition for details
|
11
|
+
# <tt>:unless</tt>:: see NotNaughty::Validation::Condition for details
|
12
|
+
#
|
13
|
+
# <b>Boundaries (by precendence):</b>
|
14
|
+
# <tt>:is</tt>:: valid length
|
15
|
+
# <tt>:within</tt>:: valid range of length
|
16
|
+
# <tt>:minimum</tt>:: maximum length
|
17
|
+
# <tt>:maximum</tt>:: minimum length
|
18
|
+
#
|
19
|
+
# If both, <tt>:minimum</tt> and <tt>:maximum</tt> are provided they're
|
20
|
+
# converted to :within. Each boundary type has its own default message:
|
21
|
+
# precise:: "Length of %s is not equal to #{__length}."
|
22
|
+
# range:: "Length of %s is not within #{__range.first} and #{__range.last}."
|
23
|
+
# lower:: "Length of %s is smaller than #{__boundary}."
|
24
|
+
# upper:: "Length of %s is greater than #{__boundary}."
|
25
|
+
#
|
26
|
+
# <b>Example:</b>
|
27
|
+
#
|
28
|
+
# obj = %w[a sentence with five words] #
|
29
|
+
# def obj.errors() @errors ||= NotNauthy::Errors.new end
|
30
|
+
#
|
31
|
+
# LengthValidation.new({:minimum => 4}, :to_a).
|
32
|
+
# call obj, :to_a, %w[a sentence with five words]
|
33
|
+
# obj.errors.on(:to_s).any? # => false
|
34
|
+
#
|
35
|
+
# LengthValidation.new({:within => 1..4}, :to_a).
|
36
|
+
# call obj, :to_a, %w[a sentence with five words]
|
37
|
+
# obj.errors.on(:to_s).any? # => true
|
38
|
+
#
|
39
|
+
# I'm sorry for using eval here...
|
40
|
+
class LengthValidation < Validation
|
41
|
+
|
42
|
+
TEMPLATE = <<-BLOCK
|
43
|
+
proc do |obj, attr, value|
|
44
|
+
value.%s or %s value.length or
|
45
|
+
obj.errors.add attr, %s
|
46
|
+
end
|
47
|
+
BLOCK
|
48
|
+
|
49
|
+
def initialize(valid, attributes) #:nodoc:
|
50
|
+
super valid, attributes, &build_block(valid)
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
def build_block(valid) #:nodoc:
|
55
|
+
if valid[:is] then is_block valid
|
56
|
+
elsif valid[:within] or valid[:minimum] && valid[:maximum] then within_block valid
|
57
|
+
elsif valid[:minimum] then minimum_block valid
|
58
|
+
elsif valid[:maximum] then maximum_block valid
|
59
|
+
else
|
60
|
+
raise ArgumentError, 'no boundary given'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def is_block(valid)
|
65
|
+
valid[:message] ||= "Length of %s is not equal to #{ valid[:is] }."
|
66
|
+
|
67
|
+
eval TEMPLATE % [ valid[:allow_blank] ? :blank? : :nil?,
|
68
|
+
"#{ valid[:is].inspect } ==", valid[:message].inspect ]
|
69
|
+
end
|
70
|
+
def within_block(valid)
|
71
|
+
valid[:within] ||= Range.new valid[:minimum], valid[:maximum]
|
72
|
+
valid[:message] ||= "Length of %s is not within #{ valid[:within].min } and #{ valid[:within].max }."
|
73
|
+
|
74
|
+
eval TEMPLATE % [ valid[:allow_blank] ? :blank? : :nil?,
|
75
|
+
"(#{ valid[:within].inspect }).include?", valid[:message].inspect ]
|
76
|
+
end
|
77
|
+
def minimum_block(valid)
|
78
|
+
valid[:message] ||= "Length of %s is less than #{ valid[:minimum] }."
|
79
|
+
|
80
|
+
eval TEMPLATE % [ valid[:allow_blank] ? :blank? : :nil?,
|
81
|
+
"#{ valid[:minimum].inspect } <=", valid[:message].inspect ]
|
82
|
+
end
|
83
|
+
def maximum_block(valid)
|
84
|
+
valid[:message] ||= "Length of %s is greater than #{ valid[:maximum] }."
|
85
|
+
|
86
|
+
eval TEMPLATE % [ valid[:allow_blank] ? :blank? : :nil?,
|
87
|
+
"#{ valid[:maximum].inspect } >=", valid[:message].inspect ]
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|