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,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,80 @@
|
|
1
|
+
require "#{ File.dirname(__FILE__) }/spec_helper.rb"
|
2
|
+
|
3
|
+
describe subject::Builder do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@validatable = Class.new(Object) do
|
7
|
+
def self.validator=(validator) @validator = validator end
|
8
|
+
def self.validator() @validator end
|
9
|
+
def initialize(state) @state = state end
|
10
|
+
def validation_state() state end
|
11
|
+
end
|
12
|
+
@validatable.validator = mock 'Validator'
|
13
|
+
@validatable.extend(subject::Builder)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should have a delegator class" do
|
17
|
+
subject::Builder.constants.
|
18
|
+
should include('ValidationDelegator')
|
19
|
+
subject::Builder.const_get('ValidationDelegator').
|
20
|
+
should < SimpleDelegator
|
21
|
+
end
|
22
|
+
it "should provide the :validates builder method" do
|
23
|
+
@validatable.should respond_to(:validates)
|
24
|
+
end
|
25
|
+
it "should add validation for :name via :validates" do
|
26
|
+
@validatable.validator.should_receive(:add_validation).
|
27
|
+
with(NotNaughty::PresenceValidation, :name, {}).twice
|
28
|
+
@validatable.validates { presence_of :name }
|
29
|
+
@validatable.validates(:name) { presence }
|
30
|
+
end
|
31
|
+
it "should add validation for :name on update via :validates" do
|
32
|
+
@validatable.validator.should_receive(:add_validation).
|
33
|
+
with(NotNaughty::PresenceValidation, :name, {:on => :update}).twice
|
34
|
+
@validatable.validates { presence_of :name, :on => :update }
|
35
|
+
@validatable.validates(:on => :update) { presence_of :name }
|
36
|
+
end
|
37
|
+
it "should add validation for :firstname and :lastname via :validates" do
|
38
|
+
@validatable.validator.should_receive(:add_validation).
|
39
|
+
with(NotNaughty::PresenceValidation, :firstname, :lastname, {}).twice
|
40
|
+
@validatable.validates { presence_of :firstname, :lastname }
|
41
|
+
@validatable.validates(:firstname, :lastname) { presence :name }
|
42
|
+
end
|
43
|
+
it "should register validation" do
|
44
|
+
validation = Class.new(subject::Validation) do
|
45
|
+
def self.name() 'TestValidation' end
|
46
|
+
def initialize(opts, &block) end
|
47
|
+
def call(obj, attr, value) end
|
48
|
+
end
|
49
|
+
|
50
|
+
@validatable.should respond_to(:validates_test_of)
|
51
|
+
end
|
52
|
+
it "should provide the :validates_each builder method" do
|
53
|
+
@validatable.should respond_to(:validates_each)
|
54
|
+
end
|
55
|
+
it "should build the Validations with :validates_each" do
|
56
|
+
@validatable.validator = mock 'Validator'
|
57
|
+
@validatable.validator.
|
58
|
+
should_receive(:add_validation).
|
59
|
+
with(:a, :b)
|
60
|
+
@validatable.validates_each(:a, :b) {|o, a, v|}
|
61
|
+
|
62
|
+
pending 'expect a block'
|
63
|
+
end
|
64
|
+
it "should support :each in validates block" do
|
65
|
+
@validatable.validator = mock 'Validator'
|
66
|
+
@validatable.validator.
|
67
|
+
should_receive(:add_validation).
|
68
|
+
with(:a, :b, {})
|
69
|
+
@validatable.validates do
|
70
|
+
each(:a, :b) {|o, a, v|}
|
71
|
+
end
|
72
|
+
|
73
|
+
pending 'expect a block'
|
74
|
+
end
|
75
|
+
it "should raise a NoMethodError is builder method does not exist" do
|
76
|
+
lambda { @validatable.validates() { bunch_of :holy_crap } }.
|
77
|
+
should raise_error(NoMethodError)
|
78
|
+
end
|
79
|
+
|
80
|
+
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::Builder
|
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(:Builder)
|
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::Builder.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 Builder to Validation observers" do
|
78
|
+
subject::Validation.instance_variable_get(:@observer_peers).
|
79
|
+
should include(subject::Builder)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
data/spec/rcov.opts
ADDED
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,118 @@
|
|
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::Builder.
|
7
|
+
should_receive(:update).any_number_of_times.
|
8
|
+
with Class.new(subject::Validation)
|
9
|
+
pending 'This one kinda sucks...'
|
10
|
+
end
|
11
|
+
it "should build validations with block" do
|
12
|
+
block = proc {|o, a, v|}
|
13
|
+
validation = subject::Validation.new({}, &block)
|
14
|
+
|
15
|
+
validation.instance_variable_get(:@block).should == block
|
16
|
+
end
|
17
|
+
it "should alias call to call_without_conditions" do
|
18
|
+
validation = subject::Validation.new({}) {|o, a, v|}
|
19
|
+
|
20
|
+
validation.method(:call).
|
21
|
+
should == validation.method(:call_without_conditions)
|
22
|
+
end
|
23
|
+
it "should not build conditions" do
|
24
|
+
validation = subject::Validation.
|
25
|
+
new({:if => nil, :unless => nil}) {|o, a, v|}
|
26
|
+
|
27
|
+
validation.instance_variable_get(:@conditions).should be_empty
|
28
|
+
end
|
29
|
+
it "should build conditions" do
|
30
|
+
validation = subject::Validation.new({:if => :nil?}) {|o, a, v|}
|
31
|
+
validation.instance_variable_get(:@conditions).should_not be_empty
|
32
|
+
|
33
|
+
validation.instance_variable_get(:@conditions).
|
34
|
+
first.instance_variable_get(:@condition).
|
35
|
+
should == :nil?
|
36
|
+
end
|
37
|
+
it "should alias call to call_with_conditions" do
|
38
|
+
validation = subject::Validation.new({:if => :nil?}) {|o, a, v|}
|
39
|
+
|
40
|
+
validation.method(:call).
|
41
|
+
should == validation.method(:call_with_conditions)
|
42
|
+
end
|
43
|
+
it "should call" do
|
44
|
+
probe = mock 'Probe'
|
45
|
+
probe.should_receive(:test).exactly(3).times
|
46
|
+
|
47
|
+
validation = subject::Validation.
|
48
|
+
new({}) { |o, a, v| [o, a, v].each { |p| p.test } }
|
49
|
+
|
50
|
+
validation.call probe, probe, probe
|
51
|
+
end
|
52
|
+
it "should call unless a condition passes" do
|
53
|
+
probe = mock 'Probe'
|
54
|
+
probe.stub!(:nil?).and_return(true)
|
55
|
+
|
56
|
+
validation = subject::Validation.
|
57
|
+
new({:unless => :nil?}) { |o, a, v| [o, a, v].each { |p| p.test } }
|
58
|
+
|
59
|
+
validation.call probe, probe, probe
|
60
|
+
|
61
|
+
probe.should_receive(:test).exactly(3).times
|
62
|
+
probe.stub!(:nil?).and_return(false)
|
63
|
+
|
64
|
+
validation.call probe, probe, probe
|
65
|
+
end
|
66
|
+
it "should not call" do
|
67
|
+
probe = mock 'Probe'
|
68
|
+
probe.should_not_receive(:test)
|
69
|
+
|
70
|
+
validation = subject::Validation.
|
71
|
+
new({:if => :nil?}) { |o, a, v| [o, a, v].each { |p| p.test } }
|
72
|
+
|
73
|
+
validation.call probe, probe, probe
|
74
|
+
|
75
|
+
end
|
76
|
+
it "should have validated attributes accessable" do
|
77
|
+
validation = subject::Validation.new(:probe)
|
78
|
+
validation.attributes.should include(:probe)
|
79
|
+
end
|
80
|
+
it "should clone observer peers to descendant" do
|
81
|
+
peers = subject::Validation.instance_variable_get :@observer_peers
|
82
|
+
peers.should_receive(:clone).and_return(true)
|
83
|
+
descendant = Class.new(subject::Validation)
|
84
|
+
descendant.instance_variable_get(:@observer_peers).should be_true
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe subject::Validation::Condition do
|
90
|
+
|
91
|
+
it "should evaluate positive callable" do
|
92
|
+
condition = subject::Validation::Condition.new proc {|o| o.nil?}
|
93
|
+
condition.evaluate(nil).should be_true
|
94
|
+
end
|
95
|
+
it "should evaluate negative callable" do
|
96
|
+
condition = subject::Validation::Condition.new proc {|o| o.nil?}, false
|
97
|
+
condition.evaluate(nil).should be_false
|
98
|
+
end
|
99
|
+
it "should evaluate positive Symbol" do
|
100
|
+
condition = subject::Validation::Condition.new :nil?
|
101
|
+
condition.evaluate(nil).should be_true
|
102
|
+
end
|
103
|
+
it "should evaluate negative Symbol" do
|
104
|
+
condition = subject::Validation::Condition.new :nil?, false
|
105
|
+
condition.evaluate(nil).should be_false
|
106
|
+
end
|
107
|
+
it "should evaluate positive UnboundMethod" do
|
108
|
+
um = NilClass.instance_method :nil?
|
109
|
+
condition = subject::Validation::Condition.new um
|
110
|
+
condition.evaluate(nil).should be_true
|
111
|
+
end
|
112
|
+
it "should evaluate negative UnboundMethod" do
|
113
|
+
um = NilClass.instance_method :nil?
|
114
|
+
condition = subject::Validation::Condition.new um, false
|
115
|
+
condition.evaluate(nil).should be_false
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|