shoulda-matchers 5.3.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +26 -9
  4. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +7 -9
  5. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
  6. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +10 -1
  7. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
  8. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +7 -0
  9. data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +1 -1
  10. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +16 -6
  11. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +0 -6
  12. data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +532 -0
  13. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +3 -3
  14. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +4 -3
  15. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +64 -9
  16. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +40 -96
  17. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +6 -7
  18. data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
  19. data/lib/shoulda/matchers/active_model/validator.rb +4 -0
  20. data/lib/shoulda/matchers/active_model.rb +2 -1
  21. data/lib/shoulda/matchers/active_record/association_matcher.rb +31 -11
  22. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +23 -19
  23. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +27 -23
  24. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +0 -8
  25. data/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
  26. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
  27. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +24 -13
  28. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
  29. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -1
  30. data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  31. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +82 -70
  32. data/lib/shoulda/matchers/active_record.rb +2 -0
  33. data/lib/shoulda/matchers/doublespeak/double_collection.rb +2 -6
  34. data/lib/shoulda/matchers/doublespeak/world.rb +2 -6
  35. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +13 -15
  36. data/lib/shoulda/matchers/rails_shim.rb +8 -6
  37. data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
  38. data/lib/shoulda/matchers/util.rb +17 -19
  39. data/lib/shoulda/matchers/version.rb +1 -1
  40. data/lib/shoulda/matchers.rb +2 -2
  41. data/shoulda-matchers.gemspec +1 -1
  42. metadata +11 -8
  43. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -136
@@ -357,51 +357,33 @@ module Shoulda
357
357
  end
358
358
 
359
359
  # @private
360
- class ValidateNumericalityOfMatcher
360
+ class ValidateNumericalityOfMatcher < ValidationMatcher
361
361
  NUMERIC_NAME = 'number'.freeze
362
362
  DEFAULT_DIFF_TO_COMPARE = 1
363
363
 
364
- include Qualifiers::IgnoringInterferenceByWriter
365
-
366
364
  attr_reader :diff_to_compare
367
365
 
368
366
  def initialize(attribute)
369
367
  super
370
- @attribute = attribute
371
368
  @submatchers = []
372
369
  @diff_to_compare = DEFAULT_DIFF_TO_COMPARE
373
- @expects_custom_validation_message = false
374
370
  @expects_to_allow_nil = false
375
- @expects_strict = false
376
371
  @allowed_type_adjective = nil
377
372
  @allowed_type_name = 'number'
378
- @context = nil
379
- @expected_message = nil
380
- end
381
-
382
- def strict
383
- @expects_strict = true
384
- self
385
- end
386
373
 
387
- def expects_strict?
388
- @expects_strict
374
+ add_disallow_non_numeric_value_matcher
389
375
  end
390
376
 
391
377
  def only_integer
392
378
  prepare_submatcher(
393
- NumericalityMatchers::OnlyIntegerMatcher.new(self, @attribute),
379
+ NumericalityMatchers::OnlyIntegerMatcher.new(self, attribute),
394
380
  )
395
381
  self
396
382
  end
397
383
 
398
384
  def allow_nil
399
385
  @expects_to_allow_nil = true
400
- prepare_submatcher(
401
- AllowValueMatcher.new(nil).
402
- for(@attribute).
403
- with_message(:not_a_number),
404
- )
386
+ prepare_submatcher(allow_value_matcher(nil, :not_a_number))
405
387
  self
406
388
  end
407
389
 
@@ -411,70 +393,55 @@ module Shoulda
411
393
 
412
394
  def odd
413
395
  prepare_submatcher(
414
- NumericalityMatchers::OddNumberMatcher.new(self, @attribute),
396
+ NumericalityMatchers::OddNumberMatcher.new(self, attribute),
415
397
  )
416
398
  self
417
399
  end
418
400
 
419
401
  def even
420
402
  prepare_submatcher(
421
- NumericalityMatchers::EvenNumberMatcher.new(self, @attribute),
403
+ NumericalityMatchers::EvenNumberMatcher.new(self, attribute),
422
404
  )
423
405
  self
424
406
  end
425
407
 
426
408
  def is_greater_than(value)
427
- prepare_submatcher(comparison_matcher_for(value, :>).for(@attribute))
409
+ prepare_submatcher(comparison_matcher_for(value, :>).for(attribute))
428
410
  self
429
411
  end
