validatable 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -64,6 +64,20 @@ Validations can be given levels. If a validation fails on a level the validation
64
64
  person.errors.on(:name) #=> "name message"
65
65
  person.errors.on(:address) #=> nil
66
66
 
67
+ Validations can also be given groups. Groups can be used to validate an object when it can be valid in various states. For example a mortgage application may be valid for saving (saving a partial application), but that same mortgage application would not be valid for underwriting. In our example a application can be saved as long as a Social Security Number is present; however, an application can not be underwritten unless the name attribute contains a value.
68
+
69
+ class MortgageApplication
70
+ include Validatable
71
+ validates_presence_of :ssn, :groups => [:saving, :underwriting]
72
+ validates_presence_of :name, :groups => :underwriting
73
+ attr_accessor :name, :ssn
74
+ end
75
+
76
+ application = MortgageApplication.new
77
+ application.ssn = 377990118
78
+ application.valid_for_saving? #=> true
79
+ application.valid_for_underwriting? #=> false
80
+
67
81
  Similar to Rails, Validatable also supports conditional validation.
68
82
 
69
83
  class Person
@@ -101,8 +115,10 @@ See the tests for more examples
101
115
  == Contributors
102
116
  Rick Bradley (Revision 25)
103
117
 
104
- Zak Tamsen (Revision 29)
118
+ Anonymous Z (Revision 29)
105
119
 
106
120
  Jason Miller (Revision 31)
107
121
 
108
- Ali Aghareza (Revision 45)
122
+ Ali Aghareza (Revision 43)
123
+
124
+ Xavier Shay (Revision 48 & 49)
@@ -0,0 +1,25 @@
1
+ module Validatable
2
+ module Understandable
3
+ def understands(*args)
4
+ understandings.concat args
5
+ end
6
+
7
+ def understandings
8
+ @understandings ||= []
9
+ end
10
+
11
+ def all_understandings
12
+ return understandings + self.superclass.understandings if self.superclass.respond_to? :understandings
13
+ understandings
14
+ end
15
+
16
+ def must_understand(hash)
17
+ invalid_options = hash.inject([]) do |errors, (key, value)|
18
+ errors << key.to_s unless all_understandings.include?(key)
19
+ errors
20
+ end
21
+ raise ArgumentError.new("invalid options: #{invalid_options.join(', ')}") if invalid_options.any?
22
+ true
23
+ end
24
+ end
25
+ end
data/lib/validatable.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require 'forwardable'
2
2
  require File.expand_path(File.dirname(__FILE__) + '/errors')
3
- require File.expand_path(File.dirname(__FILE__) + '/base')
3
+ require File.expand_path(File.dirname(__FILE__) + '/validatable_class_methods')
4
+ require File.expand_path(File.dirname(__FILE__) + '/validatable_instance_methods')
4
5
  require File.expand_path(File.dirname(__FILE__) + '/child_validation')
6
+ require File.expand_path(File.dirname(__FILE__) + '/understandable')
5
7
  require File.expand_path(File.dirname(__FILE__) + '/validations/validation_base')
6
8
  require File.expand_path(File.dirname(__FILE__) + '/validations/validates_format_of')
7
9
  require File.expand_path(File.dirname(__FILE__) + '/validations/validates_presence_of')
@@ -48,6 +48,7 @@ module Validatable
48
48
  add_validations(args, ValidatesLengthOf) do |validation, options|
49
49
  validation.minimum = options[:minimum]
50
50
  validation.maximum = options[:maximum]
51
+ validation.is = options[:is]
51
52
  end
52
53
  end
53
54
 
@@ -213,7 +214,7 @@ module Validatable
213
214
  instance.errors.add(validation.attribute, validation.message) unless validation.valid?(instance)
214
215
  end
215
216
  end
216
- return unless instance.errors.empty?
217
+ return if instance.errors.any?
217
218
  end
218
219
  end
219
220
 
@@ -236,72 +237,26 @@ module Validatable
236
237
  def add_validations(args, klass) #:nodoc:
237
238
  options = args.last.is_a?(Hash) ? args.pop : {}
238
239
  args.each do |attribute|
239
- new_validation = klass.new(attribute, options)
240
+ klass.must_understand options
241
+ new_validation = klass.new attribute, options
240
242
  yield new_validation, options if block_given?
241
243
  self.validations << new_validation
244
+ self.create_valid_method_for_groups new_validation.groups
242
245
  end
243
246
  end
244
247
 
