durran-validatable 1.7.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/README.rdoc +118 -0
  2. data/Rakefile +55 -0
  3. data/VERSION.yml +4 -0
  4. data/lib/child_validation.rb +15 -0
  5. data/lib/errors.rb +90 -0
  6. data/lib/included_validation.rb +9 -0
  7. data/lib/macros.rb +306 -0
  8. data/lib/object_extension.rb +21 -0
  9. data/lib/requireable.rb +26 -0
  10. data/lib/understandable.rb +31 -0
  11. data/lib/validatable.rb +25 -0
  12. data/lib/validatable_class_methods.rb +85 -0
  13. data/lib/validatable_instance_methods.rb +116 -0
  14. data/lib/validations/validates_acceptance_of.rb +14 -0
  15. data/lib/validations/validates_associated.rb +13 -0
  16. data/lib/validations/validates_confirmation_of.rb +23 -0
  17. data/lib/validations/validates_each.rb +14 -0
  18. data/lib/validations/validates_format_of.rb +16 -0
  19. data/lib/validations/validates_length_of.rb +30 -0
  20. data/lib/validations/validates_numericality_of.rb +27 -0
  21. data/lib/validations/validates_presence_of.rb +17 -0
  22. data/lib/validations/validates_true_for.rb +13 -0
  23. data/lib/validations/validation_base.rb +91 -0
  24. data/test/all_tests.rb +1 -0
  25. data/test/functional/test_validatable.rb +589 -0
  26. data/test/functional/test_validates_acceptance_of.rb +16 -0
  27. data/test/functional/test_validates_associated.rb +41 -0
  28. data/test/functional/test_validates_confirmation_of.rb +56 -0
  29. data/test/functional/test_validates_each.rb +14 -0
  30. data/test/functional/test_validates_format_of.rb +34 -0
  31. data/test/functional/test_validates_length_of.rb +64 -0
  32. data/test/functional/test_validates_numericality_of.rb +57 -0
  33. data/test/functional/test_validates_presence_of.rb +16 -0
  34. data/test/functional/test_validates_true_for.rb +27 -0
  35. data/test/test_helper.rb +33 -0
  36. data/test/unit/test_errors.rb +70 -0
  37. data/test/unit/test_understandable.rb +19 -0
  38. data/test/unit/test_validatable.rb +38 -0
  39. data/test/unit/test_validates_acceptance_of.rb +45 -0
  40. data/test/unit/test_validates_associated.rb +29 -0
  41. data/test/unit/test_validates_confirmation_of.rb +76 -0
  42. data/test/unit/test_validates_format_of.rb +44 -0
  43. data/test/unit/test_validates_length_of.rb +80 -0
  44. data/test/unit/test_validates_numericality_of.rb +76 -0
  45. data/test/unit/test_validates_presence_of.rb +35 -0
  46. data/test/unit/test_validates_true_for.rb +29 -0
  47. data/test/unit/test_validation_base.rb +52 -0
  48. metadata +126 -0
