shoulda-matchers 5.3.0 → 6.2.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +39 -15
  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 +46 -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 +24 -11
  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 +543 -15
  22. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +9 -1
  23. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -0
  24. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +4 -0
  25. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +23 -19
  26. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +27 -23
  27. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +0 -8
  28. data/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
  29. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
  30. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +24 -13
  31. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
  32. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -1
  33. data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  34. data/lib/shoulda/matchers/active_record/uniqueness/model.rb +1 -1
  35. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +82 -70
  36. data/lib/shoulda/matchers/active_record.rb +2 -0
  37. data/lib/shoulda/matchers/doublespeak/double_collection.rb +2 -6
  38. data/lib/shoulda/matchers/doublespeak/world.rb +2 -6
  39. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +13 -15
  40. data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +7 -5
  41. data/lib/shoulda/matchers/integrations/libraries/routing.rb +5 -3
  42. data/lib/shoulda/matchers/rails_shim.rb +8 -6
  43. data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
  44. data/lib/shoulda/matchers/util.rb +18 -20
  45. data/lib/shoulda/matchers/version.rb +1 -1
  46. data/lib/shoulda/matchers.rb +2 -2
  47. data/shoulda-matchers.gemspec +1 -1
  48. metadata +11 -8
  49. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -136
@@ -3,7 +3,7 @@ module Shoulda
3
3
  module ActiveModel
4
4
  # The `validate_length_of` matcher tests usage of the
5
5
  # `validates_length_of` matcher. Note that this matcher is intended to be
6
- # used against string columns and not integer columns.
6
+ # used against string columns and associations and not integer columns.
7
7
  #
8
8
  # #### Qualifiers
9
9
  #
@@ -36,7 +36,8 @@ module Shoulda
36
36
  #
37
37
  # Use `is_at_least` to test usage of the `:minimum` option. This asserts
38
38
  # that the attribute can take a string which is equal to or longer than
39
- # the given length and cannot take a string which is shorter.
39
+ # the given length and cannot take a string which is shorter. This qualifier
40
+ # also works for associations.
40
41
  #
41
42
  # class User
42
43
  # include ActiveModel::Model
@@ -61,7 +62,8 @@ module Shoulda
61
62
  #
62
63
  # Use `is_at_most` to test usage of the `:maximum` option. This asserts
63
64
  # that the attribute can take a string which is equal to or shorter than
64
- # the given length and cannot take a string which is longer.
65
+ # the given length and cannot take a string which is longer. This qualifier
66
+ # also works for associations.
65
67
  #
66
68
  # class User
67
69
  # include ActiveModel::Model
@@ -84,7 +86,8 @@ module Shoulda
84
86
  #
85
87
  # Use `is_equal_to` to test usage of the `:is` option. This asserts that
86
88
  # the attribute can take a string which is exactly equal to the given
87
- # length and cannot take a string which is shorter or longer.
89
+ # length and cannot take a string which is shorter or longer. This qualifier
90
+ # also works for associations.
88
91
  #
89
92
  # class User
90
93
  # include ActiveModel::Model
@@ -106,7 +109,7 @@ module Shoulda
106
109
  # ##### is_at_least + is_at_most
107
110
  #
108
111
  # Use `is_at_least` and `is_at_most` together to test usage of the `:in`
109
- # option.
112
+ # option. This qualifies also works for associations.
110
113
  #
111
114
  # class User
112
115
  # include ActiveModel::Model
@@ -260,6 +263,29 @@ module Shoulda
260
263
  # should validate_length_of(:bio).is_at_least(15).allow_blank
261
264
  # end
262
265
  #
266
+ # ##### as_array
267
+ #
268
+ # Use `as_array` if you have an ActiveModel model and the attribute being validated
269
+ # is designed to store an array. (This is not necessary if you have an ActiveRecord
270
+ # model with an array column, as the matcher will detect this automatically.)
271
+ #
272
+ # class User
273
+ # include ActiveModel::Model
274
+ # attribute :arr, array: true
275
+ #
276
+ # validates_length_of :arr, minimum: 15
277
+ # end
278
+ #
279
+ # # RSpec
280
+ # describe User do
281
+ # it { should validate_length_of(:arr).as_array.is_at_least(15) }
282
+ # end
283
+ #
284
+ # # Minitest (Shoulda)
285
+ # class UserTest < ActiveSupport::TestCase
286
+ # should validate_length_of(:arr).as_array.is_at_least(15)
287
+ # end
288
+ #
263
289
  def validate_length_of(attr)
264
290
  ValidateLengthOfMatcher.new(attr)
265
291
  end
@@ -275,6 +301,11 @@ module Shoulda
275
301
  @long_message = nil
276
302
  end
277
303
 
304
+ def as_array
305
+ @options[:array] = true
306
+ self
307
+ end
308
+
278
309
  def is_at_least(length)
279
310
  @options[:minimum] = length
280
311
  @short_message ||= :too_short
@@ -451,15 +482,39 @@ module Shoulda
451
482
  end
452
483
 
453
484
  def allows_length_of?(length, message)
454
- allows_value_of(string_of_length(length), message)
485
+ allows_value_of(value_of_length(length), message)
455
486
  end
456
487
 
457
488
  def disallows_length_of?(length, message)
458
- disallows_value_of(string_of_length(length), message)
489
+ disallows_value_of(value_of_length(length), message)
490
+ end
491
+
492
+ def value_of_length(length)
493
+ if array_column?
494
+ ['x'] * length
495
+ elsif collection_association?
496
+ Array.new(length) { association_reflection.klass.new }
497
+ else
498
+ 'x' * length
499
+ end
500
+ end
501
+
502
+ def array_column?
503
+ @options[:array] || super
504
+ end
505
+
506
+ def collection_association?
507
+ association? && [:has_many, :has_and_belongs_to_many].include?(
508
+ association_reflection.macro,
509
+ )
510
+ end
511
+
512
+ def association?
513
+ association_reflection.present?
459
514
  end
460
515
 
461
- def string_of_length(length)
462
- 'x' * length
516
+ def association_reflection
517
+ model.try(:reflect_on_association, @attribute)
463
518
  end
464
519
 
465
520
  def translated_short_message
@@ -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'