430
412
 
431
413
  def is_greater_than_or_equal_to(value)
432
- prepare_submatcher(comparison_matcher_for(value, :>=).for(@attribute))
414
+ prepare_submatcher(comparison_matcher_for(value, :>=).for(attribute))
433
415
  self
434
416
  end
435
417
 
436
418
  def is_equal_to(value)
437
- prepare_submatcher(comparison_matcher_for(value, :==).for(@attribute))
419
+ prepare_submatcher(comparison_matcher_for(value, :==).for(attribute))
438
420
  self
439
421
  end
440
422
 
441
423
  def is_less_than(value)
442
- prepare_submatcher(comparison_matcher_for(value, :<).for(@attribute))
424
+ prepare_submatcher(comparison_matcher_for(value, :<).for(attribute))
443
425
  self
444
426
  end
445
427
 
446
428
  def is_less_than_or_equal_to(value)
447
- prepare_submatcher(comparison_matcher_for(value, :<=).for(@attribute))
429
+ prepare_submatcher(comparison_matcher_for(value, :<=).for(attribute))
448
430
  self
449
431
  end
450
432
 
451
433
  def is_other_than(value)
452
- prepare_submatcher(comparison_matcher_for(value, :!=).for(@attribute))
434
+ prepare_submatcher(comparison_matcher_for(value, :!=).for(attribute))
453
435
  self
454
436
  end
455
437
 
456
438
  def is_in(range)
457
439
  prepare_submatcher(
458
- NumericalityMatchers::RangeMatcher.new(self, @attribute, range),
440
+ NumericalityMatchers::RangeMatcher.new(self, attribute, range),
459
441
  )
460
442
  self
461
443
  end
462
444
 
463
- def with_message(message)
464
- @expects_custom_validation_message = true
465
- @expected_message = message
466
- self
467
- end
468
-
469
- def expects_custom_validation_message?
470
- @expects_custom_validation_message
471
- end
472
-
473
- def on(context)
474
- @context = context
475
- self
476
- end
477
-
478
445
  def matches?(subject)
479
446
  matches_or_does_not_match?(subject)
480
447
  first_submatcher_that_fails_to_match.nil?
@@ -486,24 +453,18 @@ module Shoulda
486
453
  end
487
454
 
488
455
  def simple_description
489
- description = ''
456
+ String.new.tap do |description|
457
+ description << "validate that :#{attribute} looks like "
458
+ description << Shoulda::Matchers::Util.a_or_an(full_allowed_type)
490
459
 
491
- description << "validate that :#{@attribute} looks like "
492
- description << Shoulda::Matchers::Util.a_or_an(full_allowed_type)
493
-
494
- if range_description.present?
495
- description << " #{range_description}"
496
- end
460
+ if range_description.present?
461
+ description << " #{range_description}"
462
+ end
497
463
 
498
- if comparison_descriptions.present?
499
- description << " #{comparison_descriptions}"
464
+ if comparison_descriptions.present?
465
+ description << " #{comparison_descriptions}"
466
+ end
500
467
  end
501
-
502
- description
503
- end
504
-
505
- def description
506
- ValidationMatcher::BuildDescription.call(self, simple_description)
507
468
  end
508
469
 
509
470
  def failure_message
@@ -532,44 +493,29 @@ module Shoulda
532
493
  @subject = subject
533
494
  @number_of_submatchers = @submatchers.size
534
495
 
535
- add_disallow_value_matcher
536
496
  qualify_submatchers
537
497
  end
538
498
 
539
- def overall_failure_message
540
- Shoulda::Matchers.word_wrap(
541
- "Expected #{model.name} to #{description}, but this could not "\
542
- 'be proved.',
543
- )
544
- end
545
-
546
- def overall_failure_message_when_negated
547
- Shoulda::Matchers.word_wrap(
548
- "Expected #{model.name} not to #{description}, but this could not "\
549
- 'be proved.',
550
- )
551
- end
552
-
553
499
  def attribute_is_active_record_column?
554
- columns_hash.key?(@attribute.to_s)
500
+ columns_hash.key?(attribute.to_s)
555
501
  end
556
502
 
557
503
  def column_type
558
- columns_hash[@attribute.to_s].type
504
+ columns_hash[attribute.to_s].type
559
505
  end
560
506
 
561
507
  def columns_hash