245
- def children_to_validate #:nodoc:
246
- @children_to_validate ||= []
247
- end
248
- end
249
-
250
- def self.included(klass) #:nodoc:
251
- klass.extend Validatable::ClassMethods
252
- end
253
-
254
- # call-seq: valid?
255
- #
256
- # Returns true if no errors were added otherwise false.
257
- def valid?(*groups)
258
- errors.clear
259
- self.class.validate_children(self, groups)
260
- self.validate(groups)
261
- errors.empty?
262
- end
263
-
264
- # call-seq: errors
265
- #
266
- # Returns the Errors object that holds all information about attribute error messages.
267
- def errors
268
- @errors ||= Validatable::Errors.new
269
- end
270
-
271
- protected
272
- def validate(groups) #:nodoc:
273
- validations_for_groups(groups).each do |validation|
274
- validation_levels.sort.each do |level|
275
- validations_for_level(level).each do |validation|
276
- if validation.should_validate?(self)
277
- run_validation(validation)
248
+ def create_valid_method_for_groups(groups)
249
+ groups.each do |group|
250
+ self.class_eval do
251
+ define_method :"valid_for_#{group}?" do
252
+ valid_for_group?(group)
278
253
  end
279
254
  end
280
- return unless self.errors.empty?
281
255
  end
282
256
  end
283
- end
284
-
285
- def run_validation(validation) #:nodoc:
286
- validation_result = validation.valid?(self)
287
- self.errors.add(validation.attribute, validation.message) unless validation_result
288
- validation.run_after_validate(validation_result, self, validation.attribute)
289
- end
290
-
291
- def validation_levels #:nodoc:
292
- self.validations.collect { |validation| validation.level }.uniq
293
- end
294
-
295
- def validations_for_level(level) #:nodoc:
296
- self.validations.select { |validation| validation.level == level }
297
- end
298
-
299
- def validations_for_groups(groups) #:nodoc:
300
- return self.validations if groups.empty?
301
- self.validations.select { |validation| (groups & validation.groups).any? }
302
- end
303
-
304
- def validations #:nodoc:
305
- @validations ||= self.class.validations.collect { |validation| validation.dup }
257
+
258
+ def children_to_validate #:nodoc:
259
+ @children_to_validate ||= []
260
+ end
306
261
  end
307
262
  end
@@ -0,0 +1,56 @@
1
+ module Validatable
2
+ def self.included(klass) #:nodoc:
3
+ klass.extend Validatable::ClassMethods
4
+ end
5
+
6
+ # call-seq: valid?
7
+ #
8
+ # Returns true if no errors were added otherwise false.
9
+ def valid?
10
+ valid_for_group?
11
+ end
12
+
13
+ # call-seq: errors
14
+ #
15
+ # Returns the Errors object that holds all information about attribute error messages.
16
+ def errors
17
+ @errors ||= Validatable::Errors.new
18
+ end
19
+
20
+ protected
21
+ def valid_for_group?(*groups)
22
+ errors.clear
23
+ self.class.validate_children(self, groups)
24
+ self.validate(groups)
25
+ errors.empty?
26
+ end
27
+
28
+ def validate(groups) #:nodoc:
29
+ validation_levels.each do |level|
30
+ validations_for_level_and_groups(level, groups).each do |validation|
31
+ run_validation(validation) if validation.should_validate?(self)
32
+ end
33
+ return unless self.errors.empty?
34
+ end
35
+ end
36
+
37
+ def run_validation(validation) #:nodoc:
38
+ validation_result = validation.valid?(self)
39
+ self.errors.add(validation.attribute, validation.message) unless validation_result
40
+ validation.run_after_validate(validation_result, self, validation.attribute)
41
+ end
42
+
43
+ def validations_for_level_and_groups(level, groups)
44
+ validations_for_level = self.validations.select { |validation| validation.level == level }
45
+ return validations_for_level if groups.empty?
46
+ validations_for_level.select { |validation| (groups & validation.groups).any? }
47
+ end
48
+
49
+ def validation_levels #:nodoc:
50
+ self.validations.collect { |validation| validation.level }.uniq.sort
51
+ end
52
+
53
+ def validations #:nodoc:
54
+ @validations ||= self.class.validations.collect { |validation| validation.dup }
55
+ end
56
+ end
@@ -1,6 +1,7 @@
1
1
  module Validatable
2
2
  class ValidatesConfirmationOf < ValidationBase #:nodoc:
3
3
  attr_accessor :case_sensitive
4
+ understands :case_sensitive
4
5
 
5
6
  def valid?(instance)
6
7
  return instance.send(self.attribute) == instance.send("#{self.attribute}_confirmation".to_sym) if case_sensitive
