shoulda-matchers 5.3.0 → 6.1.0

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 (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