562
- if @subject.class.respond_to?(:columns_hash)
563
- @subject.class.columns_hash
508
+ if subject.class.respond_to?(:columns_hash)
509
+ subject.class.columns_hash
564
510
  else
565
511
  {}
566
512
  end
567
513
  end
568
514
 
569
- def add_disallow_value_matcher
515
+ def add_disallow_non_numeric_value_matcher
570
516
  disallow_value_matcher = DisallowValueMatcher.
571
517
  new(non_numeric_value).
572
- for(@attribute).
518
+ for(attribute).
573
519
  with_message(:not_a_number)
574
520
 
575
521
  add_submatcher(disallow_value_matcher)
@@ -581,9 +527,9 @@ module Shoulda
581
527
  end
582
528
 
583
529
  def comparison_matcher_for(value, operator)
584
- NumericalityMatchers::ComparisonMatcher.
530
+ ComparisonMatcher.
585
531
  new(self, value, operator).
586
- for(@attribute)
532
+ for(attribute)
587
533
  end
588
534
 
589
535
  def add_submatcher(submatcher)
@@ -615,8 +561,8 @@ module Shoulda
615
561
  submatcher.with_message(@expected_message)
616
562
  end
617
563
 
618
- if @context
619
- submatcher.on(@context)
564
+ if context
565
+ submatcher.on(context)
620
566
  end
621
567
 
