not_naughty 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +22 -0
- data/COPYING +18 -0
- data/README +62 -0
- data/Rakefile +130 -0
- data/lib/not_naughty/builder.rb +68 -0
- data/lib/not_naughty/errors.rb +61 -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 +39 -0
- data/lib/not_naughty/validations/confirmation_validation.rb +41 -0
- data/lib/not_naughty/validations/format_validation.rb +45 -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 +119 -0
- data/lib/not_naughty.rb +85 -0
- data/lib/sequel_not_naughty.rb +106 -0
- data/spec/builder_spec.rb +69 -0
- data/spec/errors_spec.rb +61 -0
- data/spec/not_naughty_spec.rb +80 -0
- data/spec/rcov.opts +4 -0
- data/spec/sequel_spec_helper.rb +17 -0
- data/spec/sequel_validated_spec.rb +97 -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 +123 -0
- metadata +84 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Validates length of obj's attribute via the <tt>:length</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>Boundaries (by precendence):</b>
|
14
|
+
# <tt>:is</tt>:: valid length
|
15
|
+
# <tt>:within</tt>:: valid range of length
|
16
|
+
# <tt>:minimum</tt>:: maximum length
|
17
|
+
# <tt>:maximum</tt>:: minimum length
|
18
|
+
#
|
19
|
+
# If both, <tt>:minimum</tt> and <tt>:maximum</tt> are provided they're
|
20
|
+
# converted to :within. Each boundary type has its own default message:
|
21
|
+
# precise:: "Length of %s is not equal to #{__length}."
|
22
|
+
# range:: "Length of %s is not within #{__range.first} and #{__range.last}."
|
23
|
+
# lower:: "Length of %s is smaller than #{__boundary}."
|
24
|
+
# upper:: "Length of %s is greater than #{__boundary}."
|
25
|
+
#
|
26
|
+
# <b>Example:</b>
|
27
|
+
#
|
28
|
+
# obj = %w[a sentence with five words] #
|
29
|
+
# def obj.errors() @errors ||= NotNauthy::Errors.new end
|
30
|
+
#
|
31
|
+
# LengthValidation.new({:minimum => 4}, :to_a).
|
32
|
+
# call obj, :to_a, %w[a sentence with five words]
|
33
|
+
# obj.errors.on(:to_s).any? # => false
|
34
|
+
#
|
35
|
+
# LengthValidation.new({:within => 1..4}, :to_a).
|
36
|
+
# call obj, :to_a, %w[a sentence with five words]
|
37
|
+
# obj.errors.on(:to_s).any? # => true
|
38
|
+
class LengthValidation < Validation
|
39
|
+
|
40
|
+
def initialize(opts, attributes) #:nodoc:
|
41
|
+
|
42
|
+
block = build_block opts
|
43
|
+
|
44
|
+
if opts[:allow_blank]
|
45
|
+
super opts, attributes do |o, a, v|
|
46
|
+
block[o, a, v] unless v.blank?
|
47
|
+
end
|
48
|
+
else
|
49
|
+
super opts, attributes do |o, a, v|
|
50
|
+
block[o, a, v] unless v.nil?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
def build_block(opts) #:nodoc:
|
57
|
+
if __length = opts[:is]
|
58
|
+
__message = opts[:message] ||
|
59
|
+
"Length of %s is not equal to #{__length}."
|
60
|
+
proc do |o, a, v|
|
61
|
+
o.errors.add a, __message unless __length.eql? v.length
|
62
|
+
end
|
63
|
+
elsif opts[:within] or opts[:minimum] && opts[:maximum]
|
64
|
+
__range = opts[:within]
|
65
|
+
__range ||= Range.new opts[:minimum], opts[:maximum]
|
66
|
+
|
67
|
+
__message = opts[:message] ||
|
68
|
+
"Length of %s is not within #{__range.first} and #{__range.last}."
|
69
|
+
|
70
|
+
proc do |o, a, v|
|
71
|
+
o.errors.add a, __message unless __range.include? v.length
|
72
|
+
end
|
73
|
+
elsif opts[:minimum]
|
74
|
+
__boundary = opts[:minimum]
|
75
|
+
__message = opts[:message] ||
|
76
|
+
"Length of %s is smaller than #{__boundary}."
|
77
|
+
|
78
|
+
proc do |o, a, v|
|
79
|
+
o.errors.add a, __message unless __boundary <= v.length
|
80
|
+
end
|
81
|
+
elsif opts[:maximum]
|
82
|
+
__boundary = opts[:maximum]
|
83
|
+
__message = opts[:message] ||
|
84
|
+
"Length of %s is greater than #{__boundary}."
|
85
|
+
|
86
|
+
proc do |o, a, v|
|
87
|
+
o.errors.add a, __message unless __boundary >= v.length
|
88
|
+
end
|
89
|
+
else
|
90
|
+
raise ArgumentError, 'no boundary given'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -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(opts, attributes) #:nodoc:
|
31
|
+
opts[:with] = if opts[:only_integer]
|
32
|
+
opts[:message] ||= '#{"%s".humanize} is not an integer.'
|
33
|
+
/^[+-]?\d+$/
|
34
|
+
else
|
35
|
+
opts[:message] ||= '#{"%s".humanize} is not a number.'
|
36
|
+
/^[+-]?\d*\.?\d+$/
|
37
|
+
end
|
38
|
+
|
39
|
+
super opts, attributes
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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(opts, attributes) #:nodoc:
|
23
|
+
__message = opts[:message] || '#{"%s".humanize} is not present.'
|
24
|
+
|
25
|
+
super opts, attributes do |o, a, v|
|
26
|
+
o.errors.add a, __message if v.blank?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,119 @@
|
|
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
|
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
|
+
end
|
26
|
+
|
27
|
+
def clone #:nodoc:
|
28
|
+
states = [@initial_state.name] | @states.keys
|
29
|
+
|
30
|
+
clone = self.class.new(*states)
|
31
|
+
@states.each do |n, s|
|
32
|
+
s.validations.each { |a, v| clone.states[n].validations[a] = v.clone }
|
33
|
+
end
|
34
|
+
clone.instance_eval { @initial_state = @states[states.first] }
|
35
|
+
|
36
|
+
clone
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the state for the given object. By default it does return the
|
40
|
+
# initial state.
|
41
|
+
#
|
42
|
+
# <em>Adapters that provide multiple states should eventually overwrite
|
43
|
+
# this method</em>.
|
44
|
+
def get_state(obj = nil) @initial_state end
|
45
|
+
|
46
|
+
# Adds a validation to all/specified states.
|
47
|
+
#
|
48
|
+
# <b>Example:</b>
|
49
|
+
# add_validation(:firstname, :lastname, :on => :default) {|o, a, v|}
|
50
|
+
# # adds validation to :default state
|
51
|
+
# add_validation(:firstname, :lastname) {|o, a, v|}
|
52
|
+
# # adds validation to all states
|
53
|
+
# add_validation(:first, :last, :on => [:create, :update]) {|o, a, v|}
|
54
|
+
# # adds validation to :create and :update states
|
55
|
+
def add_validation(*p, &b)
|
56
|
+
options = (p.last.is_a? Hash) ? p.last : {}
|
57
|
+
|
58
|
+
if states = options.delete(:on)
|
59
|
+
@states.values_at(*states).each do |state|
|
60
|
+
state.add_validation(*p, &b) unless state.nil?
|
61
|
+
end
|
62
|
+
else
|
63
|
+
@states.each { |name, state| state.add_validation(*p, &b) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns true if given object has validations in its current state. If
|
68
|
+
# no object was given it returns true if any state has validations. It
|
69
|
+
# otherwise returns false.
|
70
|
+
def has_validations?(obj = nil)
|
71
|
+
if obj.nil? then @states.any? { |name, state| state.has_validations? }
|
72
|
+
else get_state(obj).has_validations? end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Runs all validations on object for the object's state.
|
76
|
+
def invoke(obj)
|
77
|
+
get_state(obj).validations.each do |attr, validations|
|
78
|
+
val = obj.send! attr
|
79
|
+
validations.each { |validation| validation.call obj, attr, val }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# == Container for attribute specific validations
|
84
|
+
#
|
85
|
+
# See Validator for details.
|
86
|
+
class State
|
87
|
+
|
88
|
+
attr_reader :validations, :name
|
89
|
+
|
90
|
+
# Initializes the state with given name.
|
91
|
+
def initialize(name = :default)
|
92
|
+
@name, @validations = name, Hash.new {|h, k| h[k] = []}
|
93
|
+
end
|
94
|
+
|
95
|
+
# Adds the validation that results from <tt>params</tt> and
|
96
|
+
# <tt>block</tt> to validated attributes (see Validation#new for
|
97
|
+
# details).
|
98
|
+
def add_validation(*params, &block)
|
99
|
+
validation = Validation.new(*params, &block)
|
100
|
+
|
101
|
+
validation.attributes.each do |attribute|
|
102
|
+
@validations[attribute] << validation if attribute.is_a? Symbol
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns validations for given attribute.
|
107
|
+
def [](attribute)
|
108
|
+
@validations[attribute]
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns true if a attributes has validations assigned, false
|
112
|
+
# otherwise.
|
113
|
+
def has_validations?
|
114
|
+
@validations.any? { |attribute, validations| validations.any? }
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/not_naughty.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'observer'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'assistance'
|
6
|
+
|
7
|
+
module Kernel#:nodoc:all
|
8
|
+
methods.include? 'send!' or
|
9
|
+
alias_method :send!, :send
|
10
|
+
end
|
11
|
+
|
12
|
+
$:.unshift File.dirname(__FILE__)
|
13
|
+
|
14
|
+
module NotNaughty
|
15
|
+
require 'not_naughty/validator'
|
16
|
+
|
17
|
+
require 'not_naughty/builder'
|
18
|
+
require 'not_naughty/validation'
|
19
|
+
Validation.add_observer Builder
|
20
|
+
|
21
|
+
require 'not_naughty/errors'
|
22
|
+
require 'not_naughty/instance_methods'
|
23
|
+
|
24
|
+
# Extended classes get NotNaughty::Builder and NotNaughty::InstanceMethods.
|
25
|
+
def self.extended(base)
|
26
|
+
base.instance_eval do
|
27
|
+
include InstanceMethods
|
28
|
+
extend Builder
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# call-seq:
|
33
|
+
# validator(validator_klass = NotNaughty::Validator, *states = [:default])
|
34
|
+
#
|
35
|
+
# Returns instance of Validator. This is either the validator's clone of
|
36
|
+
# superclass, an instance of the the given descendant of or the
|
37
|
+
# <tt>NotNaughty:Validator</tt> himself.
|
38
|
+
#
|
39
|
+
# <b>Examples:</b>
|
40
|
+
# validator # => Instance of NotNaughty::Validator with :default state
|
41
|
+
# validator :create, :update # ~ - but with :create and :update states
|
42
|
+
# validator AnotherValidator # Instance of AnotherValidator
|
43
|
+
#
|
44
|
+
# The above examples work as long validator is not already called. To reset
|
45
|
+
# an already assigned validator set <tt>@validator</tt> to nil.
|
46
|
+
def validator(*states)
|
47
|
+
@validator ||= if superclass.respond_to? :validator
|
48
|
+
superclass.validator.clone
|
49
|
+
|
50
|
+
else
|
51
|
+
validator_klass =
|
52
|
+
if states[0].is_a? Class and states[0] <= NotNaughty::Validator
|
53
|
+
states.shift
|
54
|
+
else
|
55
|
+
NotNaughty::Validator
|
56
|
+
end
|
57
|
+
|
58
|
+
validator_klass.new(*states)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Prepends a call for validation before then given method. If, on call, the
|
63
|
+
# validation passes the method is called. Otherwise it raises an
|
64
|
+
# NotNaughty::ValidationException or returns false.
|
65
|
+
#
|
66
|
+
# <b>Example:</b>
|
67
|
+
# validated_before :save # raise ValidationException unless valid?
|
68
|
+
# validated_before :save, :without => :exception # => false unless valid?
|
69
|
+
def validated_before(method, *args)
|
70
|
+
__method = :"#{method}_without_validations"
|
71
|
+
alias_method __method, method
|
72
|
+
|
73
|
+
without = args.extract_options![:without]
|
74
|
+
if [*without].include? :exception
|
75
|
+
define_method method do |*params|
|
76
|
+
if valid? then send! __method else false end
|
77
|
+
end
|
78
|
+
else
|
79
|
+
define_method method do |*params|
|
80
|
+
if valid? then send! __method else raise errors.to_exception end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/not_naughty'
|
2
|
+
require 'sequel'
|
3
|
+
|
4
|
+
module Sequel #:nodoc:
|
5
|
+
module Plugins #:nodoc:
|
6
|
+
# == Adapter for Sequel ...
|
7
|
+
#
|
8
|
+
# ... is a Sequel::Plugin.
|
9
|
+
#
|
10
|
+
# ---
|
11
|
+
#
|
12
|
+
# <b>To make it overall available:</b>
|
13
|
+
#
|
14
|
+
# class Sequel::Model
|
15
|
+
# is :not_naughty
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# <b>To turn off before_validate and after_validate hooks:</b>
|
19
|
+
#
|
20
|
+
# class User < Sequel::Model
|
21
|
+
# is :not_naughty, :without => :hooks
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# <b>To turn off raised Exceptions if validation before save fails:</b>
|
25
|
+
#
|
26
|
+
# class User < Sequel::Model
|
27
|
+
# # save on invalid users will return false
|
28
|
+
# is :not_naughty, :without => :exceptions
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# <b>To combine those:</b>
|
32
|
+
#
|
33
|
+
# class User < Sequel::Model
|
34
|
+
# is :not_naughty, :without => [:exceptions, :hooks]
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
class NotNaughty < NotNaughty::Validator
|
38
|
+
|
39
|
+
# Applies plugin to a Sequel::Model.
|
40
|
+
def self.apply(receiver, *args)
|
41
|
+
receiver.extend ::NotNaughty
|
42
|
+
receiver.validator self, :create, :update
|
43
|
+
|
44
|
+
::NotNaughty::Validation.load(
|
45
|
+
:acceptance, :confirmation, :format,
|
46
|
+
:length, :numericality, :presence
|
47
|
+
)
|
48
|
+
|
49
|
+
receiver.extend ClassMethods
|
50
|
+
receiver.instance_eval { alias_method :save, :save! }
|
51
|
+
receiver.validated_before :save, *args
|
52
|
+
|
53
|
+
without = args.extract_options![:without]
|
54
|
+
receiver.send! :include, Hooks unless [*without].include? :hooks
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns state for given instance.
|
58
|
+
def get_state(instance)
|
59
|
+
if instance.new? then @states[:create] else @states[:update] end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Adds validation hooks to Sequel::Model.
|
63
|
+
module Hooks
|
64
|
+
def self.included(base)
|
65
|
+
base.instance_eval do
|
66
|
+
def_hook_method :before_validate
|
67
|
+
def_hook_method :after_validate
|
68
|
+
alias_method :validate_without_hooks, :validate
|
69
|
+
alias_method :validate, :validate_with_hooks
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def before_validate() end
|
74
|
+
def after_validate() end
|
75
|
+
|
76
|
+
def validate_with_hooks
|
77
|
+
before_validate
|
78
|
+
validate_without_hooks
|
79
|
+
after_validate
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Ensures Sequel::Model API compatibility.
|
84
|
+
module ClassMethods
|
85
|
+
|
86
|
+
# Returns the validations hash for the class.
|
87
|
+
def validations
|
88
|
+
validator.states.
|
89
|
+
inject({}) do |validations, state_with_name|
|
90
|
+
validations.merge(state_with_name[1].validations) {|k,o,n| o|n}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# Returns true if validations are defined.
|
94
|
+
def has_validations?()
|
95
|
+
validator.has_validations?
|
96
|
+
end
|
97
|
+
# Validates the given instance.
|
98
|
+
def validate(instance)
|
99
|
+
validator.invoke instance
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,69 @@
|
|
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 raise a NoMethodError is builder method does not exist" do
|
65
|
+
lambda { @validatable.validates() { bunch_of :holy_crap } }.
|
66
|
+
should raise_error(NoMethodError)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/spec/errors_spec.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require "#{ File.dirname(__FILE__) }/spec_helper.rb"
|
2
|
+
|
3
|
+
describe subject::Errors do
|
4
|
+
|
5
|
+
before(:each) { @errors = subject::Errors.new }
|
6
|
+
|
7
|
+
it "should add errors in a hash" do
|
8
|
+
@errors.add :attribute, 'Message'
|
9
|
+
|
10
|
+
@errors.instance_variable_get(:@errors)[:attribute].
|
11
|
+
should include('Message')
|
12
|
+
end
|
13
|
+
it "should return full messages" do
|
14
|
+
@errors.add :attribute, 'Message #{"%s".capitalize}'
|
15
|
+
|
16
|
+
@errors.full_messages.
|
17
|
+
should == ['Message Attribute']
|
18
|
+
end
|
19
|
+
it "should return an #{subject::ValidationException} with errors" do
|
20
|
+
exception = @errors.to_exception
|
21
|
+
exception.should be_an_instance_of(subject::ValidationException)
|
22
|
+
exception.errors.should == @errors
|
23
|
+
end
|
24
|
+
it "should have methods delegated" do
|
25
|
+
probe = mock 'Probe'
|
26
|
+
methods = [:empty?, :clear, :[], :each, :to_yaml]
|
27
|
+
|
28
|
+
methods.each { |m| probe.should_receive m }
|
29
|
+
@errors.instance_variable_set :@errors, probe
|
30
|
+
|
31
|
+
methods.each { |m| @errors.send! m }
|
32
|
+
end
|
33
|
+
it "should return evaluated errors messages" do
|
34
|
+
@errors.add :attribute, 'Message #{"%s".capitalize}'
|
35
|
+
@errors.on(:attribute).should == ['Message Attribute']
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe subject::ValidationException do
|
41
|
+
|
42
|
+
it "should return an exception with errors" do
|
43
|
+
probe = mock 'probe'
|
44
|
+
probe.should_receive(:any?).and_return(true)
|
45
|
+
exception = subject::ValidationException.new probe
|
46
|
+
|
47
|
+
lambda { raise exception }.should_not raise_error(TypeError)
|
48
|
+
exception.errors.should == probe
|
49
|
+
end
|
50
|
+
it "should have methods delegated" do
|
51
|
+
methods = [:on, :full_messages, :each]
|
52
|
+
|
53
|
+
probe = mock 'Probe'
|
54
|
+
probe.should_receive(:any?).and_return(true)
|
55
|
+
exception = subject::ValidationException.new probe
|
56
|
+
|
57
|
+
methods.each { |m| probe.should_receive m }
|
58
|
+
methods.each { |m| exception.send! m }
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,80 @@
|
|
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(:Errors)
|
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.should be_an_instance_of(subject::Validator)
|
23
|
+
|
24
|
+
validator = Class.new(subject::Validator)
|
25
|
+
validated = Class.new(Object).extend subject
|
26
|
+
validated.validator validator, :create, :update
|
27
|
+
validated.validator.should be_an_instance_of(validator)
|
28
|
+
validated.validator.states.keys.should include(:create, :update)
|
29
|
+
end
|
30
|
+
it "should extend the receiver if validator is defined" do
|
31
|
+
validated = Class.new(Object)
|
32
|
+
|
33
|
+
subject::Builder.should_receive(:extended).with(validated)
|
34
|
+
subject::InstanceMethods.should_receive(:included).with(validated)
|
35
|
+
|
36
|
+
validated.extend subject
|
37
|
+
end
|
38
|
+
it "should deep-clone the validator if inherited" do
|
39
|
+
super_validated = Class.new(Object).extend subject
|
40
|
+
super_validated.validator.add_validation(:name) {|o, a, v|}
|
41
|
+
validated = Class.new(super_validated)
|
42
|
+
|
43
|
+
validated.validator.should_not == super_validated.validator
|
44
|
+
validated.validator.should have_validations
|
45
|
+
validated.validator.add_validation(:name) {|o, a, v|}
|
46
|
+
|
47
|
+
super_validated.validator.get_state.validations[:name].length.
|
48
|
+
should < validated.validator.get_state.validations[:name].length
|
49
|
+
end
|
50
|
+
it "should prepend a validation before any method" do
|
51
|
+
validated = Class.new(Object).extend subject
|
52
|
+
|
53
|
+
validated.validated_before :clone
|
54
|
+
instance = validated.new
|
55
|
+
instance.should_receive(:valid?).once.and_return(true)
|
56
|
+
instance.should_receive(:clone_without_validations)
|
57
|
+
instance.clone
|
58
|
+
end
|
59
|
+
it "should raise an exception if invalid and method called" do
|
60
|
+
validated = Class.new(Object).extend subject
|
61
|
+
|
62
|
+
validated.validated_before :clone
|
63
|
+
instance = validated.new
|
64
|
+
instance.should_receive(:valid?).once.and_return(false)
|
65
|
+
lambda { instance.clone }.should raise_error(subject::ValidationException)
|
66
|
+
end
|
67
|
+
it "should return false if invalid and method called" do
|
68
|
+
validated = Class.new(Object).extend subject
|
69
|
+
|
70
|
+
validated.validated_before :clone, :without => :exception
|
71
|
+
instance = validated.new
|
72
|
+
instance.should_receive(:valid?).once.and_return(false)
|
73
|
+
instance.clone.should == false
|
74
|
+
end
|
75
|
+
it "should add Builder to Validation observers" do
|
76
|
+
subject::Validation.instance_variable_get(:@observer_peers).
|
77
|
+
should include(subject::Builder)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
data/spec/rcov.opts
ADDED