@@ -0,0 +1,17 @@
1
+ module Validatable
2
+ class ValidatesPresenceOf < ValidationBase #:nodoc:
3
+ def valid?(instance)
4
+ value = instance.send(self.attribute)
5
+ return true if allow_nil && value.nil?
6
+ return true if allow_blank && value.blank?
7
+
8
+ return false if instance.send(self.attribute).nil?
9
+ value.respond_to?(:strip) ? instance.send(self.attribute).strip.length != 0 : true
10
+ end
11
+
12
+ def message(instance)
13
+ super || "can't be empty"
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,13 @@
1
+ module Validatable
2
+ class ValidatesTrueFor < ValidationBase #:nodoc:
3
+ required_option :logic
4
+
5
+ def valid?(instance)
6
+ instance.instance_eval(&logic) == true
7
+ end
8
+
9
+ def message(instance)
10
+ super || "is invalid"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,91 @@
1
+ module Validatable
2
+ class ValidationBase #:nodoc:
3
+ def self.required_option(*args)
4
+ option(*args)
5
+ requires(*args)
6
+ end
7
+
8
+ def self.option(*args)
9
+ attr_accessor(*args)
10
+ understands(*args)
11
+ end
12
+
13
+ def self.default(hash)
14
+ defaults.merge! hash
15
+ end
16
+
17
+ def self.defaults
18
+ @defaults ||= {}
19
+ end
20
+
21
+ def self.all_defaults
22
+ return defaults.merge(self.superclass.all_defaults) if self.superclass.respond_to? :all_defaults
23
+ defaults
24
+ end
25
+
26
+ def self.after_validate(&block)
27
+ after_validations << block
28
+ end
29
+
30
+ def self.after_validations
31
+ @after_validations ||= []
32
+ end
33
+
34
+ def self.all_after_validations
35
+ return after_validations + self.superclass.all_after_validations if self.superclass.respond_to? :all_after_validations
36
+ after_validations
37
+ end
38
+
39
+ include Understandable
40
+ include Requireable
41
+
42
+ option :message, :if, :times, :level, :groups, :key, :after_validate, :allow_nil, :allow_blank
43
+ default :level => 1, :groups => []
44
+ attr_accessor :attribute
45
+
46
+ def initialize(klass, attribute, options={})
47
+ must_understand options
48
+ requires options
49
+ self.class.all_understandings.each do |understanding|
50
+ options[understanding] = self.class.all_defaults[understanding] unless options.has_key? understanding
51
+ self.instance_variable_set("@#{understanding}", options[understanding])
52
+ end
53
+ self.attribute = attribute
54
+ self.groups = [self.groups] unless self.groups.is_a?(Array)
55
+ self.key = "#{klass.name}/#{self.class.name}/#{self.key || self.attribute}"
56
+ raise_error_if_key_is_dup(klass)
57
+ end
58
+
59
+ def raise_error_if_key_is_dup(klass)
60
+ message = "key #{self.key} must be unique, provide the :key option to specify a unique key"
61
+ raise ArgumentError.new(message) if klass.validation_keys_include? self.key
62
+ end
63
+
64
+ def should_validate?(instance)
65
+ result = validate_this_time?(instance)
66
+ case self.if
67
+ when Proc
68
+ result &&= instance.instance_eval(&self.if)
69
+ when Symbol, String
70
+ result &&= instance.instance_eval(self.if.to_s)
71
+ end
72
+ result
73
+ end
74
+
75
+ def message(instance)
76
+ @message.respond_to?(:call) ? instance.instance_eval(&@message) : @message
77
+ end
78
+
79
+ def validate_this_time?(instance)
80
+ return true if @times.nil?
81
+ self.times > instance.times_validated(self.key)
82
+ end
83
+
84
+ def run_after_validate(result, instance, attribute)
85
+ self.class.all_after_validations.each do |block|
86
+ block.call result, instance, attribute
87
+ end
88
+ instance.instance_eval_with_params result, attribute, &self.after_validate unless self.after_validate.nil?
89
+ end
90
+ end
91
+ end
data/test/all_tests.rb ADDED
@@ -0,0 +1 @@
1
+ Dir['test/**/test_*.rb'].each { |test_case| require test_case }
@@ -0,0 +1,589 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ functional_tests do
4
+
5
+ expect "can't be empty" do
6
+ child_class = Module.new do
7
+ include Validatable
8
+ validates_presence_of :name
9
+ end
10
+ klass = Class.new do
11
+ include Validatable
12
+ include_validations_from :child
13
+ define_method :child do
14
+ child_class
15
+ end
16
+ attr_accessor :name
17
+ end
18
+ instance = klass.new
19
+ instance.valid?
20
+ instance.errors.on(:name)
21
+ end
22
+
23
+ expect "can't be empty" do
24
+ child_class = Module.new do
25
+ include Validatable
26
+ validates_presence_of :name
27
+ end
28
+ klass = Class.new do
29
+ include Validatable
30
+ validates_presence_of :address
31
+ include_validations_from :child
32
+ define_method :child do
33
+ child_class
34
+ end
35
+ attr_accessor :name, :address
36
+ end
37
+ instance = klass.new
38
+ instance.valid?
39
+ instance.errors.on(:address)
40
+ end
41
+
42
+ expect :is_set do
43
+ klass = Class.new do
44
+ include Validatable
45
+ attr_accessor :result
46
+ before_validation do
47
+ self.result = :is_set
48
+ end
49
+ end
50
+
51
+ instance = klass.new
52
+ instance.valid?
53
+ instance.result
54
+ end
55
+
56
+ expect :is_set do
57
+ klass = Class.new do
58
+ include Validatable
59
+ attr_accessor :name, :result
60
+ validates_presence_of :name, :after_validate => lambda { |result, attribute| self.result = :is_set }
61
+ end
62
+
63
+ instance = klass.new
64
+ instance.valid?
65
+ instance.result
66
+ end
67
+
68
+ expect false do
69
+ klass = Class.new do
70
+ include Validatable
71
+ attr_accessor :name
72
+ validates_presence_of :name
73
+ end
74
+ subclass = Class.new klass do
75
+ end
76
+ subsubclass = Class.new subclass do
77
+ end
78
+ subsubclass.new.valid?
79
+ end
80
+
81
+ expect false do
82
+ klass = Class.new do
83
+ include Validatable
84
+ attr_accessor :name
85
+ validates_presence_of :name
86
+ end
87
+ subclass = Class.new klass do
88
+ end
89
+ subclass.new.valid?
90
+ end
91
+
92
+ expect true do
93
+ klass = Class.new do
94
+ include Validatable
95
+ attr_accessor :name
96
+ validates_presence_of :name
97
+ end
98
+ subclass = Class.new klass do
99
+ end
100
+ instance = subclass.new
101
+ instance.name = 'a name'
102
+ instance.valid?
103
+ end
104
+
105
+ expect ArgumentError do
106
+ Class.new do
107
+ include Validatable
108
+ attr_accessor :name
109
+ validates_presence_of :name, :times => 1
110
+ validates_presence_of :name, :times => 1
111
+ end
112
+ end
113
+
114
+ expect ArgumentError do
115
+ Class.new do
116
+ include Validatable
117
+ attr_accessor :name
118
+ validates_presence_of :name, :times => 1, :key => 'anything'
119
+ validates_presence_of :name, :times => 1, :key => 'anything'
120
+ end
121
+ end
122
+
123
+ expect "is invalid" do
124
+ child_class = Class.new do
125
+ include Validatable
126
+ attr_accessor :name, :address
127
+ validates_presence_of :name
128
+ validates_format_of :address, :with => /.+/
129
+ end
130
+ klass = Class.new do
131
+ include Validatable
132
+ include_errors_from :child
133
+ define_method :child do
134
+ child_class.new
135
+ end
136
+ end
137
+ instance = klass.new
138
+ instance.valid?
139
+ instance.errors.on(:address)
140
+ end
141
+
142
+ expect "can't be empty" do
143
+ child_class = Class.new do
144
+ include Validatable
145
+ attr_accessor :name, :address
146
+ validates_presence_of :name
147
+ validates_format_of :address, :with => /.+/
148
+ end
149
+ klass = Class.new do
150
+ include Validatable
151
+ include_errors_from :child
152
+ define_method :child do
153
+ child_class.new
154
+ end
155
+ end
156
+ instance = klass.new
157
+ instance.valid?
158
+ instance.errors.on(:name)
159
+ end
160
+
161
+
162
+ test "when child validations have errors, level 2 and higher parent validations are not performed" do
163
+ child_class = Class.new do
164
+ include Validatable
165
+ attr_accessor :name
166
+ validates_presence_of :name
167
+ end
168
+
169
+ klass = Class.new do
170
+ include Validatable
171
+ extend Forwardable
172
+ def_delegator :child, :name
173
+ validates_true_for :name, :logic => lambda { false }, :level => 2, :message => "invalid message"
174
+ include_errors_from :child
175
+ define_method :child do
176
+ @child ||= child_class.new
177
+ end
178
+ end
179
+ instance = klass.new
180
+ instance.valid?
181
+ assert_equal "can't be empty", instance.errors.on(:name)
182
+ end
183
+
184
+ test "when child validations have errors, level 1 parent validations are still performed" do
185
+ child_class = Class.new do
186
+ include Validatable
187
+ attr_accessor :name
188
+ validates_presence_of :name
189
+ end
190
+
191
+ klass = Class.new do
192
+ include Validatable
193
+ validates_true_for :address, :logic => lambda { false }, :level => 1, :message => "invalid message"
194
+ include_errors_from :child
195
+ define_method :child do
196
+ @child ||= child_class.new
197
+ end
198
+
199
+ end
200
+ instance = klass.new
201
+ instance.valid?
202
+ assert_equal "can't be empty", instance.errors.on(:name)
203
+ assert_equal "invalid message", instance.errors.on(:address)
204
+ end
205
+
206
+ expect "can't be empty" do
207
+ child_class = Class.new do
208
+ include Validatable
209
+ attr_accessor :name, :address
210
+ validates_presence_of :name
211
+ end
212
+ klass = Class.new do
213
+ include Validatable
214
+ include_errors_from :child, :map => {:name => :namen}
215
+ define_method :child do
216
+ child_class.new
217
+ end
218
+ end
219
+ instance = klass.new
220
+ instance.valid?
221
+ instance.errors.on(:namen)
222
+ end
223
+
224
+ expect "can't be empty" do
225
+ child_class = Class.new do
226
+ include Validatable
227
+ attr_accessor :name, :address
228
+ validates_presence_of :name
229
+ end
230
+ klass = Class.new do
231
+ include Validatable
232
+ include_errors_from :child, :if => lambda { true }
233
+ define_method :child do
234
+ child_class.new
235
+ end
236
+ end
237
+ instance = klass.new
238
+ instance.valid?
239
+ instance.errors.on(:name)
240
+ end
241
+
242
+ expect true do
243
+ child_class = Class.new do
244
+ include Validatable
245
+ attr_accessor :name, :address
246
+ validates_presence_of :name
247
+ end
248
+ klass = Class.new do
249
+ include Validatable
250
+ include_errors_from :child, :if => lambda { false }
251
+ define_method :child do
252
+ child_class.new
253
+ end
254
+ end
255
+ instance = klass.new
256
+ instance.valid?
257
+ end
258
+
259
+ test ':if with symbol should work' do
260
+ klass = Class.new do
261
+ include Validatable
262
+ attr_accessor :name, :name_required
263
+ validates_presence_of :name, :if => :name_required?
264
+
265
+ def name_required?
266
+ name_required
267
+ end
268
+ end
269
+ instance = klass.new
270
+ instance.name_required = false
271
+ assert instance.valid?
272
+ instance.name_required = true
273
+ assert !instance.valid?
274
+ assert_equal "can't be empty", instance.errors.on(:name)
275
+ end
276
+
277
+ test ':if with string should work' do
278
+ klass = Class.new do
279
+ include Validatable
280
+ attr_accessor :name, :name_required
281
+ validates_presence_of :name, :if => 'name_required?'
282
+
283
+ def name_required?
284
+ name_required
285
+ end
286
+ end
287
+ instance = klass.new
288
+ instance.name_required = false
289
+ assert instance.valid?
290
+ instance.name_required = true
291
+ assert !instance.valid?
292
+ assert_equal "can't be empty", instance.errors.on(:name)
293
+ end
294
+
295
+ test "classes only have valid_for_* methods for groups that appear in their validations" do
296
+ class_with_group_one = Class.new do
297
+ include Validatable
298
+ validates_presence_of :name, :groups => :group_one
299
+ attr_accessor :name
300
+ end
301
+ class_with_group_two = Class.new do
302
+ include Validatable
303
+ validates_presence_of :name, :groups => :group_two
304
+ attr_accessor :name
305
+ end
306
+ assert_equal false, class_with_group_one.public_instance_methods.include?(:valid_for_group_two?)
307
+ assert_equal false, class_with_group_two.public_instance_methods.include?(:valid_for_group_one?)
308
+ end
309
+
310
+ test "nonmatching groups are not used as validations" do
311
+ klass = Class.new do
312
+ include Validatable
313
+ validates_presence_of :name, :groups => :group_one
314
+ validates_presence_of :address, :groups => :group_two
315
+ attr_accessor :name, :address
316
+ end
317
+ instance = klass.new
318
+ assert_equal nil, instance.errors.on(:name)
319
+ end
320
+
321
+ expect "can't be empty twice changed message" do
322
+ klass = Class.new do
323
+ include Validatable
324
+ validates_presence_of :name
325
+ attr_accessor :name
326
+ end
327
+
328
+ Validatable::ValidationBase.class_eval do
329
+ after_validate do |result, instance, attribute|
330
+ instance.errors.add(attribute, " changed message")
331
+ end
332
+ end
333
+ Validatable::ValidatesPresenceOf.class_eval do
334
+ after_validate do |result, instance, attribute|
335
+ instance.errors.add(attribute, " twice")
336
+ end
337
+ end
338
+ instance = klass.new
339
+ instance.valid?
340
+ Validatable::ValidatesPresenceOf.after_validations.clear
341
+ Validatable::ValidationBase.after_validations.clear
342
+ instance.errors.on(:name).join
343
+ end
344
+
345
+ expect false do
346
+ klass = Class.new do
347
+ include Validatable
348
+ validates_presence_of :name, :groups => :group_one
349
+ attr_accessor :name
350
+ end
351
+ instance = klass.new
352
+ instance.valid_for_group_one?
353
+ end
354
+
355
+ expect false do
356
+ klass = Class.new do
357
+ include Validatable
358
+ validates_presence_of :name, :groups => [:group_one, :group_two]
359
+ attr_accessor :name
360
+ end
361
+ instance = klass.new
362
+ instance.valid_for_group_one?
363
+ end
364
+
365
+ expect true do
366
+ klass = Class.new do
367
+ include Validatable
368
+ validates_presence_of :name, :groups => :group_one
369
+ validates_presence_of :address
370
+ attr_accessor :name, :address
371
+ end
372
+ instance = klass.new
373
+ instance.address = 'anything'
374
+ instance.valid?
375
+ end
376
+
377
+ expect true do
378
+ klass = Class.new do
379
+ include Validatable
380
+ validates_presence_of :name, :groups => :group_one
381
+ attr_accessor :name
382
+ end
383
+ instance = klass.new
384
+ instance.valid?
385
+ end
386
+
387
+ expect true do
388
+ klass = Class.new do
389
+ include Validatable
390
+ validates_presence_of :name, :times => 1
391
+ attr_accessor :name
392
+ end
393
+ instance = klass.new
394
+ instance.valid?
395
+ instance.valid?
396
+ end
397
+
398
+ expect false do
399
+ klass = Class.new do
400
+ include Validatable
401
+ validates_presence_of :name, :times => 1
402
+ attr_accessor :name
403
+ end
404
+ instance1 = klass.new
405
+ instance1.valid?
406
+ instance2 = klass.new
407
+ instance2.valid?
408
+ end
409
+
410
+ expect "name message" do
411
+ klass = Class.new do
412
+ include Validatable
413
+ validates_presence_of :name, :level => 1, :message => "name message"
414
+ validates_presence_of :address, :level => 2
415
+ attr_accessor :name, :address
416
+ end
417
+ instance = klass.new
418
+ instance.valid?
419
+ instance.errors.on(:name)
420
+ end
421
+
422
+ expect nil do
423
+ klass = Class.new do
424
+ include Validatable
425
+ validates_presence_of :name, :level => 1, :message => "name message"
426
+ validates_presence_of :address, :level => 2
427
+ attr_accessor :name, :address
428
+ end
429
+ instance = klass.new
430
+ instance.valid?
431
+ instance.errors.on(:address)
432
+ end
433
+
434
+ expect "Mod::Klass/Validatable::ValidatesPresenceOf/name" do
435
+ module Mod
436
+ class Klass
437
+ include Validatable
438
+ validates_presence_of :name
439
+ end
440
+ end
441
+ Mod::Klass.validations.first.key
442
+ end
443
+
444
+ expect "/Validatable::ValidatesPresenceOf/custom key" do
445
+ klass = Class.new do
446
+ include Validatable
447
+ validates_presence_of :name, :key => "custom key"
448
+ end
449
+ klass.validations.first.key
450
+ end
451
+
452
+ expect "can't be empty" do
453
+ klass = Class.new do
454
+ include Validatable
455
+ validates_presence_of :name, :address
456
+ attr_accessor :name, :address
457
+ end
458
+ instance = klass.new
459
+ instance.validate_only("presence_of/name")
460
+ instance.errors.on(:name)
461
+ end
462
+
463
+ expect nil do
464
+ klass = Class.new do
465
+ include Validatable
466
+ validates_presence_of :name, :address
467
+ attr_accessor :name, :address
468
+ end
469
+ instance = klass.new
470
+ instance.validate_only("presence_of/name")
471
+ instance.errors.on(:address)
472
+ end
473
+
474
+ test 'validate callback' do
475
+ klass = Class.new do
476
+ include Validatable
477
+ attr_accessor :action
478
+ validate :action_presence
479
+
480
+ private
481
+ def action_presence
482
+ errors.add(:action, 'is invalid') if action.blank?
483
+ end
484
+ end
485
+ instance = klass.new
486
+ instance.valid?
487
+ assert_equal 'is invalid', instance.errors.on(:action)
488
+
489
+ instance.action = 'walk'
490
+ instance.valid?
491
+ assert_nil instance.errors.on(:action)
492
+ end
493
+
494
+ test 'validate on create callback for new record' do
495
+ klass = Class.new do
496
+ include Validatable
497
+ attr_accessor :action
498
+ validate_on_create :action_presence
499
+
500
+ def new?
501
+ true
502
+ end
503
+
504
+ private
505
+ def action_presence
506
+ errors.add(:action, 'is invalid') if action.blank?
507
+ end
508
+ end
509
+ instance = klass.new
510
+ instance.valid?
511
+ assert_equal 'is invalid', instance.errors.on(:action)
512
+
513
+ instance.action = 'walk'
514
+ instance.valid?
515
+ assert_nil instance.errors.on(:action)
516
+ end
517
+
518
+ test 'validate on create callback for not new record' do
519
+ klass = Class.new do
520
+ include Validatable
521
+ attr_accessor :action
522
+ validate_on_create :action_presence
523
+
524
+ def new?
525
+ false
526
+ end
527
+
528
+ private
529
+ def action_presence
530
+ errors.add(:action, 'is invalid') if action.blank?
531
+ end
532
+ end
533
+ instance = klass.new
534
+ instance.valid?
535
+ assert_nil instance.errors.on(:action)
536
+
537
+ instance.action = 'walk'
538
+ instance.valid?
539
+ assert_nil instance.errors.on(:action)
540
+ end
541
+
542
+ test 'validate on update callback for new record' do
543
+ klass = Class.new do
544
+ include Validatable
545
+ attr_accessor :action
546
+ validate_on_update :action_presence
547
+
548
+ def new?
549
+ true
550
+ end
551
+
552
+ private
553
+ def action_presence
554
+ errors.add(:action, 'is invalid') if action.blank?
555
+ end
556
+ end
557
+ instance = klass.new
558
+ instance.valid?
559
+ assert_nil instance.errors.on(:action)
560
+
561
+ instance.action = 'walk'
562
+ instance.valid?
563
+ assert_nil instance.errors.on(:action)
564
+ end
565
+
566
+ test 'validate on update callback for not new record' do
567
+ klass = Class.new do
568
+ include Validatable
569
+ attr_accessor :action
570
+ validate_on_update :action_presence
571
+
572
+ def new?
573
+ false
574
+ end
575
+
576
+ private
577
+ def action_presence
578
+ errors.add(:action, 'is invalid') if action.blank?
579
+ end
580
+ end
581
+ instance = klass.new
582
+ instance.valid?
583
+ assert_equal 'is invalid', instance.errors.on(:action)
584
+
585
+ instance.action = 'walk'
586
+ instance.valid?
587
+ assert_nil instance.errors.on(:action)
588
+ end
589
+ end