not_naughty 0.4.2 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,14 @@
1
+ === 0.5 (2008-03-17)
2
+ * added an error handler, can now handle SQL validation errors
3
+ * renamed Errors to Violation and merged it with ValidationException
4
+ * changed specs for 0.4.1
5
+
6
+ === 0.4.2 (2008-03-12)
7
+ * fixed bug that causes infinite recursion in some apps that reload classes
8
+
9
+ === 0.4.1 (2008-03-12)
10
+ * some minor changes in validator method
11
+
1
12
  === 0.4 (2008-02-18)
2
13
  * completed documentation
3
14
  * added spec for :each in Builder::ValidationDelegator
data/README CHANGED
@@ -35,6 +35,14 @@ NotNaughty extends your ruby Project with a highly custumizable validation API.
35
35
  validates_bunchyness_of :bunchy_item
36
36
  end
37
37
 
38
+ <b>Handle SQL error gracefully:</b>
39
+
40
+ class Person
41
+ validator.error_handler.handle(SQLError) do |err|
42
+ # do funny things...
43
+ end
44
+ end
45
+
38
46
  <b>Syntactical Sugar with Builder methods:</b>
39
47
 
40
48
  validates(:username, :password) {length :minimum => 6}
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ include FileUtils
9
9
  # Configuration
10
10
  ##############################################################################
11
11
  NAME = "not_naughty"
12
- VERS = "0.4.2"
12
+ VERS = "0.5"
13
13
  CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