622
568
  submatcher.ignoring_interference_by_writer(
@@ -634,23 +580,25 @@ module Shoulda
634
580
  end
635
581
 
636
582
  def has_been_qualified?
637
- @submatchers.any? do |submatcher|
638
- Shoulda::Matchers::RailsShim.parent_of(submatcher.class) ==
639
- NumericalityMatchers
640
- end
583
+ @submatchers.any? { |submatcher| submatcher_qualified?(submatcher) }
584
+ end
585
+
586
+ def submatcher_qualified?(submatcher)
587
+ Shoulda::Matchers::RailsShim.parent_of(submatcher.class) ==
588
+ NumericalityMatchers || submatcher.instance_of?(ComparisonMatcher)
641
589
  end
642
590
 
643
591
  def first_submatcher_that_fails_to_match
644
592
  @_first_submatcher_that_fails_to_match ||=
645
593
  @submatchers.detect do |submatcher|
646
- !submatcher.matches?(@subject)
594
+ !submatcher.matches?(subject)
647
595
  end
648
596
  end
649
597
 
650
598
  def first_submatcher_that_fails_to_not_match
651
599
  @_first_submatcher_that_fails_to_not_match ||=
652
600
  @submatchers.detect do |submatcher|
653
- !submatcher.does_not_match?(@subject)
601
+ !submatcher.does_not_match?(subject)
654
602
  end
655
603
  end
656
604
 
@@ -719,10 +667,6 @@ module Shoulda
719
667
  range_submatcher&.range_description
720
668
  end
721
669
 
722
- def model
723
- @subject.class
724
- end
725
-
726
670
  def non_numeric_value
727
671
  'abcd'
728
672
  end
@@ -42,13 +42,12 @@ module Shoulda
42
42
  description_clauses = []
43
43
 
44
44
  if matcher.try(:expects_strict?)
45
- description_clauses << 'raising a validation exception'
46
-
47
- if matcher.try(:expects_custom_validation_message?)
48
- description_clauses.last << ' with a custom message'
49
- end
50
-
51
- description_clauses.last << ' on failure'
45
+ description_clauses <<
46
+ if matcher.try(:expects_custom_validation_message?)
47
+ 'raising a validation exception with a custom message on failure'
48
+ else
49
+ 'raising a validation exception on failure'
50
+ end
52
51
  elsif matcher.try(:expects_custom_validation_message?)
53
52
  description_clauses <<
54
53
  'producing a custom validation error on failure'
@@ -186,6 +186,12 @@ module Shoulda
186
186
  def blank_values
187
187
  ['', ' ', "\n", "\r", "\t", "\f"]
188
188
  end
189
+
190
+ def array_column?
191
+ @subject.class.respond_to?(:columns_hash) &&
192
+ @subject.class.columns_hash[@attribute.to_s].respond_to?(:array) &&
193
+ @subject.class.columns_hash[@attribute.to_s].array
194
+ end
189
195
  end
190
196
  end
191
197
  end
@@ -25,6 +25,10 @@ module Shoulda
25
25
  messages.any?
26
26
  end
27
27
 
28
+ def has_any_errors?
29
+ record.errors.any?
30
+ end
31
+
28
32
  def captured_validation_exception?
29
33
  @captured_validation_exception
30
34
  end
@@ -21,8 +21,9 @@ require 'shoulda/matchers/active_model/validate_presence_of_matcher'
21
21
  require 'shoulda/matchers/active_model/validate_acceptance_of_matcher'
22
22
  require 'shoulda/matchers/active_model/validate_confirmation_of_matcher'
23
23
  require 'shoulda/matchers/active_model/validate_numericality_of_matcher'
24
+ require 'shoulda/matchers/active_model/validate_comparison_of_matcher'
25
+ require 'shoulda/matchers/active_model/comparison_matcher'
24
26
  require 'shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher'
25
- require 'shoulda/matchers/active_model/numericality_matchers/comparison_matcher'
26
27
  require 'shoulda/matchers/active_model/numericality_matchers/odd_number_matcher'
27
28
  require 'shoulda/matchers/active_model/numericality_matchers/even_number_matcher'
28
29
  require 'shoulda/matchers/active_model/numericality_matchers/only_integer_matcher'
@@ -1093,6 +1093,11 @@ module Shoulda
1093
1093
  self
1094
1094
  end
1095
1095
 
1096
+ def with_query_constraints(query_constraints)
1097
+ @options[:query_constraints] = query_constraints
1098
+ self
1099
+ end
1100
+
1096
1101
  def required(required = true)
1097
1102
  remove_submatcher(AssociationMatchers::OptionalMatcher)
1098
1103
  add_submatcher(
@@ -1157,6 +1162,7 @@ module Shoulda
1157
1162
  (polymorphic? || class_exists?) &&
1158
1163
  foreign_key_exists? &&
1159
1164
  primary_key_exists? &&
1165
+ query_constraints_exists? &&
1160
1166
  class_name_correct? &&
1161
1167
  join_table_correct? &&
1162
1168
  autosave_correct? &&
@@ -1258,7 +1264,7 @@ module Shoulda
1258
1264
  false
1259
1265
  end
1260
1266
 
1261
- def macro_supports_primary_key?
1267
+ def macro_is_not_through?
1262
1268
  macro == :belongs_to ||
1263
1269
  ([:has_many, :has_one].include?(macro) && !through?)
1264
1270
  end
@@ -1268,7 +1274,25 @@ module Shoulda
1268
1274
  end
1269
1275
 
1270
1276
  def primary_key_exists?
1271
- !macro_supports_primary_key? || primary_key_correct?(model_class)
1277
+ !macro_is_not_through? || primary_key_correct?(model_class)
1278
+ end
1279
+
1280
+ def query_constraints_exists?
1281
+ !macro_is_not_through? || query_constraints_correct?
1282
+ end
1283
+
1284
+ def query_constraints_correct?
1285
+ if options.key?(:query_constraints)
1286
+ if option_verifier.correct_for_string?(:query_constraints, options[:query_constraints])
1287
+ true
1288
+ else
1289
+ @missing = "#{model_class} should have \:query_constraints"\
1290
+ " options set to #{options[:query_constraints]}"
1291
+ false
1292
+ end
1293
+ else
1294
+ true
1295
+ end
1272
1296
  end
1273
1297
 
1274
1298
  def belongs_foreign_key_missing?
@@ -1299,10 +1323,7 @@ module Shoulda
1299
1323
  end
1300
1324
 
1301
1325
  def join_table_correct?
1302
- if (
1303
- macro != :has_and_belongs_to_many ||
1304
- join_table_matcher.matches?(@subject)
1305
- )
1326
+ if macro != :has_and_belongs_to_many || join_table_matcher.matches?(@subject)
1306
1327
  true
1307
1328
  else
1308
1329
  @missing = join_table_matcher.failure_message
@@ -1457,11 +1478,10 @@ module Shoulda
1457
1478
  end
1458
1479
 
1459
1480
  def foreign_key_reflection
1460
- if (
1461
- [:has_one, :has_many].include?(macro) &&
1462
- reflection.options.include?(:inverse_of) &&
1463
- reflection.options[:inverse_of] != false
1464
- )
1481
+ if [:has_one, :has_many].include?(macro) &&
1482
+ reflection.options.include?(:inverse_of) &&
1483
+ reflection.options[:inverse_of] != false
1484
+
1465
1485
  associated_class.reflect_on_association(
1466
1486
  reflection.options[:inverse_of],
1467
1487
  )
@@ -22,44 +22,48 @@ module Shoulda
22
22
  if submatcher_passes?(subject)
23
23
  true
24
24
  else
25
- @missing_option = 'and for the record '
25
+ @missing_option = build_missing_option
26
26
 
27
- missing_option <<
27
+ false
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :attribute_name, :optional, :submatcher
34
+
35
+ def submatcher_passes?(subject)
36
+ if optional
37
+ submatcher.matches?(subject)
38
+ else
39
+ submatcher.does_not_match?(subject)
40
+ end
41
+ end
42
+
43
+ def build_missing_option
44
+ String.new('and for the record ').tap do |missing_option_string|
45
+ missing_option_string <<
28
46
  if optional
29
47
  'not to '
30
48
  else
31
49
  'to '
32
50
  end
33
51
 
34
- missing_option << (
52
+ missing_option_string << (
35
53
  'fail validation if '\
36
54
  ":#{attribute_name} is unset; i.e., either the association "\
37
55
  'should have been defined with `optional: '\
38
56
  "#{optional.inspect}`, or there "
39
57
  )
40
58
 
41
- missing_option <<
59
+ missing_option_string <<
42
60
  if optional
43
61
  'should not '
44
62
  else
45
63
  'should '
46
64
  end
47
65
 
48
- missing_option << "be a presence validation on :#{attribute_name}"
49
-
50
- false
51
- end
52
- end
53
-
54
- private
55
-
56
- attr_reader :attribute_name, :optional, :submatcher
57
-
58
- def submatcher_passes?(subject)
59
- if optional
60
- submatcher.matches?(subject)
61
- else
62
- submatcher.does_not_match?(subject)
66
+ missing_option_string << "be a presence validation on :#{attribute_name}"
63
67
  end
64
68
  end
65
69
  end
@@ -23,50 +23,54 @@ module Shoulda
23
23
  if submatcher_passes?(subject)
24
24
  true
25
25
  else
26
- @missing_option = 'and for the record '
26
+ @missing_option = build_missing_option
27
27
 
28
- missing_option <<
28
+ false
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :attribute_name, :required, :submatcher
35
+
36
+ def submatcher_passes?(subject)
37
+ if required
38
+ submatcher.matches?(subject)
39
+ else
40
+ submatcher.does_not_match?(subject)
41
+ end
42
+ end
43
+
44
+ def validation_message_key
45
+ :required
46
+ end
47
+
48
+ def build_missing_option
49
+ String.new('and for the record ').tap do |missing_option_string|
50
+ missing_option_string <<
29
51
  if required
30
52
  'to '
31
53
  else
32
54
  'not to '
33
55
  end
34
56
 
35
- missing_option << (
57
+ missing_option_string << (
36
58
  'fail validation if '\
37
59
  ":#{attribute_name} is unset; i.e., either the association "\
38
60
  'should have been defined with `required: '\
39
61
  "#{required.inspect}`, or there "
40
62
  )
41
63
 
42
- missing_option <<
64
+ missing_option_string <<
43
65
  if required
44
66
  'should '
45
67
  else
46
68
  'should not '
47
69
  end
48
70
 
49
- missing_option << "be a presence validation on :#{attribute_name}"
50
-
51
- false
71
+ missing_option_string << "be a presence validation on :#{attribute_name}"
52
72
  end
53
73
  end
54
-
55
- private
56
-
57
- attr_reader :attribute_name, :required, :submatcher
58
-
59
- def submatcher_passes?(subject)
60
- if required
61
- submatcher.matches?(subject)
62
- else
63
- submatcher.does_not_match?(subject)
64
- end
65
- end
66
-
67
- def validation_message_key
68
- :required
69
- end
70
74
  end
71
75
  end
72
76
  end
@@ -227,14 +227,6 @@ module Shoulda
227
227
  self
228
228
  end
229
229
 
230
- def with(expected_enum_values)
231
- Shoulda::Matchers.warn_about_deprecated_method(
232
- 'The `with` qualifier on `define_enum_for`',
233
- '`with_values`',
234
- )
235
- with_values(expected_enum_values)
236
- end
237
-
238
230
  def with_prefix(expected_prefix = true)
239
231
  options[:prefix] = expected_prefix
240
232
  self