shoulda-matchers 5.3.0 → 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
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'