jnunemaker-validatable 1.1.9

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.
Files changed (53) hide show
  1. data/README.rdoc +116 -0
  2. data/Rakefile +29 -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 +90 -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 +32 -0
  39. data/test/unit/test_errors.rb +70 -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