@@ -1,9 +1,10 @@
1
1
  module Validatable
2
2
  class ValidatesFormatOf < ValidationBase #:nodoc:
3
3
  attr_accessor :with
4
+ understands :with
4
5
 
5
6
  def valid?(instance)
6
- instance.send(self.attribute) =~ self.with && true
7
+ not (instance.send(self.attribute).to_s =~ self.with).nil?
7
8
  end
8
9
 
9
10
  def message
@@ -1,6 +1,7 @@
1
1
  module Validatable
2
2
  class ValidatesLengthOf < ValidationBase #:nodoc:
3
- attr_accessor :minimum, :maximum
3
+ attr_accessor :minimum, :maximum, :is
4
+ understands :minimum, :maximum, :is
4
5
 
5
6
  def message
6
7
  super || "is invalid"
@@ -11,6 +12,7 @@ module Validatable
11
12
  value = instance.send(self.attribute) || ""
12
13
  valid &&= value.length <= maximum unless maximum.nil?
13
14
  valid &&= value.length >= minimum unless minimum.nil?
15
+ valid &&= value.length == is unless is.nil?
14
16
  valid
15
17
  end
16
18
  end
@@ -1,6 +1,7 @@
1
1
  module Validatable
2
2
  class ValidatesTrueFor < ValidationBase #:nodoc:
3
3
  attr_accessor :logic
4
+ understands :logic
4
5
 
5
6
  def valid?(instance)
6
7
  instance.instance_eval(&logic) == true
@@ -1,5 +1,8 @@
1
1
  module Validatable
2
2
  class ValidationBase #:nodoc:
3
+ extend Understandable
4
+ understands :message, :if, :times, :level, :groups
5
+
3
6
  attr_accessor :attribute, :message, :times
4
7
  attr_reader :level, :groups
5
8
 
@@ -9,7 +12,11 @@ module Validatable
9
12
  @conditional = options[:if] || Proc.new { true }
10
13
  @times = options[:times]
11
14
  @level = options[:level] || 1
12
- @groups = options[:groups].is_a?(Array) ? options[:groups] : [options[:groups]]
15
+ @groups = case options[:groups]
16
+ when nil then []
17
+ when Array then options[:groups]
18
+ else [options[:groups]]
19
+ end
13
20
  end
14
21
 
15
22
  def should_validate?(instance)
data/rakefile.rb CHANGED
@@ -29,7 +29,7 @@ Gem::manage_gems
29
29
  specification = Gem::Specification.new do |s|
30
30
  s.name = "validatable"
31
31
  s.summary = "Validatable is a library for adding validations."
32
- s.version = "1.2.1"
32
+ s.version = "1.2.2"
33
33
  s.author = 'Jay Fields'
34
34
  s.description = "Validatable is a library for adding validations."
35
35
  s.email = 'validatable-developer@rubyforge.org'
@@ -2,6 +2,19 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
2
 
3
3
  module Functional
4
4
  class ValidatableTest < Test::Unit::TestCase
5
+ test "validations are only executed once" do
6
+ if_condition = mock
7
+ if_condition.expects(:where?).times 2
8
+ klass = Class.new do
9
+ include Validatable
10
+ attr_accessor :name, :address
11
+ validates_presence_of :name, :if => lambda { if_condition.where? }
12
+ validates_presence_of :address, :if => lambda { if_condition.where? }
13
+ end
14
+ instance = klass.new
15
+ instance.valid?
16
+ end
17
+
5
18
  test "given a child class with validations, when parent class is validated, then the error is in the parent objects error collection" do
6
19
  child_class = Class.new do
7
20
  include Validatable
@@ -75,14 +88,30 @@ module Functional
75
88
  assert_equal true, instance.valid?
76
89
  end
77
90
 
91
+ test "classes only have valid_for_* methods for groups that appear in their validations" do
92
+ class_with_group_one = Class.new do
93
+ include Validatable
94
+ validates_presence_of :name, :groups => :group_one
95
+ attr_accessor :name
96
+ end
97
+ class_with_group_two = Class.new do
98
+ include Validatable
99
+ validates_presence_of :name, :groups => :group_two
100
+ attr_accessor :name
101
+ end
102
+ assert_equal false, class_with_group_one.public_instance_methods.include?(:valid_for_group_two?)
103
+ assert_equal false, class_with_group_two.public_instance_methods.include?(:valid_for_group_one?)
104
+ end
105
+
78
106
  test "nonmatching groups are not used as validations" do
79
107
  klass = Class.new do
80
108
  include Validatable