14
14
  RDOC_OPTS = [
15
15
  "--quiet",
@@ -4,6 +4,7 @@ require 'observer'
4
4
 
5
5
  require 'rubygems'
6
6
  require 'assistance'
7
+ require 'tree'
7
8
 
8
9
  module Kernel#:nodoc:all
9
10
  methods.include? 'send!' or
@@ -19,7 +20,8 @@ module NotNaughty
19
20
  require 'not_naughty/validation'
20
21
  Validation.add_observer Builder
21
22
 
22
- require 'not_naughty/errors'
23
+ require 'not_naughty/violation'
24
+ require 'not_naughty/error_handler'
23
25
  require 'not_naughty/instance_methods'
24
26
 
25
27
  # Extended classes get NotNaughty::Builder and NotNaughty::InstanceMethods.
@@ -42,6 +44,8 @@ module NotNaughty
42
44
  # validator :create, :update # ~ - but with :create and :update states
43
45
  # validator AnotherValidator # Instance of AnotherValidator
44
46
  #
47
+ # validator AnotherValidator, :state_a, :state_b
48
+ #
45
49
  # The above examples work as long validator is not already called. To reset
46
50
  # an already assigned validator set <tt>@validator</tt> to nil.
47
51
  def validator(*states)
@@ -54,6 +58,7 @@ module NotNaughty
54
58
  end
55
59
 
56
60
  validator_klass.new(*states)
61
+
57
62
  elsif superclass.respond_to? :validator
58
63
  superclass.validator.clone
59
64
 
@@ -69,19 +74,15 @@ module NotNaughty
69
74
  #
70
75
  # <b>Example:</b>
71
76
  # validated_before :save # raise ValidationException unless valid?
72
- # validated_before :save, :without => :exception # => false unless valid?
73
- def validated_before(method, *args)
77
+ def validated_before(method)
74
78
  __method = :"#{method}_without_validations"
75
79
  alias_method __method, method
76
80
 
77
- without = args.extract_options![:without]
78
- if [*without].include? :exception
79
- define_method method do |*params|
80
- if valid? then send! __method else false end
81
- end
82
- else
83
- define_method method do |*params|
84
- if valid? then send! __method else raise errors.to_exception end
81
+ define_method method do |*params|
82
+ begin
83
+ if valid? then send! __method else raise errors end
84
+ rescue Exception => error
85
+ self.class.validator.error_handler.raise error
85
86
  end
86
87
  end
87
88
  end
@@ -46,7 +46,7 @@ module NotNaughty
46
46
  super receiver
47
47
  end
48
48
  def method_missing(method_sym, *params) #:nodoc:
49
- method_sym, params = if @_sd_obj_params.any?
49
+ method_sym, params = unless @_sd_obj_params.empty?
50
50
  opts = @_sd_obj_opts.update params.extract_options!
51
51
  [:"validates_#{method_sym}_of", @_sd_obj_params + [opts]]
52
52
  else
@@ -0,0 +1,48 @@
1
+ class Tree::TreeNode
2
+
3
+ def closest(obj)
4
+ if @name == obj then self
5
+ elsif @name > obj
6
+ closest = self
7
+ @children.any? { |child| node = child.closest(obj) and closest = node }
8
+
9
+ closest
10
+ end
11
+ end
12
+
13
+ end
14
+
15
+ module NotNaughty
16
+
17
+ class ErrorHandler
18
+
19
+ def initialize(handler = Kernel)
20
+ @handles = Tree::TreeNode.new Exception, proc { |e| handler.raise e }
21
+ end
22
+
23
+ # Calls closest handle with exception.
24
+ def raise(exception)
25
+ handle = @handles.closest exception.class
26
+ handle.content.call exception
27
+ end
28
+
29
+ # Inserts handle into the ordered tree.
30
+ def handle(exception_class, &block)
31
+ closest_handle = @handles.closest exception_class
32
+
33
+ if closest_handle == exception_class then closest_handle.content = block
34
+ else
35
+ new_handle = Tree::TreeNode.new exception_class, block
36
+
37
+ closest_handle.children do |child|
38
+ exception_class > child.name and
39
+ new_handle << closest_handle.remove!(child)
40
+ end
41
+
42
+ closest_handle << new_handle
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -2,7 +2,7 @@ module NotNaughty
2
2
  module InstanceMethods
3
3
 
4
4
  # Returns instance of Errors.
5
- def errors() @errors ||= ::NotNaughty::Errors.new end
5
+ def errors() @errors ||= ::NotNaughty::Violation.new end
6
6
 
7
7
  # Returns true if all validations passed
8
8
  def valid?
@@ -7,7 +7,7 @@ module NotNaughty
7
7
 
8
8
  BASEDIR = File.dirname __FILE__
9
9
  # Loader pattern
10
- PATTERN = File.join BASEDIR, %w[validations %s_validation.rb]
10
+ PATTERN = File.join BASEDIR, %w[validations ** %s_validation.rb]
11
11
 
12
12
  # Loads validations.
13
13
  def self.load(*validations)
@@ -85,7 +85,7 @@ module NotNaughty
85
85
 
86
86
  (class << self; self; end).module_eval do
87
87
  alias_method :call, :call_with_conditions
88
- end if @conditions.any?
88
+ end unless @conditions.empty?
89
89
  end
90
90
 
91
91
  # Conditions for use in Validations are usually used with Validations.
@@ -0,0 +1,24 @@
1
+ module NotNaughty
2
+
3
+ class UniquenessValidation < Validation
4
+
5
+ def initialize(opts, attributes) #:nodoc:
6
+ __message = opts[:message] || '#{"%s".humanize} is not unique.'
7
+
8
+ if opts[:allow_blank] or opts[:allow_nil]
9
+ __allow = if opts[:allow_blank] then :blank? else :nil? end
10
+ super opts, attributes do |obj, attr, val|
11
+ obj.errors.add attr, __message unless
12
+ v.send! __allow or
13
+ obj.model.find(attr => val).limit(1).length.zero?
14
+ end
15
+ else
16
+ super opts, attributes do |obj, attr, val|
17
+ obj.errors.add attr, __message unless
18
+ obj.model.find(attr => val).limit(1).length.zero?
19
+ end
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -5,7 +5,7 @@ module NotNaughty
5
5
  # See new and get_state for more information.
6
6
  class Validator
7
7
 
8
- attr_reader :states
8
+ attr_reader :states, :error_handler
9
9
 
10
10
  # By default it comes with the :default State unless other states are
11
11
  # provided.
@@ -22,16 +22,22 @@ module NotNaughty
22
22
 
23
23
  @states = states.inject({}) {|m, s| m.update s => State.new(s)}
24
24
  @initial_state = @states[states.first]
25
+
26
+ @error_handler = NotNaughty::ErrorHandler.new
25
27
  end
26
28
 
27
29
  def clone #:nodoc:
28
30
  states = [@initial_state.name] | @states.keys
31
+ error_handler = @error_handler.clone
29
32
 
30
33
  clone = self.class.new(*states)
31
34
  @states.each do |n, s|
32
35
  s.validations.each { |a, v| clone.states[n].validations[a] = v.clone }
33
36
  end
34
- clone.instance_eval { @initial_state = @states[states.first] }
37
+ clone.instance_eval do
38
+ @initial_state = @states[states.first]
39
+ @error_handler = error_handler
40
+ end
35
41
 
36
42
  clone
37
43
  end
@@ -3,10 +3,10 @@ module NotNaughty
3
3
  # == Container for failed validations.
4
4
  #
5
5
  # ...
6
- class Errors
6
+ class Violation < RuntimeError
7
7
  extend Forwardable
8
8
 
9
- def_delegators :@errors, :empty?, :clear, :[], :each, :to_yaml
9
+ def_delegators :@errors, :empty?, :clear, :[], :each
10
10
 
11
11
  include Enumerable
12
12
 
@@ -31,31 +31,6 @@ module NotNaughty
31
31
  end
32
32
  end
33
33
 
34
- # Returns a ValidationException with <tt>self</tt> in <tt>:errors</tt>.
35
- def to_exception
36
- ValidationException.new self
37
- end
38
34
  end
39
35
 
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
36
  end
@@ -54,6 +54,8 @@ module Sequel #:nodoc:
54
54
 
55
55
  # Applies plugin to a Sequel::Model.
56
56
  def self.apply(receiver, *args)
57
+ without = [args.extract_options![:without]].flatten
58
+
57
59
  receiver.extend ::NotNaughty
58
60
  receiver.validator self, :create, :update
59
61
 
@@ -63,11 +65,15 @@ module Sequel #:nodoc:
63
65
  )
