boof-not-naughty 0.6.2

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