not-naughty 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,4 @@
1
+ --exclude
2
+ gems
3
+ --exclude
4
+ spec
@@ -0,0 +1,5 @@
1
+ --colour
2
+ --backtrace
3
+ --format
4
+ specdoc
5
+ --diff
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/../lib/not_naughty.rb'
2
+
3
+ def subject() ::NotNaughty end
4
+ def h(something)
5
+ puts '<pre>%s</pre>' %
6
+ something.inspect.gsub(/[<>]/) {|m| (m == '<')? '&lt;': '&gt;'}
7
+ end
@@ -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