not_naughty 0.4.2 → 0.5

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 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