not_naughty 0.3

Sign up to get free protection for your applications and to get access to all the features.
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