not-naughty 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
=== 0.6 (2008-10-08)
|
2
|
+
* fixed Rakefile
|
3
|
+
* removed whitespaces
|
4
|
+
* removed Ruby-Sequel adapter (becomes an extra gem)
|
5
|
+
* removed assistance gem dependency
|
6
|
+
|
7
|
+
=== 0.5.1 (2008-03-17)
|
8
|
+
* fixed missing dependency
|
9
|
+
|
10
|
+
=== 0.5 (2008-03-17)
|
11
|
+
* added an error handler, can now handle SQL validation errors
|
12
|
+
* renamed Errors to Violation and merged it with ValidationException
|
13
|
+
* changed specs for 0.4.1
|
14
|
+
|
15
|
+
=== 0.4.2 (2008-03-12)
|
16
|
+
* fixed bug that causes infinite recursion in some apps that reload classes
|
17
|
+
|
18
|
+
=== 0.4.1 (2008-03-12)
|
19
|
+
* some minor changes in validator method
|
20
|
+
|
21
|
+
=== 0.4 (2008-02-18)
|
22
|
+
* completed documentation
|
23
|
+
* added spec for :each in Builder::ValidationDelegator
|
24
|
+
|
25
|
+
=== 0.3.1 (2008-02-14)
|
26
|
+
* fixed missing require
|
27
|
+
|
28
|
+
=== 0.3 (2008-02-09)
|
29
|
+
* renamed to NotNaughty - The Validation Framework
|
30
|
+
* made predefined Validation optional, you can load them with:
|
31
|
+
|
32
|
+
::NotNaughty::Validation.load '*'
|
33
|
+
|
34
|
+
or ordered (should be preferred):
|
35
|
+
|
36
|
+
::NotNaughty::Validation.load :acceptance, :confirmation, :format, :length,
|
37
|
+
:numericality, :presence
|
38
|
+
|
39
|
+
* specs pass, 100% code coverage, 2 not complete, so pending specs
|
40
|
+
* docs added
|
41
|
+
|
42
|
+
=== 0.2 (2008-02-09)
|
43
|
+
* complete rewrite
|
44
|
+
|
45
|
+
=== 0.1 (2008-02-06)
|
46
|
+
* initial release
|
data/COPYING
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2007-2008 Florian Aßmann
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
= NotNaughty - The Validation Framework
|
2
|
+
|
3
|
+
NotNaughty extends your ruby Project with a highly custumizable validation API.
|
4
|
+
|
5
|
+
== Features
|
6
|
+
|
7
|
+
<b>Easy to adapt:</b>
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'not_naughty'
|
11
|
+
|
12
|
+
Person = Struct.new(:name) do
|
13
|
+
extend NotNaughty
|
14
|
+
validates(:name) { presence and length :minimum => 4 }
|
15
|
+
end
|
16
|
+
|
17
|
+
Person.new('Horst').valid? # => true
|
18
|
+
Person.new('Foo').valid? # => false
|
19
|
+
|
20
|
+
<b>Easy to extent:</b>
|
21
|
+
|
22
|
+
class ExampleValidation < NotNaughty::Validation
|
23
|
+
def initialize(options, attributes)
|
24
|
+
msg = options[:message] || '#{"%s".humanize} is not an example.'
|
25
|
+
|
26
|
+
super options, attributes do |record, attribute, value|
|
27
|
+
record.errors.add(attribute, msg) unless value.is_a? Example
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Person.instance_eval do
|
33
|
+
validates_example_of :example_attribute
|
34
|
+
end
|
35
|
+
|
36
|
+
<b>Handle SQL error gracefully:</b>
|
37
|
+
|
38
|
+
Person.instance_eval do
|
39
|
+
validator.error_handler.handle(SQLError) { |err| '...' }
|
40
|
+
end
|
41
|
+
|
42
|
+
<b>Syntactical Sugar with Builder methods:</b>
|
43
|
+
|
44
|
+
validates(:username, :password, :if => :new?) {length :minimum => 6}
|
45
|
+
validates(:password, :allow_blank => 1) {confirmation and complexity :high}
|
46
|
+
|
47
|
+
<b>Beautiful error messages:</b>
|
48
|
+
|
49
|
+
validates_presence_of :red_shoes,
|
50
|
+
:message => '#{"%s".humanize} are not here.' # => Red shoes are not here.
|
51
|
+
|
52
|
+
<b>Conditional Validations:</b>
|
53
|
+
|
54
|
+
validates(:if => :necessary?) {...}
|
55
|
+
validates(:unless => proc {|obj| obj.vip?}) { '...' }
|
56
|
+
|
57
|
+
== Copying
|
58
|
+
|
59
|
+
:include: COPYING
|
data/Rakefile
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "rake/clean"
|
3
|
+
require "rake/gempackagetask"
|
4
|
+
require "rake/rdoctask"
|
5
|
+
require "fileutils"
|
6
|
+
include FileUtils
|
7
|
+
|
8
|
+
##############################################################################
|
9
|
+
# Configuration
|
10
|
+
##############################################################################
|
11
|
+
NAME = "not-naughty"
|
12
|
+
VERS = "0.6.0"
|
13
|
+
CLEAN.include %w[ pkg doc coverage ]
|
14
|
+
RDOC_OPTS = [
|
15
|
+
"--quiet",
|
16
|
+
"--title", "NotNaughty: The Validation Framework",
|
17
|
+
"--opname", "index.html",
|
18
|
+
"--inline-source",
|
19
|
+
"--line-numbers",
|
20
|
+
"--main", "README.rdoc",
|
21
|
+
"--inline-source",
|
22
|
+
"--charset", "utf-8"
|
23
|
+
]
|
24
|
+
|
25
|
+
##############################################################################
|
26
|
+
# RDoc
|
27
|
+
##############################################################################
|
28
|
+
task :doc => [:rdoc]
|
29
|
+
|
30
|
+
Rake::RDocTask.new do |rdoc|
|
31
|
+
rdoc.rdoc_dir = "doc"
|
32
|
+
rdoc.options += RDOC_OPTS
|
33
|
+
rdoc.main = "README.rdoc"
|
34
|
+
rdoc.title = "NotNaughty: The Validation Framework"
|
35
|
+
rdoc.rdoc_files.add %w[README.rdoc COPYING CHANGELOG.rdoc lib/**/*.rb]
|
36
|
+
end
|
37
|
+
|
38
|
+
task :doc_rforge => [:doc]
|
39
|
+
|
40
|
+
desc "Update docs and upload to rubyforge.org"
|
41
|
+
task :doc_rforge do
|
42
|
+
sh %{scp -r doc/rdoc/* boof@rubyforge.org:/var/www/gforge-projects/not-naughty}
|
43
|
+
end
|
44
|
+
|
45
|
+
##############################################################################
|
46
|
+
# specs
|
47
|
+
##############################################################################
|
48
|
+
require "spec/rake/spectask"
|
49
|
+
|
50
|
+
desc "Run specs with coverage"
|
51
|
+
Spec::Rake::SpecTask.new("spec") do |t|
|
52
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
53
|
+
t.spec_opts = File.read("spec/spec.opts").split("\n")
|
54
|
+
t.rcov_opts = File.read("spec/rcov.opts").split("\n")
|
55
|
+
t.rcov = true
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "Run specs without coverage"
|
59
|
+
Spec::Rake::SpecTask.new("spec_no_cov") do |t|
|
60
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
61
|
+
t.spec_opts = File.read("spec/spec.opts").split("\n")
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "check documentation coverage"
|
65
|
+
task :dcov do
|
66
|
+
sh "find lib -name '*.rb' | xargs dcov"
|
67
|
+
end
|
68
|
+
|
69
|
+
##############################################################################
|
70
|
+
# Gem packaging
|
71
|
+
##############################################################################
|
72
|
+
desc "Packages up NotNaughty."
|
73
|
+
task :default => [:package]
|
74
|
+
task :package => [:clean]
|
75
|
+
|
76
|
+
spec = Gem::Specification.new do |s|
|
77
|
+
s.name = NAME
|
78
|
+
s.rubyforge_project = NAME
|
79
|
+
s.version = VERS
|
80
|
+
s.platform = Gem::Platform::RUBY
|
81
|
+
s.has_rdoc = true
|
82
|
+
s.extra_rdoc_files = ["README.rdoc", "CHANGELOG.rdoc", "COPYING"]
|
83
|
+
s.summary = "Heavily armed validation framework."
|
84
|
+
s.description = s.summary
|
85
|
+
s.author = "Florian Aßmann"
|
86
|
+
s.email = "boof@monkey-patch.me"
|
87
|
+
s.homepage = "http://monkey-patch.me/p/notnaughty"
|
88
|
+
s.required_ruby_version = ">= 1.8.6"
|
89
|
+
s.add_dependency("rubytree", ">= 0.5.2")
|
90
|
+
|
91
|
+
s.files = %w(COPYING README.rdoc Rakefile) + Dir.glob("{spec,lib}/**/*")
|
92
|
+
|
93
|
+
s.require_path = "lib"
|
94
|
+
end
|
95
|
+
|
96
|
+
Rake::GemPackageTask.new(spec) do |p|
|
97
|
+
p.need_tar = true
|
98
|
+
p.gem_spec = spec
|
99
|
+
end
|
100
|
+
|
101
|
+
##############################################################################
|
102
|
+
# installation & removal
|
103
|
+
##############################################################################
|
104
|
+
task :install do
|
105
|
+
sh %{rake package}
|
106
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS}}
|
107
|
+
end
|
108
|
+
|
109
|
+
task :install_no_docs do
|
110
|
+
sh %{rake package}
|
111
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS} --no-rdoc --no-ri}
|
112
|
+
end
|
113
|
+
|
114
|
+
task :uninstall => [:clean] do
|
115
|
+
sh %{sudo gem uninstall #{NAME}}
|
116
|
+
end
|
117
|
+
|
118
|
+
##############################################################################
|
119
|
+
# gem and rdoc release
|
120
|
+
##############################################################################
|
121
|
+
task :release => [:package] do
|
122
|
+
sh %{rubyforge login}
|
123
|
+
sh %{rubyforge add_release #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.tgz}
|
124
|
+
sh %{rubyforge add_file #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.gem}
|
125
|
+
end
|
126
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Array #:nodoc:
|
2
|
+
instance_methods.include? 'extract_options!' or
|
3
|
+
define_method(:extract_options!) { Hash === last ? pop : {} }
|
4
|
+
end
|
5
|
+
class Object #:nodoc:
|
6
|
+
instance_methods.include? 'blank?' or
|
7
|
+
define_method(:blank?) { respond_to?(:empty?) ? empty? : !self }
|
8
|
+
end
|
9
|
+
class String #:nodoc:
|
10
|
+
instance_methods.include? 'blank?' or
|
11
|
+
define_method(:blank?) { self !~ /\S/ }
|
12
|
+
end
|
13
|
+
class Numeric #:nodoc:
|
14
|
+
instance_methods.include? 'blank?' or
|
15
|
+
define_method(:blank?) { false }
|
16
|
+
end
|
17
|
+
|
18
|
+
def nil.blank?() true end unless nil.respond_to? :blank?
|
19
|
+
def true.blank?() false end unless true.respond_to? :blank?
|
20
|
+
def false.blank?() true end unless false.respond_to? :blank?
|
data/lib/not_naughty.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'observer'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
gem 'rubytree', '>= 0.5.2'
|
7
|
+
require 'tree'
|
8
|
+
|
9
|
+
$:.unshift File.dirname(__FILE__)
|
10
|
+
require 'core_extensions'
|
11
|
+
|
12
|
+
module NotNaughty
|
13
|
+
require 'not_naughty/validator'
|
14
|
+
|
15
|
+
require 'not_naughty/builder'
|
16
|
+
require 'not_naughty/validation'
|
17
|
+
Validation.add_observer Builder
|
18
|
+
|
19
|
+
require 'not_naughty/violation'
|
20
|
+
require 'not_naughty/error_handler'
|
21
|
+
require 'not_naughty/instance_methods'
|
22
|
+
|
23
|
+
# Extended classes get NotNaughty::Builder and NotNaughty::InstanceMethods.
|
24
|
+
def self.extended(base)
|
25
|
+
base.instance_eval do
|
26
|
+
include InstanceMethods
|
27
|
+
extend Builder
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# call-seq:
|
32
|
+
# validator(validator_klass = NotNaughty::Validator, *states = [:default])
|
33
|
+
#
|
34
|
+
# Returns instance of Validator. This is either the validator's clone of
|
35
|
+
# superclass, an instance of the the given descendant of or the
|
36
|
+
# <tt>NotNaughty:Validator</tt> himself.
|
37
|
+
#
|
38
|
+
# <b>Examples:</b>
|
39
|
+
# validator # => Instance of NotNaughty::Validator with :default state
|
40
|
+
# validator :create, :update # ~ - but with :create and :update states
|
41
|
+
# validator AnotherValidator # Instance of AnotherValidator
|
42
|
+
#
|
43
|
+
# validator AnotherValidator, :state_a, :state_b
|
44
|
+
#
|
45
|
+
# The above examples work as long validator is not already called. To reset
|
46
|
+
# an already assigned validator set <tt>@validator</tt> to nil.
|
47
|
+
def validator(*states)
|
48
|
+
@validator ||= if !states.empty?
|
49
|
+
validator_klass =
|
50
|
+
if states.first.is_a? Class and states.first <= NotNaughty::Validator
|
51
|
+
states.shift
|
52
|
+
else
|
53
|
+
NotNaughty::Validator
|
54
|
+
end
|
55
|
+
|
56
|
+
validator_klass.new(*states)
|
57
|
+
|
58
|
+
elsif superclass.respond_to? :validator
|
59
|
+
superclass.validator.clone
|
60
|
+
|
61
|
+
else
|
62
|
+
NotNaughty::Validator.new
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Prepends a call for validation before then given method. If, on call, the
|
68
|
+
# validation passes the method is called. Otherwise it raises an
|
69
|
+
# NotNaughty::ValidationException or returns false.
|
70
|
+
#
|
71
|
+
# <b>Example:</b>
|
72
|
+
# validated_before :save # raise ValidationException unless valid?
|
73
|
+
def validated_before(method)
|
74
|
+
__method = :"#{method}_without_validations"
|
75
|
+
alias_method __method, method
|
76
|
+
|
77
|
+
define_method method do |*params|
|
78
|
+
begin
|
79
|
+
if valid? then send __method else raise errors end
|
80
|
+
rescue Exception => error
|
81
|
+
self.class.validator.error_handler.raise error
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Builder that builds
|
4
|
+
#
|
5
|
+
# With this you get syntactical sugar for all descendants of Validation, see
|
6
|
+
# validates for examples.
|
7
|
+
module Builder
|
8
|
+
|
9
|
+
# Observer method that creates Validation builder methods.
|
10
|
+
#
|
11
|
+
# A Validation with the class name TestValidation will get the
|
12
|
+
# builder method <tt>validate_test_of</tt> that adds a validation via
|
13
|
+
# add_validation in the current <tt>validator</tt>.
|
14
|
+
def self.update(validation)
|
15
|
+
if basename = validation.name[/([^:]+)Validation$/, 1]
|
16
|
+
define_method 'validates_%s_of' % basename.downcase do |*params|
|
17
|
+
validator.add_validation(validation, *params)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# == Syntactic sugar.
|
23
|
+
#
|
24
|
+
# <b>Example:</b>
|
25
|
+
# validates { presence_of :example, :if => :reading? }
|
26
|
+
# # => validate_presence_of :example, :if => :reading?
|
27
|
+
# validates(:name) { presence and length :minimum => 6 }
|
28
|
+
# # => validates_presence_of :name
|
29
|
+
# validates_length_of :name, :minimum => 6
|
30
|
+
# validates(:temp, :if => water?) { value :lt => 100 }
|
31
|
+
# # => validates_value_of :temp, :lt => 100, :if => water?
|
32
|
+
def validates(*params, &block)
|
33
|
+
ValidationDelegator.new(self, *params).instance_eval(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Allows adding validations the legacy way.
|
37
|
+
def validates_each(*attributes, &block)
|
38
|
+
validator.add_validation(*attributes, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
class ValidationDelegator < SimpleDelegator #:nodoc:all
|
42
|
+
def initialize(receiver, *params)
|
43
|
+
@_sd_obj_opts = params.extract_options!
|
44
|
+
@_sd_obj_params = params
|
45
|
+
|
46
|
+
super receiver
|
47
|
+
end
|
48
|
+
def method_missing(method_sym, *params) #:nodoc:
|
49
|
+
method_sym, params = unless @_sd_obj_params.empty?
|
50
|
+
opts = @_sd_obj_opts.update params.extract_options!
|
51
|
+
[:"validates_#{method_sym}_of", @_sd_obj_params + [opts]]
|
52
|
+
else
|
53
|
+
opts = @_sd_obj_opts.update params.extract_options!
|
54
|
+
[:"validates_#{method_sym}", params + [opts]]
|
55
|
+
end
|
56
|
+
|
57
|
+
if @_sd_obj.respond_to? method_sym
|
58
|
+
@_sd_obj.send(method_sym, *params)
|
59
|
+
return true
|
60
|
+
else
|
61
|
+
raise NoMethodError,
|
62
|
+
"unable to evaluate ´#{method_sym}(*#{params.inspect})'"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Tree #:nodoc:
|
2
|
+
class TreeNode #:nodoc:
|
3
|
+
|
4
|
+
def closest(obj)
|
5
|
+
if @name == obj then self
|
6
|
+
elsif @name > obj
|
7
|
+
closest = self
|
8
|
+
@children.any? { |child| node = child.closest(obj) and closest = node }
|
9
|
+
|
10
|
+
closest
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module NotNaughty
|
18
|
+
class ErrorHandler
|
19
|
+
|
20
|
+
def initialize(handler = Kernel)
|
21
|
+
@handles = Tree::TreeNode.new Exception, proc { |e| handler.raise e }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Calls closest handle with exception.
|
25
|
+
def raise(exception)
|
26
|
+
handle = @handles.closest exception.class
|
27
|
+
handle.content.call exception
|
28
|
+
end
|
29
|
+
|
30
|
+
# Inserts handle into the ordered tree.
|
31
|
+
def handle(exception_class, &block)
|
32
|
+
closest_handle = @handles.closest exception_class
|
33
|
+
|
34
|
+
if closest_handle == exception_class then closest_handle.content = block
|
35
|
+
else
|
36
|
+
new_handle = Tree::TreeNode.new exception_class, block
|
37
|
+
|
38
|
+
closest_handle.children do |child|
|
39
|
+
exception_class > child.name and
|
40
|
+
new_handle << closest_handle.remove!(child)
|
41
|
+
end
|
42
|
+
|
43
|
+
closest_handle << new_handle
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|