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
data/CHANGELOG
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
=== 0.3 (2008-02-09)
|
2
|
+
|
3
|
+
* renamed to NotNaughty - The Validation Framework
|
4
|
+
* made predefined Validation optional, you can load them with:
|
5
|
+
|
6
|
+
::NotNaughty::Validation.load '*'
|
7
|
+
|
8
|
+
or ordered (should be preferred):
|
9
|
+
|
10
|
+
::NotNaughty::Validation.load :acceptance, :confirmation, :format, :length,
|
11
|
+
:numericality, :presence
|
12
|
+
|
13
|
+
* specs pass, 100% code coverage, 2 not complete, so pending specs
|
14
|
+
* docs added
|
15
|
+
|
16
|
+
=== 0.2 (2008-02-09)
|
17
|
+
|
18
|
+
* complete rewrite
|
19
|
+
|
20
|
+
=== 0.1 (2008-02-06)
|
21
|
+
|
22
|
+
* 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
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
= NotNaughty - The Validation Framework
|
2
|
+
|
3
|
+
<b>Features:</b>
|
4
|
+
* API compatible to Sequel as plugin (see Sequel::Plugins::NotNaughty for
|
5
|
+
details):
|
6
|
+
|
7
|
+
class User < Sequel::Model
|
8
|
+
is :not_naughty
|
9
|
+
validates { presence_of :username and length_of :username, :within => 4..16 }
|
10
|
+
has_validations? # => true
|
11
|
+
|
12
|
+
# ...
|
13
|
+
end
|
14
|
+
|
15
|
+
* Syntactical Sugar with Builder methods:
|
16
|
+
|
17
|
+
validates(:username, :password) {length :minimum => 6}
|
18
|
+
validates(:password) {confirmed and complexity :level => :high}
|
19
|
+
validates(:if => :necessary?) {bunchyness_of :crap}
|
20
|
+
|
21
|
+
* Beautiful error messages:
|
22
|
+
|
23
|
+
validates_presence_of :person,
|
24
|
+
:message => '#{"%s".humanize} is gone missing.'
|
25
|
+
|
26
|
+
* Conditional Validations:
|
27
|
+
|
28
|
+
validates(:if => :necessary?) {...}
|
29
|
+
validates(:unless => proc {|obj| obj.vip?}) {...}
|
30
|
+
|
31
|
+
* Easy to adapt and to extent:
|
32
|
+
|
33
|
+
_adapt_
|
34
|
+
require 'rubygems'
|
35
|
+
require 'not_naughty'
|
36
|
+
Person = Struct.new(:name) do
|
37
|
+
extend NotNaughty
|
38
|
+
validates(:name) { presence and length :minimum => 4 }
|
39
|
+
validated_before :clone
|
40
|
+
validated_before :dup, :without => :exception
|
41
|
+
end
|
42
|
+
Person.new('Horst').valid? # => true
|
43
|
+
Person.new('Foo').valid? # => false
|
44
|
+
Person.new('Foo').clone # goes *boom* with NotNaughty::ValidationException
|
45
|
+
Person.new('Foo').dup # => false
|
46
|
+
|
47
|
+
_extend_
|
48
|
+
class BunchynessValidation < NotNaughty::Validation
|
49
|
+
def initialize(opts, attributes)
|
50
|
+
__message = opts[:message] || '#{"%s".humanize} is not bunchy.'
|
51
|
+
super opts, attributes do |o, a, v|
|
52
|
+
o.errors.add(a, __message) unless v.respond_to? :to_bunchy
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
Thingy = Struct.new(:bunchy_item) do
|
58
|
+
extend NotNaughty
|
59
|
+
validates_bunchyness_of :bunchy_item
|
60
|
+
end
|
61
|
+
|
62
|
+
:include: COPYING
|
data/Rakefile
ADDED
@@ -0,0 +1,130 @@
|
|
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.3"
|
13
|
+
CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "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",
|
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/rdoc"
|
32
|
+
rdoc.options += RDOC_OPTS
|
33
|
+
rdoc.main = "README"
|
34
|
+
rdoc.title = "NotNaughty: The Validation Framework"
|
35
|
+
rdoc.rdoc_files.add %w[README COPYING 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", "CHANGELOG", "COPYING"]
|
83
|
+
s.summary = "Heavily armed validation framework."
|
84
|
+
s.description = s.summary
|
85
|
+
s.author = "Florian Aßmann"
|
86
|
+
s.email = "florian.assmann@oniversus.de"
|
87
|
+
s.homepage = "http://not-naughty.rubyforge.org"
|
88
|
+
s.required_ruby_version = ">= 1.8.4"
|
89
|
+
|
90
|
+
s.files = %w(COPYING README Rakefile) + Dir.glob("{doc,spec,lib}/**/*")
|
91
|
+
|
92
|
+
s.require_path = "lib"
|
93
|
+
end
|
94
|
+
|
95
|
+
Rake::GemPackageTask.new(spec) do |p|
|
96
|
+
p.need_tar = true
|
97
|
+
p.gem_spec = spec
|
98
|
+
end
|
99
|
+
|
100
|
+
##############################################################################
|
101
|
+
# installation & removal
|
102
|
+
##############################################################################
|
103
|
+
task :install do
|
104
|
+
sh %{rake package}
|
105
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS}}
|
106
|
+
end
|
107
|
+
|
108
|
+
task :install_no_docs do
|
109
|
+
sh %{rake package}
|
110
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS} --no-rdoc --no-ri}
|
111
|
+
end
|
112
|
+
|
113
|
+
task :uninstall => [:clean] do
|
114
|
+
sh %{sudo gem uninstall #{NAME}}
|
115
|
+
end
|
116
|
+
|
117
|
+
task :tag do
|
118
|
+
cwd = FileUtils.pwd
|
119
|
+
sh %{cd .. && svn copy #{cwd} tags/#{NAME}-#{VERS} && svn commit -m "#{NAME}-#{VERS} tag." tags}
|
120
|
+
end
|
121
|
+
|
122
|
+
##############################################################################
|
123
|
+
# gem and rdoc release
|
124
|
+
##############################################################################
|
125
|
+
task :release => [:package] do
|
126
|
+
sh %{rubyforge login}
|
127
|
+
sh %{rubyforge add_release #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.tgz}
|
128
|
+
sh %{rubyforge add_file #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.gem}
|
129
|
+
end
|
130
|
+
|
@@ -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 = if @_sd_obj_params.any?
|
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,61 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Container for failed validations.
|
4
|
+
#
|
5
|
+
# ...
|
6
|
+
class Errors
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@errors, :empty?, :clear, :[], :each, :to_yaml
|
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
|
+
# Returns a ValidationException with <tt>self</tt> in <tt>:errors</tt>.
|
35
|
+
def to_exception
|
36
|
+
ValidationException.new self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# == Exception class for NotNaughty
|
41
|
+
#
|
42
|
+
# Includes the instance of Errors that caused the Exception.
|
43
|
+
class ValidationException < RuntimeError
|
44
|
+
extend Forwardable
|
45
|
+
|
46
|
+
attr_reader :errors
|
47
|
+
def_delegators :@errors, :on, :full_messages, :each
|
48
|
+
|
49
|
+
# Returns instance of ValidationError with errors set
|
50
|
+
def initialize(errors)
|
51
|
+
@errors = errors
|
52
|
+
|
53
|
+
if errors.any?
|
54
|
+
super 'validation errors'
|
55
|
+
else
|
56
|
+
super 'no validation errors'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
module InstanceMethods
|
3
|
+
|
4
|
+
# Returns instance of Errors.
|
5
|
+
def errors() @errors ||= ::NotNaughty::Errors.new end
|
6
|
+
|
7
|
+
# Returns true if all validations passed
|
8
|
+
def valid?
|
9
|
+
validate
|
10
|
+
errors.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
# Clears errors and validates.
|
14
|
+
def validate
|
15
|
+
errors.clear
|
16
|
+
self.class.validator.invoke self
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == The superclass for Validations.
|
4
|
+
#
|
5
|
+
# See new for more information.
|
6
|
+
class Validation
|
7
|
+
|
8
|
+
BASEDIR = File.dirname __FILE__
|
9
|
+
# Loader pattern
|
10
|
+
PATTERN = File.join BASEDIR, %w[validations %s_validation.rb]
|
11
|
+
|
12
|
+
# Loads validations.
|
13
|
+
def self.load(*validations)
|
14
|
+
validations.each do |validation|
|
15
|
+
Dir.glob(PATTERN % validation).each { |path| require path }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
extend Observable
|
20
|
+
def self.inherited(descendant) #:nodoc:
|
21
|
+
changed and notify_observers(descendant)
|
22
|
+
|
23
|
+
descendant.
|
24
|
+
instance_variable_set :@observer_peers, @observer_peers.clone
|
25
|
+
end
|
26
|
+
|
27
|
+
# Builds validations.
|
28
|
+
#
|
29
|
+
# <b>Example:</b>
|
30
|
+
# NotNaughty::Validation.new :temp, :if => :water? do |obj, attr, val|
|
31
|
+
# obj.errors.add attr, 'too hot' unless val < 100
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# <b>Like:</b>
|
35
|
+
# class TempValidation < NotNaughty::Validation
|
36
|
+
# def initialize(opts, attributes)
|
37
|
+
# super opts, attributes method(:temp_validation)
|
38
|
+
# end
|
39
|
+
# def temp_validation(obj, attr, val)
|
40
|
+
# obj.errors.add attr, 'too hot' unless val < 100
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Validation.new TempValidation, :temp, :if => :water?
|
45
|
+
#
|
46
|
+
# The last one also notifies all Observers of Validation (see
|
47
|
+
# Builder#update). If Builder#update is called because <Name>Validation
|
48
|
+
# is inherited from Validation the ValidationBuilder gets the method
|
49
|
+
# validates_<name>_of and so does the classes that included the Builder.
|
50
|
+
def self.new(*params, &block)
|
51
|
+
attributes = if params.first.is_a? Class and params.first < self
|
52
|
+
klass = params.shift
|
53
|
+
klass.new(*params, &block)
|
54
|
+
else
|
55
|
+
options = params.extract_options!
|
56
|
+
instance = allocate
|
57
|
+
instance.send! :initialize, options, params.map {|p|p.to_sym}, &block
|
58
|
+
instance
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_reader :attributes
|
63
|
+
|
64
|
+
def initialize(opts, attributes, &block) #:nodoc:
|
65
|
+
build_conditions opts[:if], opts[:unless]
|
66
|
+
@attributes, @block, @opts = attributes, block, opts
|
67
|
+
end
|
68
|
+
|
69
|
+
def call_without_conditions(obj, attr, value) #:nodoc:
|
70
|
+
@block.call obj, attr, value
|
71
|
+
end
|
72
|
+
alias_method :call, :call_without_conditions
|
73
|
+
|
74
|
+
def call_with_conditions(obj, attr, value) #:nodoc:
|
75
|
+
if @conditions.all? { |c| c.evaluate obj }
|
76
|
+
call_without_conditions obj, attr, value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
def build_conditions(p, n) #:nodoc:
|
82
|
+
@conditions = []
|
83
|
+
[p].flatten.each {|c| @conditions << Condition.new(c) if c }
|
84
|
+
[n].flatten.each {|c| @conditions << Condition.new(c, false) if c }
|
85
|
+
|
86
|
+
(class << self; self; end).module_eval do
|
87
|
+
alias_method :call, :call_with_conditions
|
88
|
+
end if @conditions.any?
|
89
|
+
end
|
90
|
+
|
91
|
+
# Conditions for use in Validations are usually used with Validations.
|
92
|
+
class Condition
|
93
|
+
|
94
|
+
# An instance of Condition accepts Symbols, UnboundMethods or anything
|
95
|
+
# that responds to :call.
|
96
|
+
#
|
97
|
+
# The following examples are similiar to each other:
|
98
|
+
#
|
99
|
+
# NotNaughty::Validation::Condition.new proc {|o| o.nil?}
|
100
|
+
# NotNaughty::Validation::Condition.new :nil?
|
101
|
+
# NotNaughty::Validation::Condition.new Object.instance_method(:nil?)
|
102
|
+
def self.new(condition, positive = true)
|
103
|
+
instance = allocate
|
104
|
+
instance.instance_variable_set :@condition, condition
|
105
|
+
|
106
|
+
block = case condition
|
107
|
+
when Symbol then positive ?
|
108
|
+
proc { |o| o.send! @condition } :
|
109
|
+
proc { |o| not o.send! @condition }
|
110
|
+
when UnboundMethod then positive ?
|
111
|
+
proc { |o| @condition.bind(o).call } :
|
112
|
+
proc { |o| not @condition.bind(o).call }
|
113
|
+
else positive ?
|
114
|
+
proc { |o| @condition.call o } :
|
115
|
+
proc { |o| not @condition.call o }
|
116
|
+
end
|
117
|
+
|
118
|
+
(class << instance; self; end).
|
119
|
+
module_eval { define_method(:evaluate, &block) }
|
120
|
+
|
121
|
+
instance
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Validates acceptance of obj's attribute against a fixed value.
|
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>:accept</tt>:: object that that'll check via a <tt>:match</tt> call
|
10
|
+
# <tt>:message</tt>:: see NotNaughty::Errors for details
|
11
|
+
# <tt>:if</tt>:: see NotNaughty::Validation::Condition for details
|
12
|
+
# <tt>:unless</tt>:: see NotNaughty::Validation::Condition for details
|
13
|
+
#
|
14
|
+
# <b>Example:</b>
|
15
|
+
#
|
16
|
+
# obj = 'abc'
|
17
|
+
# def obj.errors() @errors ||= NotNauthy::Errors.new end
|
18
|
+
#
|
19
|
+
class AcceptanceValidation < Validation
|
20
|
+
|
21
|
+
def initialize(opts, attributes)
|
22
|
+
__message, __accept =
|
23
|
+
opts[:message] || '#{"%s".humanize} not accepted.',
|
24
|
+
opts[:accept] || '1'
|
25
|
+
|
26
|
+
if opts[:allow_blank] or opts.fetch(:allow_nil, true)
|
27
|
+
__allow = if opts[:allow_blank] then :blank? else :nil? end
|
28
|
+
super opts, attributes do |o, a, v|
|
29
|
+
o.errors.add a, __message unless v.send! __allow or __accept.eql? v
|
30
|
+
end
|
31
|
+
else
|
32
|
+
super opts, attributes do |o, a, v|
|
33
|
+
o.errors.add a, __message unless __accept.eql? v
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Validates confirmaton of obj's attribute via <tt>:eql?</tt> method against the _appropiate_ confirmation attribute.
|
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 = 'abc'
|
16
|
+
# def obj.errors() @errors ||= NotNauthy::Errors.new end
|
17
|
+
# def obj.to_s_confirmation() '123 end
|
18
|
+
#
|
19
|
+
# ConfirmationValidation.new({}, :to_s).call obj, :to_s, 'abc'
|
20
|
+
# obj.errors.on(:to_s).any? # => true
|
21
|
+
class ConfirmationValidation < Validation
|
22
|
+
|
23
|
+
def initialize(opts, attributes) #:nodoc:
|
24
|
+
__message = opts[:message] || '#{"%s".humanize} could not be confirmed.'
|
25
|
+
|
26
|
+
if opts[:allow_blank] or opts[:allow_nil]
|
27
|
+
__allow = if opts[:allow_blank] then :blank? else :nil? end
|
28
|
+
super opts, attributes do |o, a, v|
|
29
|
+
o.errors.add a, __message unless
|
30
|
+
v.send! __allow or o.send!(:"#{a}_confirmation").eql? v
|
31
|
+
end
|
32
|
+
else
|
33
|
+
super opts, attributes do |o, a, v|
|
34
|
+
o.errors.add a, __message unless
|
35
|
+
o.send!(:"#{a}_confirmation").eql? v
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module NotNaughty
|
2
|
+
|
3
|
+
# == Validates format of obj's attribute via the <tt>:match</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>:with</tt>:: object that that'll check via a <tt>:match</tt> call
|
10
|
+
# <tt>:message</tt>:: see NotNaughty::Errors for details
|
11
|
+
# <tt>:if</tt>:: see NotNaughty::Validation::Condition for details
|
12
|
+
# <tt>:unless</tt>:: see NotNaughty::Validation::Condition for details
|
13
|
+
#
|
14
|
+
# <b>Example:</b>
|
15
|
+
#
|
16
|
+
# obj = 'abc'
|
17
|
+
# def obj.errors() @errors ||= NotNauthy::Errors.new end
|
18
|
+
#
|
19
|
+
# FormatValidation.new({:with => /[a-z]/}, :to_s).call obj, :to_s, 'abc'
|
20
|
+
# obj.errors.on(:to_s).any? # => false
|
21
|
+
#
|
22
|
+
# FormatValidation.new({:with => /[A-Z]/}, :to_s).call obj, :to_s, 'abc'
|
23
|
+
# obj.errors.on(:to_s) # => ["Format of to_s does not match."]
|
24
|
+
class FormatValidation < Validation
|
25
|
+
|
26
|
+
def initialize(opts, attributes) #:nodoc:
|
27
|
+
(__format = opts[:with]).respond_to? :match or
|
28
|
+
raise ArgumentError, "#{__format.inspect} doesn't respond to :match"
|
29
|
+
|
30
|
+
__message = opts[:message] || 'Format of %s does not match.'
|
31
|
+
|
32
|
+
if opts[:allow_blank] or opts[:allow_nil]
|
33
|
+
__allow = if opts[:allow_blank] then :blank? else :nil? end
|
34
|
+
super opts, attributes do |o, a, v|
|
35
|
+
o.errors.add a, __message unless v.send! __allow or __format.match v
|
36
|
+
end
|
37
|
+
else
|
38
|
+
super opts, attributes do |o, a, v|
|
39
|
+
o.errors.add a, __message unless __format.match v
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|