shoulda-matchers 5.1.0 → 6.4.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +41 -18
  4. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +7 -9
  5. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +1 -1
  6. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
  7. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +46 -1
  8. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
  9. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +7 -0
  10. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +3 -5
  11. data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
  12. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +53 -0
  13. data/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
  14. data/lib/shoulda/matchers/active_model/qualifiers.rb +1 -0
  15. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +2 -7
  16. data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +532 -0
  17. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +5 -5
  18. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +29 -14
  19. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +65 -10
  20. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +76 -86
  21. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +29 -4
  22. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +6 -7
  23. data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
  24. data/lib/shoulda/matchers/active_model/validator.rb +4 -0
  25. data/lib/shoulda/matchers/active_model.rb +4 -1
  26. data/lib/shoulda/matchers/active_record/association_matcher.rb +543 -15
  27. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +34 -4
  28. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +9 -1
  29. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -0
  30. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +4 -0
  31. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +23 -19
  32. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +27 -23
  33. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +338 -30
  34. data/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
  35. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
  36. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +24 -13
  37. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
  38. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -1
  39. data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  40. data/lib/shoulda/matchers/active_record/uniqueness/model.rb +13 -1
  41. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +82 -70
  42. data/lib/shoulda/matchers/active_record.rb +2 -0
  43. data/lib/shoulda/matchers/doublespeak/double_collection.rb +2 -6
  44. data/lib/shoulda/matchers/doublespeak/world.rb +2 -6
  45. data/lib/shoulda/matchers/doublespeak.rb +0 -1
  46. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +13 -15
  47. data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +7 -5
  48. data/lib/shoulda/matchers/integrations/libraries/routing.rb +5 -3
  49. data/lib/shoulda/matchers/rails_shim.rb +22 -6
  50. data/lib/shoulda/matchers/util/word_wrap.rb +2 -2
  51. data/lib/shoulda/matchers/util.rb +18 -20
  52. data/lib/shoulda/matchers/version.rb +1 -1
  53. data/lib/shoulda/matchers.rb +2 -2
  54. data/shoulda-matchers.gemspec +2 -2
  55. metadata +15 -9
  56. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -157
@@ -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
@@ -239,7 +242,7 @@ module Shoulda
239
242
  #
240
243
  # @return [ValidateLengthOfMatcher]
241
244
  #
242
- # # ##### allow_blank
245
+ # ##### allow_blank
243
246
  #
244
247
  # Use `allow_blank` to assert that the attribute allows blank.
245
248
  #
@@ -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
@@ -276,6 +276,33 @@ module Shoulda
276
276
  # should validate_numericality_of(:birth_day).odd
277
277
  # end
278
278
  #
279
+ # ##### is_in
280
+ #
281
+ # Use `is_in` to test usage of the `:in` option.
282
+ # This asserts that the attribute can take a number which is contained
283
+ # in the given range.
284
+ #
285
+ # class Person
286
+ # include ActiveModel::Model
287
+ # attr_accessor :legal_age
288
+ #
289
+ # validates_numericality_of :birth_month, in: 1..12
290
+ # end
291
+ #
292
+ # # RSpec
293
+ # RSpec.describe Person, type: :model do
294
+ # it do
295
+ # should validate_numericality_of(:birth_month).
296
+ # is_in(1..12)
297
+ # end
298
+ # end
299
+ #
300
+ # # Minitest (Shoulda)
301
+ # class PersonTest < ActiveSupport::TestCase
302
+ # should validate_numericality_of(:birth_month).
303
+ # is_in(1..12)
304
+ # end
305
+ #
279
306
  # ##### with_message
280
307
  #
281
308
  # Use `with_message` if you are using a custom validation message.
@@ -330,51 +357,33 @@ module Shoulda
330
357
  end
331
358
 
332
359
  # @private
333
- class ValidateNumericalityOfMatcher
360
+ class ValidateNumericalityOfMatcher < ValidationMatcher
334
361
  NUMERIC_NAME = 'number'.freeze
335
362
  DEFAULT_DIFF_TO_COMPARE = 1
336
363
 
337
- include Qualifiers::IgnoringInterferenceByWriter
338
-
339
364
  attr_reader :diff_to_compare
340
365
 
341
366
  def initialize(attribute)
