not-naughty 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +46 -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/builder.rb +68 -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 +126 -0
- data/lib/not_naughty/validations/acceptance_validation.rb +46 -0
- data/lib/not_naughty/validations/confirmation_validation.rb +41 -0
- data/lib/not_naughty/validations/format_validation.rb +55 -0
- data/lib/not_naughty/validations/length_validation.rb +95 -0
- data/lib/not_naughty/validations/numericality_validation.rb +43 -0
- data/lib/not_naughty/validations/presence_validation.rb +31 -0
- data/lib/not_naughty/validator.rb +125 -0
- data/lib/not_naughty/violation.rb +36 -0
- data/spec/builder_spec.rb +80 -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 +7 -0
- data/spec/validation_spec.rb +118 -0
- data/spec/validations_spec.rb +267 -0
- data/spec/validator_spec.rb +132 -0
- data/spec/violation_spec.rb +36 -0
- metadata +93 -0
@@ -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,126 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == The superclass for Validations.
|
4
|
+
#
|
5
|
+
# See new for more information.
|
6
|
+
class Validation
|
7
|
+
|
8
|
+
BASEDIR = File.dirname __FILE__
|
9
|
+
# Loader pattern
|
10
|
+
PATTERN = File.join BASEDIR, %w[validations ** %s_validation.rb]
|
11
|
+
|
12
|
+
# Loads validations.
|
13
|
+
def self.load(*validations)
|
14
|
+
validations.each do |validation|
|
15
|
+
Dir.glob(PATTERN % validation).each { |path| require path }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
extend Observable
|
20
|
+
def self.inherited(descendant) #:nodoc:
|
21
|
+
changed and notify_observers(descendant)
|
22
|
+
|
23
|
+
descendant.
|
24
|
+
instance_variable_set :@observer_peers, @observer_peers.clone
|
25
|
+
end
|
26
|
+
|
27
|
+
# Builds validations.
|
28
|
+
#
|
29
|
+
# <b>Example:</b>
|
30
|
+
# NotNaughty::Validation.new :temp, :if => :water? do |obj, attr, val|
|
31
|
+
# obj.errors.add attr, 'too hot' unless val < 100
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# <b>Like:</b>
|
35
|
+
# class TempValidation < NotNaughty::Validation
|
36
|
+
# def initialize(opts, attributes)
|
37
|
+
# super opts, attributes method(:temp_validation)
|
38
|
+
# end
|
39
|
+
# def temp_validation(obj, attr, val)
|
40
|
+
# obj.errors.add attr, 'too hot' unless val < 100
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Validation.new TempValidation, :temp, :if => :water?
|
45
|
+
#
|
46
|
+
# The last one also notifies all Observers of Validation (see
|
47
|
+
# Builder#update). If Builder#update is called because <Name>Validation
|
48
|
+
# is inherited from Validation the ValidationBuilder gets the method
|
49
|
+
# validates_<name>_of and so does the classes that included the Builder.
|
50
|
+
def self.new(*params, &block)
|
51
|
+
attributes = if params.first.is_a? Class and params.first < self
|
52
|
+
klass = params.shift
|
53
|
+
klass.new(*params, &block)
|
54
|
+
else
|
55
|
+
options = params.extract_options!
|
56
|
+
instance = allocate
|
57
|
+
instance.send :initialize, options, params.map {|p|p.to_sym}, &block
|
58
|
+
instance
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_reader :attributes
|
63
|
+
|
64
|
+
def initialize(opts, attributes, &block) #:nodoc:
|
65
|
+
build_conditions opts[:if], opts[:unless]
|
66
|
+
@attributes, @block, @opts = attributes, block, opts
|
67
|
+
end
|
68
|
+
|
69
|
+
def call_without_conditions(obj, attr, value) #:nodoc:
|
70
|
+
@block.call obj, attr, value
|
71
|
+
end
|
72
|
+
alias_method :call, :call_without_conditions
|
73
|
+
|
74
|
+
def call_with_conditions(obj, attr, value) #:nodoc:
|
75
|
+
if @conditions.all? { |c| c.evaluate obj }
|
76
|
+
call_without_conditions obj, attr, value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
def build_conditions(p, n) #:nodoc:
|
82
|
+
@conditions = []
|
83
|
+
[p].flatten.each {|c| @conditions << Condition.new(c) if c }
|
84
|
+
[n].flatten.each {|c| @conditions << Condition.new(c, false) if c }
|
85
|
+
|
86
|
+
(class << self; self; end).module_eval do
|
87
|
+
alias_method :call, :call_with_conditions
|
88
|
+
end unless @conditions.empty?
|
89
|
+
end
|
90
|
+
|
91
|
+
# Conditions for use in Validations are usually used with Validations.
|
92
|
+
class Condition
|
93
|
+
|
94
|
+
# An instance of Condition accepts Symbols, UnboundMethods or anything
|
95
|
+
# that responds to :call.
|
96
|
+
#
|
97
|
+
# The following examples are similiar to each other:
|
98
|
+
#
|
99
|
+
# NotNaughty::Validation::Condition.new proc {|o| o.nil?}
|
100
|
+
# NotNaughty::Validation::Condition.new :nil?
|
101
|
+
# NotNaughty::Validation::Condition.new Object.instance_method(:nil?)
|
102
|
+
def self.new(condition, positive = true)
|
103
|
+
instance = allocate
|
104
|
+
instance.instance_variable_set :@condition, condition
|
105
|
+
|
106
|
+
block = case condition
|
107
|
+
when Symbol then positive ?
|
108
|
+
proc { |o| o.send @condition } :
|
109
|
+
proc { |o| not o.send @condition }
|
110
|
+
when UnboundMethod then positive ?
|
111
|
+
proc { |o| @condition.bind(o).call } :
|
112
|
+
proc { |o| not @condition.bind(o).call }
|
113
|
+
else positive ?
|
114
|
+
proc { |o| @condition.call o } :
|
115
|
+
proc { |o| not @condition.call o }
|
116
|
+
end
|
117
|
+
|
118
|
+
(class << instance; self; end).
|
119
|
+
module_eval { define_method(:evaluate, &block) }
|
120
|
+
|
121
|
+
instance
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
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,41 @@
|
|
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(opts, attributes) #:nodoc:
|
24
|
+
__message = opts[:message] || '#{"%s".humanize} could not be confirmed.'
|
25
|
+
|
26
|
+
if opts[:allow_blank] or opts[:allow_nil]
|
27
|
+
__allow = if opts[:allow_blank] then :blank? else :nil? end
|
28
|
+
super opts, attributes do |o, a, v|
|
29
|
+
o.errors.add a, __message unless
|
30
|
+
v.send __allow or o.send(:"#{a}_confirmation").eql? v
|
31
|
+
end
|
32
|
+
else
|
33
|
+
super opts, attributes do |o, a, v|
|
34
|
+
o.errors.add a, __message unless
|
35
|
+
o.send(:"#{a}_confirmation").eql? v
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,55 @@
|
|
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, 'abc'
|
20
|
+
# obj.errors.on(:to_s).any? # => false
|
21
|
+
#
|
22
|
+
# FormatValidation.new({:with => /[A-Z]/}, :to_s).call obj, :to_s, 'abc'
|
23
|
+
# obj.errors.on(:to_s) # => ["Format of to_s does not match."]
|
24
|
+
class FormatValidation < Validation
|
25
|
+
|
26
|
+
# Predefined matchers.
|
27
|
+
DEFINITIONS = {
|
28
|
+
: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])?/,
|
29
|
+
: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]?)/
|
30
|
+
}
|
31
|
+
|
32
|
+
def initialize(opts, attributes) #:nodoc:
|
33
|
+
format_matcher = if Symbol === opts[:with] then DEFINITIONS[opts[:with]]
|
34
|
+
elsif opts[:with].respond_to? :match then opts[:with]
|
35
|
+
end or raise ArgumentError, "#{ opts[:with].inspect } doesn't :match"
|
36
|
+
|
37
|
+
msg = opts[:message] || '%s does not match format.'
|
38
|
+
|
39
|
+
if opts[:allow_blank] or opts[:allow_nil]
|
40
|
+
pass = opts[:allow_blank] ? :blank? : :nil?
|
41
|
+
|
42
|
+
super opts, attributes do |obj, attr, val|
|
43
|
+
val.send pass or format_matcher.match val or
|
44
|
+
obj.errors.add attr, msg
|
45
|
+
end
|
46
|
+
else
|
47
|
+
super opts, attributes do |obj, attr, val|
|
48
|
+
format_matcher.match val or
|
49
|
+
obj.errors.add attr, msg
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,95 @@
|
|
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
|
+
class LengthValidation < Validation
|
39
|
+
|
40
|
+
def initialize(opts, attributes) #:nodoc:
|
41
|
+
|
42
|
+
block = build_block opts
|
43
|
+
|
44
|
+
if opts[:allow_blank]
|
45
|
+
super opts, attributes do |o, a, v|
|
46
|
+
block[o, a, v] unless v.blank?
|
47
|
+
end
|
48
|
+
else
|
49
|
+
super opts, attributes do |o, a, v|
|
50
|
+
block[o, a, v] unless v.nil?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
def build_block(opts) #:nodoc:
|
57
|
+
if __length = opts[:is]
|
58
|
+
__message = opts[:message] ||
|
59
|
+
"Length of %s is not equal to #{__length}."
|
60
|
+
proc do |o, a, v|
|
61
|
+
o.errors.add a, __message unless __length.eql? v.length
|
62
|
+
end
|
63
|
+
elsif opts[:within] or opts[:minimum] && opts[:maximum]
|
64
|
+
__range = opts[:within]
|
65
|
+
__range ||= Range.new opts[:minimum], opts[:maximum]
|
66
|
+
|
67
|
+
__message = opts[:message] ||
|
68
|
+
"Length of %s is not within #{__range.first} and #{__range.last}."
|
69
|
+
|
70
|
+
proc do |o, a, v|
|
71
|
+
o.errors.add a, __message unless __range.include? v.length
|
72
|
+
end
|
73
|
+
elsif opts[:minimum]
|
74
|
+
__boundary = opts[:minimum]
|
75
|
+
__message = opts[:message] ||
|
76
|
+
"Length of %s is smaller than #{__boundary}."
|
77
|
+
|
78
|
+
proc do |o, a, v|
|
79
|
+
o.errors.add a, __message unless __boundary <= v.length
|
80
|
+
end
|
81
|
+
elsif opts[:maximum]
|
82
|
+
__boundary = opts[:maximum]
|
83
|
+
__message = opts[:message] ||
|
84
|
+
"Length of %s is greater than #{__boundary}."
|
85
|
+
|
86
|
+
proc do |o, a, v|
|
87
|
+
o.errors.add a, __message unless __boundary >= v.length
|
88
|
+
end
|
89
|
+
else
|
90
|
+
raise ArgumentError, 'no boundary given'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
NotNaughty::Validation.load :format
|
2
|
+
|
3
|
+
module NotNaughty
|
4
|
+
|
5
|
+
# == Validates numericality of obj's attribute via an regular expression.
|
6
|
+
#
|
7
|
+
# Unless the validation succeeds an error hash (:attribute => :message)
|
8
|
+
# is added to the obj's instance of Errors.
|
9
|
+
#
|
10
|
+
# <b>Options:</b>
|
11
|
+
# <tt>:only_integer</tt>:: validates with <tt>/^[+-]?\d+$/</tt> (false)
|
12
|
+
# <tt>:message</tt>:: see NotNaughty::Errors for details
|
13
|
+
# <tt>:if</tt>:: see NotNaughty::Validation::Condition for details
|
14
|
+
# <tt>:unless</tt>:: see NotNaughty::Validation::Condition for details
|
15
|
+
#
|
16
|
+
# <b>Example:</b>
|
17
|
+
#
|
18
|
+
# obj = '-12.2' #
|
19
|
+
# def obj.errors() @errors ||= NotNauthy::Errors.new end
|
20
|
+
#
|
21
|
+
# NumericalityValidation.new({}, :to_s).call obj, :to_s, '-12.2'
|
22
|
+
# obj.errors.on(:to_s).any? # => false
|
23
|
+
#
|
24
|
+
# NumericalityValidation.new({:only_integer => true}, :to_s).
|
25
|
+
# call obj, :to_s, '-12.2'
|
26
|
+
#
|
27
|
+
# obj.errors.on(:to_s).any? # => true
|
28
|
+
class NumericalityValidation < FormatValidation
|
29
|
+
|
30
|
+
def initialize(opts, attributes) #:nodoc:
|
31
|
+
opts[:with] = if opts[:only_integer]
|
32
|
+
opts[:message] ||= '#{"%s".humanize} is not an integer.'
|
33
|
+
/^[+-]?\d+$/
|
34
|
+
else
|
35
|
+
opts[:message] ||= '#{"%s".humanize} is not a number.'
|
36
|
+
/^[+-]?\d*\.?\d+$/
|
37
|
+
end
|
38
|
+
|
39
|
+
super opts, attributes
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Validates presence of obj's attribute via the <tt>:blank?</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>Example:</b>
|
14
|
+
#
|
15
|
+
# obj = '' # blank? => true
|
16
|
+
# def obj.errors() @errors ||= NotNauthy::Errors.new end
|
17
|
+
#
|
18
|
+
# PresenceValidation.new({}, :to_s).call obj, :to_s, ''
|
19
|
+
# obj.errors.on(:to_s) # => ["To s is not present."]
|
20
|
+
class PresenceValidation < Validation
|
21
|
+
|
22
|
+
def initialize(opts, attributes) #:nodoc:
|
23
|
+
__message = opts[:message] || '#{"%s".humanize} is not present.'
|
24
|
+
|
25
|
+
super opts, attributes do |o, a, v|
|
26
|
+
o.errors.add a, __message if v.blank?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|