81
109
  validates_presence_of :name, :groups => :group_one
82
- attr_accessor :name
110
+ validates_presence_of :address, :groups => :group_two
111
+ attr_accessor :name, :address
83
112
  end
84
113
  instance = klass.new
85
- assert_equal true, instance.valid?(:group_two)
114
+ assert_equal nil, instance.errors.on(:name)
86
115
  end
87
116
 
88
117
  test "after validate is called following a validation" do
@@ -116,7 +145,7 @@ module Functional
116
145
  attr_accessor :name
117
146
  end
118
147
  instance = klass.new
119
- assert_equal false, instance.valid?(:group_one)
148
+ assert_equal false, instance.valid_for_group_one?
120
149
  end
121
150
 
122
151
  test "matching groups are used as validations when validations are part of multiple groups" do
@@ -126,7 +155,7 @@ module Functional
126
155
  attr_accessor :name
127
156
  end
128
157
  instance = klass.new
129
- assert_equal false, instance.valid?(:group_one)
158
+ assert_equal false, instance.valid_for_group_one?
130
159
  end
131
160
 
132
161
  test "no group given then all validations are used" do
@@ -139,18 +168,6 @@ module Functional
139
168
  assert_equal false, instance.valid?
140
169
  end
141
170
 
142
- test "matching multiple groups for validations" do
143
- klass = Class.new do
144
- include Validatable
145
- validates_presence_of :name, :groups => :group_one
146
- validates_presence_of :address, :groups => :group_two
147
- attr_accessor :name, :address
148
- end
149
- instance = klass.new
150
- instance.valid?(:group_one, :group_two)
151
- assert_equal 2, instance.errors.size
152
- end
153
-
154
171
  expect true do
155
172
  klass = Class.new do
156
173
  include Validatable
@@ -12,5 +12,27 @@ module Functional
12
12
  instance.valid?
13
13
  assert_equal "is invalid", instance.errors.on(:name)
14
14
  end
15
+
16
+ test "given exact value, when validated, then error is in the objects error collection" do
17
+ klass = Class.new do
18
+ include Validatable
19
+ attr_accessor :name
20
+ validates_length_of :name, :is => 2
21
+ end
22
+ instance = klass.new
23
+ instance.valid?
24
+ assert_equal "is invalid", instance.errors.on(:name)
25
+ end
26
+
27
+ test "given exact value is met, when validated, then valid is true" do
28
+ klass = Class.new do
29
+ include Validatable
30
+ attr_accessor :name
31
+ validates_length_of :name, :is => 2
32
+ end
33
+ instance = klass.new
34
+ instance.name = "bk"
35
+ assert_equal true, instance.valid?
36
+ end
15
37
  end
16
38
  end
@@ -21,7 +21,7 @@ module Unit
21
21
  instance.expects(:errors).returns(errors=mock).times 3
22
22
  errors.expects(:add).with('attribute', 'message')
23
23
  errors.expects(:add).with('attribute2', 'message2')
24
- errors.expects(:empty?).returns true
24
+ errors.expects(:any?).returns false
25
25
  klass.validate(instance)
26
26
  end
27
27
 
@@ -12,4 +12,9 @@ class ValidatesAcceptanceOfTest < Test::Unit::TestCase
12
12
  instance = stub(:acceptance=>'false')
13
13
  assert_equal false, validation.valid?(instance)
14
14
  end
15
+
16
+ expect true do
17
+ Validatable::ValidatesAcceptanceOf.must_understand(:message => nil, :if => nil, :times => nil, :level => nil, :groups => nil)
18
+ end
19
+
15
20
  end
@@ -62,4 +62,10 @@ class ValidatesConfirmationOfTest < Test::Unit::TestCase
62
62
  validation.case_sensitive = false
63
63
  assert_equal true, validation.valid?(stub(:username => nil, :username_confirmation => nil))
64
64
  end
65
+
66
+ expect true do
67
+ options = { :message => nil, :if => nil, :times => nil, :level => nil, :groups => nil, :case_sensitive => nil }
68
+ Validatable::ValidatesConfirmationOf.must_understand(options)
69
+ end
70
+
65
71
  end
@@ -12,4 +12,15 @@ class ValidatesFormatOfTest < Test::Unit::TestCase
12
12
  validation.with = /book/
13
13
  assert_equal true, validation.valid?(stub(:name=>"book"))
14
14
  end
