mattmatt-validatable 1.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/README.rdoc +116 -0
  2. data/Rakefile +33 -0
  3. data/VERSION.yml +5 -0
  4. data/lib/validatable.rb +28 -0
  5. data/lib/validatable/child_validation.rb +15 -0
  6. data/lib/validatable/errors.rb +93 -0
  7. data/lib/validatable/included_validation.rb +9 -0
  8. data/lib/validatable/macros.rb +314 -0
  9. data/lib/validatable/object_extension.rb +21 -0
  10. data/lib/validatable/requireable.rb +26 -0
  11. data/lib/validatable/understandable.rb +31 -0
  12. data/lib/validatable/validatable_class_methods.rb +87 -0
  13. data/lib/validatable/validatable_instance_methods.rb +106 -0
  14. data/lib/validatable/validations/validates_acceptance_of.rb +14 -0
  15. data/lib/validatable/validations/validates_associated.rb +13 -0
  16. data/lib/validatable/validations/validates_confirmation_of.rb +23 -0
  17. data/lib/validatable/validations/validates_each.rb +14 -0
  18. data/lib/validatable/validations/validates_exclusion_of.rb +17 -0
  19. data/lib/validatable/validations/validates_format_of.rb +16 -0
  20. data/lib/validatable/validations/validates_inclusion_of.rb +17 -0
  21. data/lib/validatable/validations/validates_length_of.rb +30 -0
  22. data/lib/validatable/validations/validates_numericality_of.rb +27 -0
  23. data/lib/validatable/validations/validates_presence_of.rb +17 -0
  24. data/lib/validatable/validations/validates_true_for.rb +13 -0
  25. data/lib/validatable/validations/validation_base.rb +91 -0
  26. data/test/functional/test_validatable.rb +519 -0
  27. data/test/functional/test_validates_acceptance_of.rb +16 -0
  28. data/test/functional/test_validates_associated.rb +41 -0
  29. data/test/functional/test_validates_confirmation_of.rb +56 -0
  30. data/test/functional/test_validates_each.rb +14 -0
  31. data/test/functional/test_validates_exclusion_of.rb +29 -0
  32. data/test/functional/test_validates_format_of.rb +34 -0
  33. data/test/functional/test_validates_inclusion_of.rb +29 -0
  34. data/test/functional/test_validates_length_of.rb +64 -0
  35. data/test/functional/test_validates_numericality_of.rb +57 -0
  36. data/test/functional/test_validates_presence_of.rb +16 -0
  37. data/test/functional/test_validates_true_for.rb +27 -0
  38. data/test/test_helper.rb +33 -0
  39. data/test/unit/test_errors.rb +82 -0
  40. data/test/unit/test_understandable.rb +19 -0
  41. data/test/unit/test_validatable.rb +38 -0
  42. data/test/unit/test_validates_acceptance_of.rb +45 -0
  43. data/test/unit/test_validates_associated.rb +29 -0
  44. data/test/unit/test_validates_confirmation_of.rb +76 -0
  45. data/test/unit/test_validates_exclusion_of.rb +23 -0
  46. data/test/unit/test_validates_format_of.rb +44 -0
  47. data/test/unit/test_validates_inclusion_of.rb +23 -0
  48. data/test/unit/test_validates_length_of.rb +80 -0
  49. data/test/unit/test_validates_numericality_of.rb +76 -0
  50. data/test/unit/test_validates_presence_of.rb +35 -0
  51. data/test/unit/test_validates_true_for.rb +29 -0
  52. data/test/unit/test_validation_base.rb +52 -0
  53. metadata +133 -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
