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.
- 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
|