342
367
  super
343
- @attribute = attribute
344
368
  @submatchers = []
345
369
  @diff_to_compare = DEFAULT_DIFF_TO_COMPARE
346
- @expects_custom_validation_message = false
347
370
  @expects_to_allow_nil = false
348
- @expects_strict = false
349
371
  @allowed_type_adjective = nil
350
372
  @allowed_type_name = 'number'
351
- @context = nil
352
- @expected_message = nil
353
- end
354
-
355
- def strict
356
- @expects_strict = true
357
- self
358
- end
359
373
 
360
- def expects_strict?
361
- @expects_strict
374
+ add_disallow_non_numeric_value_matcher
362
375
  end
363
376
 
364
377
  def only_integer
365
378
  prepare_submatcher(
366
- NumericalityMatchers::OnlyIntegerMatcher.new(self, @attribute),
379
+ NumericalityMatchers::OnlyIntegerMatcher.new(self, attribute),
367
380
  )
368
381
  self
369
382
  end
370
383
 
371
384
  def allow_nil
372
385
  @expects_to_allow_nil = true
373
- prepare_submatcher(
374
- AllowValueMatcher.new(nil).
375
- for(@attribute).
376
- with_message(:not_a_number),
377
- )
386
+ prepare_submatcher(allow_value_matcher(nil, :not_a_number))
378
387
  self
379
388
  end
380
389
 
@@ -384,60 +393,52 @@ module Shoulda
384
393
 
385
394
  def odd
386
395
  prepare_submatcher(
387
- NumericalityMatchers::OddNumberMatcher.new(self, @attribute),
396
+ NumericalityMatchers::OddNumberMatcher.new(self, attribute),
388
397
  )
389
398
  self
390
399
  end
391
400
 
392
401
  def even
393
402
  prepare_submatcher(
394
- NumericalityMatchers::EvenNumberMatcher.new(self, @attribute),
403
+ NumericalityMatchers::EvenNumberMatcher.new(self, attribute),
395
404
  )
396
405
  self
397
406
  end
398
407
 
399
408
  def is_greater_than(value)
400
- prepare_submatcher(comparison_matcher_for(value, :>).for(@attribute))
409
+ prepare_submatcher(comparison_matcher_for(value, :>).for(attribute))
401
410
  self
402
411
  end
403
412
 
404
413
  def is_greater_than_or_equal_to(value)
405
- prepare_submatcher(comparison_matcher_for(value, :>=).for(@attribute))
414
+ prepare_submatcher(comparison_matcher_for(value, :>=).for(attribute))
406
415
  self
407
416
  end
408
417
 
409
418
  def is_equal_to(value)
410
- prepare_submatcher(comparison_matcher_for(value, :==).for(@attribute))
419
+ prepare_submatcher(comparison_matcher_for(value, :==).for(attribute))
411
420
  self
412
421
  end
413
422
 
414
423
  def is_less_than(value)
415
- prepare_submatcher(comparison_matcher_for(value, :<).for(@attribute))
424
+ prepare_submatcher(comparison_matcher_for(value, :<).for(attribute))
416
425
  self
417
426
  end
418
427
 
419
428
  def is_less_than_or_equal_to(value)
420
- prepare_submatcher(comparison_matcher_for(value, :<=).for(@attribute))
429
+ prepare_submatcher(comparison_matcher_for(value, :<=).for(attribute))
421
430
  self
422
431
  end
423
432
 
424
433
  def is_other_than(value)
425
- prepare_submatcher(comparison_matcher_for(value, :!=).for(@attribute))
426
- self
427
- end
428
-
429
- def with_message(message)
430
- @expects_custom_validation_message = true
431
- @expected_message = message
434
+ prepare_submatcher(comparison_matcher_for(value, :!=).for(attribute))
432
435
  self
433
436
  end
434
437
 
435
- def expects_custom_validation_message?
436
- @expects_custom_validation_message
437
- end
438
-
439
- def on(context)
440
- @context = context
438
+ def is_in(range)
439
+ prepare_submatcher(
440
+ NumericalityMatchers::RangeMatcher.new(self, attribute, range),
441
+ )
441
442
  self
442
443
  end
443
444
 
@@ -452,20 +453,18 @@ module Shoulda
452
453
  end
