boof-not-naughty 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- 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,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(valid, attributes) #:nodoc:
|
31
|
+
valid[:with] = if valid[:only_integer]
|
32
|
+
valid[:message] ||= '%s is not an integer.'
|
33
|
+
/^[+-]?\d+$/
|
34
|
+
else
|
35
|
+
valid[:message] ||= '%s is not a number.'
|
36
|
+
/^[+-]?\d*\.?\d+$/
|
37
|
+
end
|
38
|
+
|
39
|
+
super valid, attributes
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
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(valid, attributes) #:nodoc:
|
23
|
+
valid[:message] ||= '%s is not present.'
|
24
|
+
|
25
|
+
super valid, attributes do |obj, attr, value|
|
26
|
+
value.blank? and
|
27
|
+
obj.errors.add attr, valid[:message]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Superclass for all Adapters.
|
4
|
+
#
|
5
|
+
# See new and get_state for more information.
|
6
|
+
class Validator
|
7
|
+
|
8
|
+
attr_reader :states, :error_handler
|
9
|
+
|
10
|
+
# By default it comes with the :default State unless other states are
|
11
|
+
# provided.
|
12
|
+
#
|
13
|
+
# <b>Example:</b>
|
14
|
+
# NotNaughty::Validator.new
|
15
|
+
# # has the :default state
|
16
|
+
# NotNaughty::Validator.new :create, :update
|
17
|
+
# # has the :create and :update states
|
18
|
+
#
|
19
|
+
# <em>Adapters should overwrite this method.</em>
|
20
|
+
def initialize(*states)
|
21
|
+
states << :default if states.empty?
|
22
|
+
|
23
|
+
@states = states.inject({}) {|m, s| m.update s => State.new(s)}
|
24
|
+
@initial_state = @states[states.first]
|
25
|
+
|
26
|
+
@error_handler = NotNaughty::ErrorHandler.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def clone #:nodoc:
|
30
|
+
states = [@initial_state.name] | @states.keys
|
31
|
+
error_handler = @error_handler.clone
|
32
|
+
|
33
|
+
clone = self.class.new(*states)
|
34
|
+
@states.each do |n, s|
|
35
|
+
s.validations.each { |a, v| clone.states[n].validations[a] = v.clone }
|
36
|
+
end
|
37
|
+
clone.instance_eval do
|
38
|
+
@initial_state = @states[states.first]
|
39
|
+
@error_handler = error_handler
|
40
|
+
end
|
41
|
+
|
42
|
+
clone
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the state for the given object. By default it does return the
|
46
|
+
# initial state.
|
47
|
+
#
|
48
|
+
# <em>Adapters that provide multiple states should eventually overwrite
|
49
|
+
# this method</em>.
|
50
|
+
def get_state(obj = nil) @initial_state end
|
51
|
+
|
52
|
+
# Adds a validation to all/specified states.
|
53
|
+
#
|
54
|
+
# <b>Example:</b>
|
55
|
+
# add_validation(:firstname, :lastname, :on => :default) {|o, a, v|}
|
56
|
+
# # adds validation to :default state
|
57
|
+
# add_validation(:firstname, :lastname) {|o, a, v|}
|
58
|
+
# # adds validation to all states
|
59
|
+
# add_validation(:first, :last, :on => [:create, :update]) {|o, a, v|}
|
60
|
+
# # adds validation to :create and :update states
|
61
|
+
def add_validation(*p, &b)
|
62
|
+
options = (p.last.is_a? Hash) ? p.last : {}
|
63
|
+
|
64
|
+
if states = options.delete(:on)
|
65
|
+
@states.values_at(*states).each do |state|
|
66
|
+
state.add_validation(*p, &b) unless state.nil?
|
67
|
+
end
|
68
|
+
else
|
69
|
+
@states.each { |name, state| state.add_validation(*p, &b) }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns true if given object has validations in its current state. If
|
74
|
+
# no object was given it returns true if any state has validations. It
|
75
|
+
# otherwise returns false.
|
76
|
+
def has_validations?(obj = nil)
|
77
|
+
if obj.nil? then @states.any? { |name, state| state.has_validations? }
|
78
|
+
else get_state(obj).has_validations? end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Runs all validations on object for the object's state.
|
82
|
+
def invoke(obj)
|
83
|
+
get_state(obj).validations.each do |attr, validations|
|
84
|
+
val = obj.send attr
|
85
|
+
validations.each { |validation| validation.call obj, attr, val }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# == Container for attribute specific validations
|
90
|
+
#
|
91
|
+
# See Validator for details.
|
92
|
+
class State
|
93
|
+
|
94
|
+
attr_reader :validations, :name
|
95
|
+
|
96
|
+
# Initializes the state with given name.
|
97
|
+
def initialize(name = :default)
|
98
|
+
@name, @validations = name, Hash.new {|h, k| h[k] = []}
|
99
|
+
end
|
100
|
+
|
101
|
+
# Adds the validation that results from <tt>params</tt> and
|
102
|
+
# <tt>block</tt> to validated attributes (see Validation#new for
|
103
|
+
# details).
|
104
|
+
def add_validation(*params, &block)
|
105
|
+
validation = Validation.new(*params, &block)
|
106
|
+
|
107
|
+
validation.attributes.each do |attribute|
|
108
|
+
@validations[attribute] << validation if attribute.is_a? Symbol
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns validations for given attribute.
|
113
|
+
def [](attribute)
|
114
|
+
@validations[attribute]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns true if a attributes has validations assigned, false
|
118
|
+
# otherwise.
|
119
|
+
def has_validations?
|
120
|
+
@validations.any? { |attribute, validations| validations.any? }
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Container for failed validations.
|
4
|
+
#
|
5
|
+
# ...
|
6
|
+
class Violation < RuntimeError
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@errors, :empty?, :clear, :[], :each
|
10
|
+
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
def initialize() #:nodoc:
|
14
|
+
@errors = Hash.new {|h, k| h[k] = []}
|
15
|
+
end
|
16
|
+
# Adds an error for the given attribute.
|
17
|
+
def add(k, msg) @errors[k] << msg end
|
18
|
+
|
19
|
+
# Returns an array of fully-formatted error messages.
|
20
|
+
def full_messages
|
21
|
+
@errors.inject([]) do |messages, k_errors| k, errors = *k_errors
|
22
|
+
errors.each {|e| messages << eval(e.inspect.delete('\\') % k) }
|
23
|
+
messages
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns an array of evaluated error messages for given attribute.
|
28
|
+
def on(attribute)
|
29
|
+
@errors[attribute].map do |message|
|
30
|
+
eval(message.inspect.delete('\\') % attribute)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "#{ File.dirname(__FILE__) }/spec_helper.rb"
|
2
|
+
|
3
|
+
describe subject::ErrorHandler do
|
4
|
+
before(:each) { @eh = subject::ErrorHandler.new }
|
5
|
+
|
6
|
+
it "passed all Exceptions to Kernel.raise" do
|
7
|
+
proc { @eh.raise Exception.new }.should raise_error(Exception)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "calls the closest handle" do
|
11
|
+
@eh.handle(ArgumentError) {ArgumentError}
|
12
|
+
@eh.handle(StandardError) {StandardError}
|
13
|
+
|
14
|
+
@eh.raise(ArgumentError.new).should == ArgumentError
|
15
|
+
@eh.raise(StandardError.new).should == StandardError
|
16
|
+
@eh.raise(NoMethodError.new).should == StandardError
|
17
|
+
|
18
|
+
proc { @eh.raise ScriptError.new }.should raise_error(Exception)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "#{ File.dirname(__FILE__) }/spec_helper.rb"
|
2
|
+
|
3
|
+
module subject::ClassMethods
|
4
|
+
def self.extended(base) end
|
5
|
+
end
|
6
|
+
module subject::InstanceMethods
|
7
|
+
def self.included(base) end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe subject do
|
11
|
+
|
12
|
+
it "should load necessary files" do
|
13
|
+
subject::should be_const_defined(:Validation)
|
14
|
+
subject::should be_const_defined(:Validator)
|
15
|
+
subject::should be_const_defined(:ClassMethods)
|
16
|
+
subject::should be_const_defined(:InstanceMethods)
|
17
|
+
subject::should be_const_defined(:Violation)
|
18
|
+
end
|
19
|
+
it "should define a validator" do
|
20
|
+
validated = Class.new(Object).extend subject
|
21
|
+
validated.should respond_to(:validator)
|
22
|
+
validated.validator(:state).should be_an_instance_of(subject::Validator)
|
23
|
+
validated.validator.states.should have_key(:state)
|
24
|
+
|
25
|
+
validator = Class.new(subject::Validator)
|
26
|
+
validated = Class.new(Object).extend subject
|
27
|
+
validated.validator validator, :create, :update
|
28
|
+
validated.validator.should be_an_instance_of(validator)
|
29
|
+
validated.validator.states.keys.should include(:create, :update)
|
30
|
+
end
|
31
|
+
it "should extend the receiver if validator is defined" do
|
32
|
+
validated = Class.new(Object)
|
33
|
+
|
34
|
+
subject::ClassMethods.should_receive(:extended).with(validated)
|
35
|
+
subject::InstanceMethods.should_receive(:included).with(validated)
|
36
|
+
|
37
|
+
validated.extend subject
|
38
|
+
end
|
39
|
+
it "should deep-clone the validator if inherited" do
|
40
|
+
super_validated = Class.new(Object).extend subject
|
41
|
+
super_validated.validator.add_validation(:name) {|o, a, v|}
|
42
|
+
validated = Class.new(super_validated)
|
43
|
+
|
44
|
+
validated.validator.should_not == super_validated.validator
|
45
|
+
validated.validator.should have_validations
|
46
|
+
validated.validator.add_validation(:name) {|o, a, v|}
|
47
|
+
|
48
|
+
super_validated.validator.get_state.validations[:name].length.
|
49
|
+
should < validated.validator.get_state.validations[:name].length
|
50
|
+
end
|
51
|
+
it "should prepend a validation before any method" do
|
52
|
+
validated = Class.new(Object).extend subject
|
53
|
+
|
54
|
+
validated.validated_before :clone
|
55
|
+
instance = validated.new
|
56
|
+
instance.should_receive(:valid?).once.and_return(true)
|
57
|
+
instance.should_receive(:clone_without_validations)
|
58
|
+
instance.clone
|
59
|
+
end
|
60
|
+
it "should raise an exception if invalid and method called" do
|
61
|
+
validated = Class.new(Object).extend subject
|
62
|
+
|
63
|
+
validated.validated_before :clone
|
64
|
+
instance = validated.new
|
65
|
+
instance.should_receive(:valid?).once.and_return(false)
|
66
|
+
lambda { instance.clone }.should raise_error(subject::Violation)
|
67
|
+
end
|
68
|
+
it "should return false if invalid and method called" do
|
69
|
+
validated = Class.new(Object).extend subject
|
70
|
+
|
71
|
+
validated.validated_before :clone
|
72
|
+
validated.validator.error_handler.handle(subject::Violation) {false}
|
73
|
+
instance = validated.new
|
74
|
+
instance.should_receive(:valid?).once.and_return(false)
|
75
|
+
instance.clone.should == false
|
76
|
+
end
|
77
|
+
it "should add ClassMethods to Validation observers" do
|
78
|
+
subject::Validation.instance_variable_get(:@observer_peers).
|
79
|
+
should include(subject::ClassMethods)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
data/spec/rcov.opts
ADDED
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
|
4
|
+
require "#{ File.dirname __FILE__ }/../lib/not_naughty.rb"
|
5
|
+
NotNaughty::Validation.load 'format', 'presence'
|
6
|
+
|
7
|
+
def subject() ::NotNaughty end
|
8
|
+
def h(something)
|
9
|
+
puts '<pre>%s</pre>' %
|
10
|
+
something.inspect.gsub(/[<>]/) {|m| (m == '<')? '<': '>'}
|
11
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require "#{ File.dirname __FILE__ }/spec_helper.rb"
|
2
|
+
|
3
|
+
describe subject::Validation do
|
4
|
+
|
5
|
+
it "should register validations if inherited" do
|
6
|
+
subject::ClassMethods.
|
7
|
+
should_receive(:update).once.
|
8
|
+
with Class.new(subject::Validation)
|
9
|
+
end
|
10
|
+
it "should build validations with block" do
|
11
|
+
block = proc {|o, a, v|}
|
12
|
+
validation = subject::Validation.new({}, &block)
|
13
|
+
|
14
|
+
validation.instance_variable_get(:@block).should == block
|
15
|
+
end
|
16
|
+
it "should alias call to call_without_conditions" do
|
17
|
+
validation = subject::Validation.new({}) {|o, a, v|}
|
18
|
+
|
19
|
+
validation.method(:call).
|
20
|
+
should == validation.method(:call_without_conditions)
|
21
|
+
end
|
22
|
+
it "should not build conditions" do
|
23
|
+
validation = subject::Validation.
|
24
|
+
new({:if => nil, :unless => nil}) {|o, a, v|}
|
25
|
+
|
26
|
+
validation.instance_variable_get(:@conditions).should be_empty
|
27
|
+
end
|
28
|
+
it "should build conditions" do
|
29
|
+
validation = subject::Validation.new({:if => :nil?}) {|o, a, v|}
|
30
|
+
validation.instance_variable_get(:@conditions).should_not be_empty
|
31
|
+
|
32
|
+
validation.instance_variable_get(:@conditions).
|
33
|
+
first.instance_variable_get(:@condition).
|
34
|
+
should == :nil?
|
35
|
+
end
|
36
|
+
it "should alias call to call_with_conditions" do
|
37
|
+
validation = subject::Validation.new({:if => :nil?}) {|o, a, v|}
|
38
|
+
|
39
|
+
validation.method(:call).
|
40
|
+
should == validation.method(:call_with_conditions)
|
41
|
+
end
|
42
|
+
it "should call" do
|
43
|
+
probe = mock 'Probe'
|
44
|
+
probe.should_receive(:test).exactly(3).times
|
45
|
+
|
46
|
+
validation = subject::Validation.
|
47
|
+
new({}) { |o, a, v| [o, a, v].each { |p| p.test } }
|
48
|
+
|
49
|
+
validation.call probe, probe, probe
|
50
|
+
end
|
51
|
+
it "should call unless a condition passes" do
|
52
|
+
probe = mock 'Probe'
|
53
|
+
probe.stub!(:nil?).and_return(true)
|
54
|
+
|
55
|
+
validation = subject::Validation.
|
56
|
+
new({:unless => :nil?}) { |o, a, v| [o, a, v].each { |p| p.test } }
|
57
|
+
|
58
|
+
validation.call probe, probe, probe
|
59
|
+
|
60
|
+
probe.should_receive(:test).exactly(3).times
|
61
|
+
probe.stub!(:nil?).and_return(false)
|
62
|
+
|
63
|
+
validation.call probe, probe, probe
|
64
|
+
end
|
65
|
+
it "should not call" do
|
66
|
+
probe = mock 'Probe'
|
67
|
+
probe.should_not_receive(:test)
|
68
|
+
|
69
|
+
validation = subject::Validation.
|
70
|
+
new({:if => :nil?}) { |o, a, v| [o, a, v].each { |p| p.test } }
|
71
|
+
|
72
|
+
validation.call probe, probe, probe
|
73
|
+
|
74
|
+
end
|
75
|
+
it "should have validated attributes accessable" do
|
76
|
+
validation = subject::Validation.new(:probe)
|
77
|
+
validation.attributes.should include(:probe)
|
78
|
+
end
|
79
|
+
it "should clone observer peers to descendant" do
|
80
|
+
peers = subject::Validation.instance_variable_get :@observer_peers
|
81
|
+
peers.should_receive(:clone).and_return(true)
|
82
|
+
descendant = Class.new(subject::Validation)
|
83
|
+
descendant.instance_variable_get(:@observer_peers).should be_true
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
describe subject::Validation::Condition do
|
89
|
+
|
90
|
+
it "should evaluate positive callable" do
|
91
|
+
condition = subject::Validation::Condition.new proc {|o| o.nil?}
|
92
|
+
condition.evaluate(nil).should be_true
|
93
|
+
end
|
94
|
+
it "should evaluate negative callable" do
|
95
|
+
condition = subject::Validation::Condition.new proc {|o| o.nil?}, false
|
96
|
+
condition.evaluate(nil).should be_false
|
97
|
+
end
|
98
|
+
it "should evaluate positive Symbol" do
|
99
|
+
condition = subject::Validation::Condition.new :nil?
|
100
|
+
condition.evaluate(nil).should be_true
|
101
|
+
end
|
102
|
+
it "should evaluate negative Symbol" do
|
103
|
+
condition = subject::Validation::Condition.new :nil?, false
|
104
|
+
condition.evaluate(nil).should be_false
|
105
|
+
end
|
106
|
+
it "should evaluate positive UnboundMethod" do
|
107
|
+
um = NilClass.instance_method :nil?
|
108
|
+
condition = subject::Validation::Condition.new um
|
109
|
+
condition.evaluate(nil).should be_true
|
110
|
+
end
|
111
|
+
it "should evaluate negative UnboundMethod" do
|
112
|
+
um = NilClass.instance_method :nil?
|
113
|
+
condition = subject::Validation::Condition.new um, false
|
114
|
+
condition.evaluate(nil).should be_false
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|