not-naughty 0.6.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.
@@ -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