64
66
 
65
67
  receiver.extend ClassMethods
68
+
66
69
  receiver.instance_eval { alias_method :save, :save! }
67
- receiver.validated_before :save, *args
70
+ receiver.validated_before :save
71
+
72
+ without.include? :exception or
73
+ receiver.validator.error_handler.handle(::NotNaughty::Violation) {false}
68
74
 
69
- without = args.extract_options![:without]
70
- receiver.send! :include, Hooks unless [*without].include? :hooks
75
+ without.include? :hooks or
76
+ receiver.send! :include, Hooks
71
77
  end
72
78
 
73
79
  # Returns state for given instance.
@@ -0,0 +1,21 @@
1
+ require "#{ File.dirname(__FILE__) }/spec_helper.rb"
2
+
3
+ describe subject::ErrorHandler do
4
+ before(:each) { @eh = subject::ErrorHandler.new }
5
+
6
+ it "passed all Exceptions to Kernel.raise" do
7
+ proc { @eh.raise Exception.new }.should raise_error(Exception)
8
+ end
9
+
10
+ it "calls the closest handle" do
11
+ @eh.handle(ArgumentError) {ArgumentError}
12
+ @eh.handle(StandardError) {StandardError}
13
+
14
+ @eh.raise(ArgumentError.new).should == ArgumentError
15
+ @eh.raise(StandardError.new).should == StandardError
16
+ @eh.raise(NoMethodError.new).should == StandardError
17
+
18
+ proc { @eh.raise ScriptError.new }.should raise_error(Exception)
19
+ end
20
+
21
+ end
@@ -14,12 +14,13 @@ describe subject do
14
14
  subject::should be_const_defined(:Validator)
15
15
  subject::should be_const_defined(:Builder)
16
16
  subject::should be_const_defined(:InstanceMethods)
17
- subject::should be_const_defined(:Errors)
17
+ subject::should be_const_defined(:Violation)
18
18
  end
19
19
  it "should define a validator" do
20
20
  validated = Class.new(Object).extend subject
21
21
  validated.should respond_to(:validator)
22
- validated.validator.should be_an_instance_of(subject::Validator)
22
+ validated.validator(:state).should be_an_instance_of(subject::Validator)
23
+ validated.validator.states.should have_key(:state)
23
24
 
24
25
  validator = Class.new(subject::Validator)
25
26
  validated = Class.new(Object).extend subject
@@ -62,12 +63,13 @@ describe subject do
62
63
  validated.validated_before :clone
63
64
  instance = validated.new
64
65
  instance.should_receive(:valid?).once.and_return(false)
65
- lambda { instance.clone }.should raise_error(subject::ValidationException)
66
+ lambda { instance.clone }.should raise_error(subject::Violation)
66
67
  end
67
68
  it "should return false if invalid and method called" do
68
69
  validated = Class.new(Object).extend subject
69
70
 
70
- validated.validated_before :clone, :without => :exception
71
+ validated.validated_before :clone
72
+ validated.validator.error_handler.handle(subject::Violation) {false}
71
73
  instance = validated.new