15
+
16
+ test "when attribute value is an integer it should be converted to a string before matching" do
17
+ validation = Validatable::ValidatesFormatOf.new :age
18
+ validation.with = /14/
19
+ assert_equal true, validation.valid?(stub(:age=>14))
20
+ end
21
+
22
+ expect true do
23
+ Validatable::ValidatesFormatOf.must_understand(:message => nil, :if => nil, :times => nil, :level => nil, :groups => nil, :with => nil)
24
+ end
25
+
15
26
  end
@@ -17,6 +17,20 @@ module Unit
17
17
  assert_equal false, validation.valid?(instance)
18
18
  end
19
19
 
20
+ test "is length is false" do
21
+ validation = Validatable::ValidatesLengthOf.new :username
22
+ validation.is = 2
23
+ instance = stub(:username=>"u")
24
+ assert_equal false, validation.valid?(instance)
25
+ end
26
+
27
+ test "is length is true" do
28
+ validation = Validatable::ValidatesLengthOf.new :username
29
+ validation.is = 2
30
+ instance = stub(:username=>"uu")
31
+ assert_equal true, validation.valid?(instance)
32
+ end
33
+
20
34
  test "valid length" do
21
35
  validation = Validatable::ValidatesLengthOf.new :username
22
36
  validation.minimum = 2
@@ -24,5 +38,11 @@ module Unit
24
38
  instance = stub(:username=>"udfgdf")
25
39
  assert_equal true, validation.valid?(instance)
26
40
  end
41
+
42
+ expect true do
43
+ options = {:message => nil, :if => nil, :times => nil, :level => nil, :groups => nil, :maximum => nil, :minimum => nil, :is => nil}
44
+ Validatable::ValidatesLengthOf.must_understand(options)
45
+ end
46
+
27
47
  end
28
48
  end
@@ -32,5 +32,10 @@ module Unit
32
32
  instance = stub(:multiple_dots => "50.0.0")
33
33
  assert_equal false, validation.valid?(instance)
34
34
  end
35
+
36
+ expect true do
37
+ Validatable::ValidatesNumericalityOf.must_understand(:message => nil, :if => nil, :times => nil, :level => nil, :groups => nil)
38
+ end
39
+
35
40
  end
36
41
  end
@@ -16,4 +16,8 @@ class ValidatesPresenceOfTest < Test::Unit::TestCase
16
16
  assert_equal true, validation.valid?(stub(:employee => stub(:nil? => false)))
17
17
  end
18
18
 
19
+ expect true do
20
+ Validatable::ValidatesPresenceOf.must_understand(:message => nil, :if => nil, :times => nil, :level => nil, :groups => nil)
21
+ end
22
+
19
23
  end
@@ -13,4 +13,8 @@ class ValidatesTrueForTest < Test::Unit::TestCase
13
13
  assert_equal true, validation.valid?(stub_everything)
14
14
  end
15
15
 
16
+ expect true do
17
+ Validatable::ValidatesTrueFor.must_understand(:message => nil, :if => nil, :times => nil, :level => nil, :groups => nil, :logic => nil)
18
+ end
19
+
16
20
  end
@@ -32,4 +32,15 @@ class ValidationBaseTest < Test::Unit::TestCase
32
32
  validation = Validatable::ValidationBase.new :base
33
33
  validation.level
34
34
  end
35
+
36
+ test "invalid option causes raise" do
37
+ assert_raises ArgumentError do
38
+ Validatable::ValidationBase.must_understand(:foo => 1, :bar => 2)
39
+ end
40
+ end
41
+
42
+ expect true do
43
+ Validatable::ValidationBase.must_understand(:message => nil, :if => nil, :times => nil, :level => nil, :groups => nil)
44
+ end
45
+
35
46
  end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.2
2
+ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: validatable
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.2.1
7
- date: 2007-04-26 00:00:00 -04:00
6
+ version: 1.2.2
7
+ date: 2007-04-27 00:00:00 -04:00
8
8
  summary: Validatable is a library for adding validations.
9
9
  require_paths:
10
10
  - lib
@@ -25,14 +25,15 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
25
25
  platform: ruby
26
26
  signing_key:
27
27
  cert_chain:
28
- post_install_message:
29
28
  authors:
30
29
  - Jay Fields
31
30
  files:
32
- - lib/base.rb
33
31
  - lib/child_validation.rb
34
32
  - lib/errors.rb
33
+ - lib/understandable.rb
35
34
  - lib/validatable.rb
35
+ - lib/validatable_class_methods.rb
36
+ - lib/validatable_instance_methods.rb
36
37
  - lib/validations/validates_acceptance_of.rb
37
38
  - lib/validations/validates_confirmation_of.rb
38
39
  - lib/validations/validates_format_of.rb