not-naughty 0.6.0

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