72
74
  instance.should_receive(:valid?).once.and_return(false)
73
75
  instance.clone.should == false
@@ -14,7 +14,7 @@ describe Sequel::Plugins::NotNaughty do
14
14
  @obj.validator.should_receive(:has_validations?)
15
15
  @obj.validator.should_receive(:invoke).with(@instance)
16
16
 
17
- @instance.errors.should be_an_instance_of(subject::Errors)
17
+ @instance.errors.should be_an_instance_of(subject::Violation)
18
18
  @instance.should respond_to(:validate)
19
19
  @instance.should respond_to(:valid?)
20
20
  @instance.should respond_to(:save_without_validations)
@@ -93,5 +93,6 @@ describe Sequel::Plugins::NotNaughty do
93
93
  i.should_not_receive(:after_validate)
94
94
  i.validate
95
95
  end
96
+ it "should alias the validate_with_hooks just once"
96
97
 
97
98
  end
@@ -10,7 +10,16 @@ describe subject::Validator, 'with default state' do
10
10
  it "should shoult return default state" do
11
11
  @validator.get_state(nil).name.should == :default
12
12
  end
13
-
13
+ it "should have an errors handler" do
14
+ @validator.error_handler.should be_kind_of(NotNaughty::ErrorHandler)
15
+ end
16
+ it "should clone the error handler" do
17
+ rnd = rand(10)
18
+
19
+ @validator.error_handler.should_receive(:clone).and_return(rnd)
20
+ @validator.clone.error_handler.should == rnd
21
+ end
22
+
14
23
  end
15
24
 
16
25
  describe NotNaughty::Validator, 'with custom states' do
@@ -0,0 +1,36 @@
1
+ require "#{ File.dirname(__FILE__) }/spec_helper.rb"
2
+
3
+ describe subject::Violation do
4
+
5
+ before(:each) { @errors = subject::Violation.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 be kind of RuntimeError" do
20
+ @errors.should be_kind_of(RuntimeError)
21
+ end
22
+ it "should have methods delegated" do
23
+ probe = mock 'Probe'
24
+ methods = [:empty?, :clear, :[], :each]
25
+
26
+ methods.each { |m| probe.should_receive m }
27
+ @errors.instance_variable_set :@errors, probe
28
+
29
+ methods.each { |m| @errors.send! m }
30
+ end
31
+ it "should return evaluated errors messages" do
32
+ @errors.add :attribute, 'Message #{"%s".capitalize}'
33
+ @errors.on(:attribute).should == ['Message Attribute']
34
+ end
35
+
36
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: not_naughty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: "0.5"
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Florian A\xC3\x9Fmann"
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-03-12 00:00:00 +01:00
12
+ date: 2008-03-17 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -28,7 +28,7 @@ files:
28
28
  - README
29
29
  - Rakefile
30
30
  - spec/builder_spec.rb
31
- - spec/errors_spec.rb
31
+ - spec/error_handler_spec.rb
32
32
  - spec/not_naughty_spec.rb
33
33
  - spec/rcov.opts
34
34
  - spec/sequel_spec_helper.rb
@@ -38,9 +38,10 @@ files:
38
38
  - spec/validation_spec.rb
39
39
  - spec/validations_spec.rb
40
40
  - spec/validator_spec.rb
41
+ - spec/violation_spec.rb
41
42
  - lib/not_naughty
42
43
  - lib/not_naughty/builder.rb
43
- - lib/not_naughty/errors.rb
44
+ - lib/not_naughty/error_handler.rb
44
45
  - lib/not_naughty/instance_methods.rb
45
46
  - lib/not_naughty/validation.rb
46
47
  - lib/not_naughty/validations
@@ -50,7 +51,10 @@ files:
50
51
  - lib/not_naughty/validations/length_validation.rb
51
52
  - lib/not_naughty/validations/numericality_validation.rb
52
53
  - lib/not_naughty/validations/presence_validation.rb
54
+ - lib/not_naughty/validations/sequel
55
+ - lib/not_naughty/validations/sequel/uniqueness_validation.rb
53
56
  - lib/not_naughty/validator.rb
57
+ - lib/not_naughty/violation.rb
54
58
  - lib/not_naughty.rb
55
59
  - lib/sequel_not_naughty.rb
56
60
  - CHANGELOG
@@ -1,61 +0,0 @@
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