@@ -0,0 +1,519 @@
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 "is invalid" 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
+ valid_child = child_class.new
154
+ valid_child.name = "nom de plume"
155
+ valid_child.address = "nowhere"
156
+ [valid_child, child_class.new]
157
+ end
158
+ end
159
+ instance = klass.new
160
+ instance.valid?
161
+ instance.errors.on(:address)
162
+ end
163
+
164
+ expect true do
165
+ child_class = Class.new do
166
+ include Validatable
167
+ attr_accessor :name, :address
168
+ validates_presence_of :name
169
+ validates_format_of :address, :with => /.+/
170
+ end
171
+ klass = Class.new do
172
+ include Validatable
173
+ include_errors_from :child
174
+ define_method :child do
175
+ valid_child = child_class.new
176
+ valid_child.name = "nom de plume"
177
+ valid_child.address = "nowhere"
178
+ also_valid = child_class.new
179
+ also_valid.name = "nom de guerre"
180
+ also_valid.address = "somewhere else"
181
+ [valid_child, valid_child]
182
+ end
183
+ end
184
+ instance = klass.new
185
+ instance.valid?
186
+ end
187
+
188
+ expect "can't be empty" do
189
+ child_class = Class.new do
190
+ include Validatable
191
+ attr_accessor :name, :address
192
+ validates_presence_of :name
193
+ validates_format_of :address, :with => /.+/
194
+ end
195
+ klass = Class.new do
196
+ include Validatable
197
+ include_errors_from :child
198
+ define_method :child do
199
+ child_class.new
200
+ end
201
+ end
202
+ instance = klass.new
203
+ instance.valid?
204
+ instance.errors.on(:name)
205
+ end
206
+
207
+
208
+ test "when child validations have errors, level 2 and higher parent validations are not performed" do
209
+ child_class = Class.new do
210
+ include Validatable
211
+ attr_accessor :name
212
+ validates_presence_of :name
213
+ end
214
+
215
+ klass = Class.new do
216
+ include Validatable
217
+ extend Forwardable
218
+ def_delegator :child, :name
219
+ validates_true_for :name, :logic => lambda { false }, :level => 2, :message => "invalid message"
220
+ include_errors_from :child
221
+ define_method :child do
222
+ @child ||= child_class.new
223
+ end
224
+ end
225
+ instance = klass.new
226
+ instance.valid?
227
+ assert_equal "can't be empty", instance.errors.on(:name)
228
+ end
229
+
230
+ test "when child validations have errors, level 1 parent validations are still performed" do
231
+ child_class = Class.new do
232
+ include Validatable
233
+ attr_accessor :name
234
+ validates_presence_of :name
235
+ end
236
+
237
+ klass = Class.new do
238
+ include Validatable
239
+ validates_true_for :address, :logic => lambda { false }, :level => 1, :message => "invalid message"
240
+ include_errors_from :child
241
+ define_method :child do
242
+ @child ||= child_class.new
243
+ end
244
+
245
+ end
246
+ instance = klass.new
247
+ instance.valid?
248
+ assert_equal "can't be empty", instance.errors.on(:name)
249
+ assert_equal "invalid message", instance.errors.on(:address)
250
+ end
251
+
252
+ expect "can't be empty" do
253
+ child_class = Class.new do
254
+ include Validatable
255
+ attr_accessor :name, :address
256
+ validates_presence_of :name
257
+ end
258
+ klass = Class.new do
259
+ include Validatable
260
+ include_errors_from :child, :map => {:name => :namen}
261
+ define_method :child do
262
+ child_class.new
263
+ end
264
+ end
265
+ instance = klass.new
266
+ instance.valid?
267
+ instance.errors.on(:namen)
268
+ end
269
+
270
+ expect "can't be empty" do
271
+ child_class = Class.new do
272
+ include Validatable
273
+ attr_accessor :name, :address
274
+ validates_presence_of :name
275
+ end
276
+ klass = Class.new do
277
+ include Validatable
278
+ include_errors_from :child, :if => lambda { true }
279
+ define_method :child do
280
+ child_class.new
281
+ end
282
+ end
283
+ instance = klass.new
284
+ instance.valid?
285
+ instance.errors.on(:name)
286
+ end
287
+
288
+ expect true do
289
+ child_class = Class.new do
290
+ include Validatable
291
+ attr_accessor :name, :address
292
+ validates_presence_of :name
293
+ end
294
+ klass = Class.new do
295
+ include Validatable
296
+ include_errors_from :child, :if => lambda { false }
297
+ define_method :child do
298
+ child_class.new
299
+ end
300
+ end
301
+ instance = klass.new
302
+ instance.valid?
303
+ end
304
+
305
+ test ':if with symbol should work' do
306
+ klass = Class.new do
307
+ include Validatable
308
+ attr_accessor :name, :name_required
309
+ validates_presence_of :name, :if => :name_required?
310
+
311
+ def name_required?
312
+ name_required
313
+ end
314
+ end
315
+ instance = klass.new
316
+ instance.name_required = false
317
+ assert instance.valid?
318
+ instance.name_required = true
319
+ assert !instance.valid?
320
+ assert_equal "can't be empty", instance.errors.on(:name)
321
+ end
322
+
323
+ test ':if with string should work' do
324
+ klass = Class.new do
325
+ include Validatable
326
+ attr_accessor :name, :name_required
327
+ validates_presence_of :name, :if => 'name_required?'
328
+
329
+ def name_required?
330
+ name_required
331
+ end
332
+ end
333
+ instance = klass.new
334
+ instance.name_required = false
335
+ assert instance.valid?
336
+ instance.name_required = true
337
+ assert !instance.valid?
338
+ assert_equal "can't be empty", instance.errors.on(:name)
339
+ end
340
+
341
+ test "classes only have valid_for_* methods for groups that appear in their validations" do
342
+ class_with_group_one = Class.new do
343
+ include Validatable
344
+ validates_presence_of :name, :groups => :group_one
345
+ attr_accessor :name
346
+ end
347
+ class_with_group_two = Class.new do
348
+ include Validatable
349
+ validates_presence_of :name, :groups => :group_two
350
+ attr_accessor :name
351
+ end
352
+ assert_equal false, class_with_group_one.public_instance_methods.include?(:valid_for_group_two?)
353
+ assert_equal false, class_with_group_two.public_instance_methods.include?(:valid_for_group_one?)
354
+ end
355
+
356
+ test "nonmatching groups are not used as validations" do
357
+ klass = Class.new do
358
+ include Validatable
359
+ validates_presence_of :name, :groups => :group_one
360
+ validates_presence_of :address, :groups => :group_two
361
+ attr_accessor :name, :address
362
+ end
363
+ instance = klass.new
364
+ assert_equal nil, instance.errors.on(:name)
365
+ end
366
+
367
+ expect "can't be empty twice changed message" do
368
+ klass = Class.new do
369
+ include Validatable
370
+ validates_presence_of :name
371
+ attr_accessor :name
372
+ end
373
+
374
+ Validatable::ValidationBase.class_eval do
375
+ after_validate do |result, instance, attribute|
376
+ instance.errors.add(attribute, " changed message")
377
+ end
378
+ end
379
+ Validatable::ValidatesPresenceOf.class_eval do
380
+ after_validate do |result, instance, attribute|
381
+ instance.errors.add(attribute, " twice")
382
+ end
383
+ end
384
+ instance = klass.new
385
+ instance.valid?
386
+ Validatable::ValidatesPresenceOf.after_validations.clear
387
+ Validatable::ValidationBase.after_validations.clear
388
+ instance.errors.on(:name).join
389
+ end
390
+
391
+ expect false do
392
+ klass = Class.new do
393
+ include Validatable
394
+ validates_presence_of :name, :groups => :group_one
395
+ attr_accessor :name
396
+ end
397
+ instance = klass.new
398
+ instance.valid_for_group_one?
399
+ end
400
+
401
+ expect false do
402
+ klass = Class.new do
403
+ include Validatable
404
+ validates_presence_of :name, :groups => [:group_one, :group_two]
405
+ attr_accessor :name
406
+ end
407
+ instance = klass.new
408
+ instance.valid_for_group_one?
409
+ end
410
+
411
+ expect true do
412
+ klass = Class.new do
413
+ include Validatable
414
+ validates_presence_of :name, :groups => :group_one
415
+ validates_presence_of :address
416
+ attr_accessor :name, :address
417
+ end
418
+ instance = klass.new
419
+ instance.address = 'anything'
420
+ instance.valid?
421
+ end
422
+
423
+ expect true do
424
+ klass = Class.new do
425
+ include Validatable
426
+ validates_presence_of :name, :groups => :group_one
427
+ attr_accessor :name
428
+ end
429
+ instance = klass.new
430
+ instance.valid?
431
+ end
432
+
433
+ expect true do
434
+ klass = Class.new do
435
+ include Validatable
436
+ validates_presence_of :name, :times => 1
437
+ attr_accessor :name
438
+ end
439
+ instance = klass.new
440
+ instance.valid?
441
+ instance.valid?
442
+ end
443
+
444
+ expect false do
445
+ klass = Class.new do
446
+ include Validatable
447
+ validates_presence_of :name, :times => 1
448
+ attr_accessor :name
449
+ end
450
+ instance1 = klass.new
451
+ instance1.valid?
452
+ instance2 = klass.new
453
+ instance2.valid?
454
+ end
455
+
456
+ expect "name message" do
457
+ klass = Class.new do
458
+ include Validatable
459
+ validates_presence_of :name, :level => 1, :message => "name message"
460
+ validates_presence_of :address, :level => 2
461
+ attr_accessor :name, :address
462
+ end
463
+ instance = klass.new
464
+ instance.valid?
465
+ instance.errors.on(:name)
466
+ end
467
+
468
+ expect nil do
469
+ klass = Class.new do
470
+ include Validatable
471
+ validates_presence_of :name, :level => 1, :message => "name message"
472
+ validates_presence_of :address, :level => 2
473
+ attr_accessor :name, :address
474
+ end
475
+ instance = klass.new
476
+ instance.valid?
477
+ instance.errors.on(:address)
478
+ end
479
+
480
+ expect "Mod::Klass/Validatable::ValidatesPresenceOf/name" do
481
+ module Mod
482
+ class Klass
483
+ include Validatable
484
+ validates_presence_of :name
485
+ end
486
+ end
487
+ Mod::Klass.validations.first.key
488
+ end
489
+
490
+ expect "/Validatable::ValidatesPresenceOf/custom key" do
491
+ klass = Class.new do
492
+ include Validatable
493
+ validates_presence_of :name, :key => "custom key"
494
+ end
495
+ klass.validations.first.key
496
+ end
497
+
498
+ expect "can't be empty" do
499
+ klass = Class.new do
500
+ include Validatable
501
+ validates_presence_of :name, :address
502
+ attr_accessor :name, :address
503
+ end
504
+ instance = klass.new
505
+ instance.validate_only("presence_of/name")
506
+ instance.errors.on(:name)
507
+ end
508
+
509
+ expect nil do
510
+ klass = Class.new do
511
+ include Validatable
512
+ validates_presence_of :name, :address
513
+ attr_accessor :name, :address
514
+ end
515
+ instance = klass.new
516
+ instance.validate_only("presence_of/name")
517
+ instance.errors.on(:address)
518
+ end
519
+ end