not_naughty 0.3
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 +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