453
454
 
454
455
  def simple_description
455
- 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)
456
459
 
457
- description << "validate that :#{@attribute} looks like "
458
- description << Shoulda::Matchers::Util.a_or_an(full_allowed_type)
460
+ if range_description.present?
461
+ description << " #{range_description}"
462
+ end
459
463
 
460
- if comparison_descriptions.present?
461
- description << " #{comparison_descriptions}"
464
+ if comparison_descriptions.present?
465
+ description << " #{comparison_descriptions}"
466
+ end
462
467
  end
463
-
464
- description
465
- end
466
-
467
- def description
468
- ValidationMatcher::BuildDescription.call(self, simple_description)
469
468
  end
470
469
 
471
470
  def failure_message
@@ -494,44 +493,29 @@ module Shoulda
494
493
  @subject = subject
495
494
  @number_of_submatchers = @submatchers.size
496
495
 
497
- add_disallow_value_matcher
498
496
  qualify_submatchers
499
497
  end
500
498
 
501
- def overall_failure_message
502
- Shoulda::Matchers.word_wrap(
503
- "Expected #{model.name} to #{description}, but this could not "\
504
- 'be proved.',
505
- )
506
- end
507
-
508
- def overall_failure_message_when_negated
509
- Shoulda::Matchers.word_wrap(
510
- "Expected #{model.name} not to #{description}, but this could not "\
511
- 'be proved.',
512
- )
513
- end
514
-
515
499
  def attribute_is_active_record_column?
516
- columns_hash.key?(@attribute.to_s)
500
+ columns_hash.key?(attribute.to_s)
517
501
  end
518
502
 
519
503
  def column_type
520
- columns_hash[@attribute.to_s].type
504
+ columns_hash[attribute.to_s].type
521
505
  end
522
506
 
523
507
  def columns_hash
524
- if @subject.class.respond_to?(:columns_hash)
525
- @subject.class.columns_hash
508
+ if subject.class.respond_to?(:columns_hash)
509
+ subject.class.columns_hash
526
510
  else
527
511
  {}
528
512
  end
529
513
  end
530
514
 
531
- def add_disallow_value_matcher
515
+ def add_disallow_non_numeric_value_matcher
532
516
  disallow_value_matcher = DisallowValueMatcher.
533
517
  new(non_numeric_value).
534
- for(@attribute).
518
+ for(attribute).
535
519
  with_message(:not_a_number)
536
520
 
537
521
  add_submatcher(disallow_value_matcher)
@@ -543,9 +527,9 @@ module Shoulda
543
527
  end
544
528
 
545
529
  def comparison_matcher_for(value, operator)
546
- NumericalityMatchers::ComparisonMatcher.
530
+ ComparisonMatcher.
547
531
  new(self, value, operator).
548
- for(@attribute)
532
+ for(attribute)
549
533
  end
550
534
 
551
535
  def add_submatcher(submatcher)
@@ -577,8 +561,8 @@ module Shoulda
577
561
  submatcher.with_message(@expected_message)
578
562
  end
579
563
 
580
- if @context
581
- submatcher.on(@context)
564
+ if context
565
+ submatcher.on(context)
582
566
  end
583
567
 
584
568
  submatcher.ignoring_interference_by_writer(
@@ -596,23 +580,25 @@ module Shoulda
596
580
  end
597
581
 
598
582
  def has_been_qualified?
599
- @submatchers.any? do |submatcher|
600
- Shoulda::Matchers::RailsShim.parent_of(submatcher.class) ==
601
- NumericalityMatchers
602
- 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)
603
589
  end
604
590
 
605
591
  def first_submatcher_that_fails_to_match
606
592
  @_first_submatcher_that_fails_to_match ||=
607
593
  @submatchers.detect do |submatcher|
608
- !submatcher.matches?(@subject)
594
+ !submatcher.matches?(subject)
609
595
  end
610
596
  end
611
597
 
612
598
  def first_submatcher_that_fails_to_not_match
613
599
  @_first_submatcher_that_fails_to_not_match ||=
614
600
  @submatchers.detect do |submatcher|
615
- !submatcher.does_not_match?(@subject)
601
+ !submatcher.does_not_match?(subject)
616
602
  end
617
603
  end
