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