618
604
 
@@ -673,8 +659,12 @@ module Shoulda
673
659
  end
674
660
  end
675
661
 
676
- def model
677
- @subject.class
662
+ def range_description
663
+ range_submatcher = @submatchers.detect do |submatcher|
664
+ submatcher.respond_to? :range_description
665
+ end
666
+
667
+ range_submatcher&.range_description
678
668
  end
679
669
 
680
670
  def non_numeric_value
@@ -78,6 +78,27 @@ module Shoulda
78
78
  # should validate_presence_of(:nickname).allow_nil
79
79
  # end
80
80
  #
81
+ # #### allow_blank
82
+ #
83
+ # Use `allow_blank` to assert that the attribute allows blank.
84
+ #
85
+ # class Robot
86
+ # include ActiveModel::Model
87
+ # attr_accessor :nickname
88
+ #
89
+ # validates_presence_of :nickname, allow_blank: true
90
+ # end
91
+ #
92
+ # # RSpec
93
+ # RSpec.describe Robot, type: :model do
94
+ # it { should validate_presence_of(:nickname).allow_blank }
95
+ # end
96
+ #
97
+ # # Minitest (Shoulda)
98
+ # class RobotTest < ActiveSupport::TestCase
99
+ # should validate_presence_of(:nickname).allow_blank
100
+ # end
101
+ #
81
102
  # ##### on
82
103
  #
83
104
  # Use `on` if your validation applies only under a certain context.
@@ -133,6 +154,7 @@ module Shoulda
133
154
  # @private
134
155
  class ValidatePresenceOfMatcher < ValidationMatcher
135
156
  include Qualifiers::AllowNil
157
+ include Qualifiers::AllowBlank
136
158
 
137
159
  def initialize(attribute)
138
160
  super
@@ -144,7 +166,8 @@ module Shoulda
144
166
 
145
167
  possibly_ignore_interference_by_writer
146
168
 
147
- if secure_password_being_validated?
169
+ if secure_password_being_validated? &&
170
+ Shoulda::Matchers::RailsShim.active_model_lt_7?
148
171
  ignore_interference_by_writer.default_to(when: :blank?)
149
172
 
150
173
  disallowed_values.all? do |value|
@@ -152,6 +175,7 @@ module Shoulda
152
175
  end
153
176
  else
154
177
  (!expects_to_allow_nil? || allows_value_of(nil)) &&
178
+ (!expects_to_allow_blank? || allows_value_of('')) &&
155
179
  disallowed_values.all? do |value|
156
180
  disallows_original_or_typecast_value?(value)
157
181
  end
@@ -171,6 +195,7 @@ module Shoulda
171
195
  end
172
196
  else
173
197
  (expects_to_allow_nil? && disallows_value_of(nil)) ||
198
+ (expects_to_allow_blank? && disallows_value_of('')) ||
174
199
  disallowed_values.any? do |value|
175
200
  allows_original_or_typecast_value?(value)
176
201
  end
@@ -208,7 +233,7 @@ validation for you? Instead of using `validate_presence_of`, try
208
233
  end
209
234
 
210
235
  def possibly_ignore_interference_by_writer
211
- if secure_password_being_validated?
236
+ if secure_password_being_validated? && RailsShim.active_model_lt_7?
212
237
  ignore_interference_by_writer.default_to(when: :blank?)
213
238
  end
214
239
  end
@@ -241,11 +266,11 @@ validation for you? Instead of using `validate_presence_of`, try
241
266
  else
242
267
  values = []
243
268
 
244
- if attribute_accepts_string_values?
269
+ if attribute_accepts_string_values? && !expects_to_allow_blank?
245
270
  values << ''
246
271
  end
247
272
 
248
- if !expects_to_allow_nil?
273
+ if !expects_to_allow_nil? && !expects_to_allow_blank?
249
274
  values << nil
250
275
  end
251
276
 
@@ -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,11 +21,14 @@ 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'
30
+ require 'shoulda/matchers/active_model/numericality_matchers/range_matcher'
31
+ require 'shoulda/matchers/active_model/numericality_matchers/submatchers'
29
32
  require 'shoulda/matchers/active_model/errors'
30
33
  require 'shoulda/matchers/active_model/have_secure_password_matcher'
31
34