shoulda-matchers 5.2.0 → 6.0.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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +22 -7
  4. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +1 -1
  5. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +9 -0
  6. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +162 -0
  7. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +3 -5
  8. data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
  9. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +58 -0
  10. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +2 -1
  11. data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +534 -0
  12. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +5 -5
  13. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +9 -6
  14. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +64 -9
  15. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +72 -80
  16. data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
  17. data/lib/shoulda/matchers/active_model/validator.rb +4 -0
  18. data/lib/shoulda/matchers/active_model.rb +4 -1
  19. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +0 -8
  20. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
  21. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +14 -3
  22. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
  23. data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  24. data/lib/shoulda/matchers/active_record.rb +1 -0
  25. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +1 -1
  26. data/lib/shoulda/matchers/rails_shim.rb +10 -8
  27. data/lib/shoulda/matchers/util/word_wrap.rb +2 -2
  28. data/lib/shoulda/matchers/util.rb +1 -1
  29. data/lib/shoulda/matchers/version.rb +1 -1
  30. data/lib/shoulda/matchers.rb +2 -2
  31. data/shoulda-matchers.gemspec +1 -1
  32. metadata +12 -8
  33. 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
@@ -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
 
@@ -454,9 +455,13 @@ module Shoulda
454
455
  def simple_description
455
456
  description = ''
456
457
 
457
- description << "validate that :#{@attribute} looks like "
458
+ description << "validate that :#{attribute} looks like "
458
459
  description << Shoulda::Matchers::Util.a_or_an(full_allowed_type)
459
460
 
461
+ if range_description.present?
462
+ description << " #{range_description}"
463
+ end
464
+
460
465
  if comparison_descriptions.present?
461
466
  description << " #{comparison_descriptions}"
462
467
  end
@@ -464,10 +469,6 @@ module Shoulda
464
469
  description
465
470
  end
466
471
 
467
- def description
468
- ValidationMatcher::BuildDescription.call(self, simple_description)
469
- end
470
-
471
472
  def failure_message
472
473
  overall_failure_message.dup.tap do |message|
473
474
  message << "\n"
@@ -494,44 +495,29 @@ module Shoulda
494
495
  @subject = subject
495
496
  @number_of_submatchers = @submatchers.size
496
497
 
497
- add_disallow_value_matcher
498
498
  qualify_submatchers
499
499
  end
500
500
 
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
501
  def attribute_is_active_record_column?
516
- columns_hash.key?(@attribute.to_s)
502
+ columns_hash.key?(attribute.to_s)
517
503
  end
518
504
 
519
505
  def column_type
520
- columns_hash[@attribute.to_s].type
506
+ columns_hash[attribute.to_s].type
521
507
  end
522
508
 
523
509
  def columns_hash
524
- if @subject.class.respond_to?(:columns_hash)
525
- @subject.class.columns_hash
510
+ if subject.class.respond_to?(:columns_hash)
511
+ subject.class.columns_hash
526
512
  else
527
513
  {}
528
514
  end
529
515
  end
530
516
 
531
- def add_disallow_value_matcher
517
+ def add_disallow_non_numeric_value_matcher
532
518
  disallow_value_matcher = DisallowValueMatcher.
533
519
  new(non_numeric_value).
534
- for(@attribute).
520
+ for(attribute).
535
521
  with_message(:not_a_number)
536
522
 
537
523
  add_submatcher(disallow_value_matcher)
@@ -543,9 +529,9 @@ module Shoulda
543
529
  end
544
530
 
545
531
  def comparison_matcher_for(value, operator)
546
- NumericalityMatchers::ComparisonMatcher.
532
+ ComparisonMatcher.
547
533
  new(self, value, operator).
548
- for(@attribute)
534
+ for(attribute)
549
535
  end
550
536
 
551
537
  def add_submatcher(submatcher)
@@ -577,8 +563,8 @@ module Shoulda
577
563
  submatcher.with_message(@expected_message)
578
564
  end
579
565
 
580
- if @context
581
- submatcher.on(@context)
566
+ if context
567
+ submatcher.on(context)
582
568
  end
583
569
 
584
570
  submatcher.ignoring_interference_by_writer(
@@ -596,23 +582,25 @@ module Shoulda
596
582
  end
597
583
 
598
584
  def has_been_qualified?
599
- @submatchers.any? do |submatcher|
600
- Shoulda::Matchers::RailsShim.parent_of(submatcher.class) ==
601
- NumericalityMatchers
602
- end
585
+ @submatchers.any? { |submatcher| submatcher_qualified?(submatcher) }
586
+ end
587
+
588
+ def submatcher_qualified?(submatcher)
589
+ Shoulda::Matchers::RailsShim.parent_of(submatcher.class) ==
590
+ NumericalityMatchers || submatcher.instance_of?(ComparisonMatcher)
603
591
  end
604
592
 
605
593
  def first_submatcher_that_fails_to_match
606
594
  @_first_submatcher_that_fails_to_match ||=
607
595
  @submatchers.detect do |submatcher|
608
- !submatcher.matches?(@subject)
596
+ !submatcher.matches?(subject)
609
597
  end
610
598
  end
611
599
 
612
600
  def first_submatcher_that_fails_to_not_match
613
601
  @_first_submatcher_that_fails_to_not_match ||=
614
602
  @submatchers.detect do |submatcher|
615
- !submatcher.does_not_match?(@subject)
603
+ !submatcher.does_not_match?(subject)
616
604
  end
617
605
  end
618
606
 
@@ -673,8 +661,12 @@ module Shoulda
673
661
  end
674
662
  end
675
663
 
676
- def model
677
- @subject.class
664
+ def range_description
665
+ range_submatcher = @submatchers.detect do |submatcher|
666
+ submatcher.respond_to? :range_description
667
+ end
668
+
669
+ range_submatcher&.range_description
678
670
  end
679
671
 
680
672
  def non_numeric_value
@@ -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
 
@@ -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
@@ -48,6 +48,30 @@ module Shoulda
48
48
  # should have_db_column(:camera_aperture).of_type(:decimal)
49
49
  # end
50
50
  #
51
+ # ##### of_sql_type
52
+ #
53
+ # Use `of_sql_type` to assert that a column is defined as a certain sql_type.
54
+ #
55
+ # class CreatePhones < ActiveRecord::Migration
56
+ # def change
57
+ # create_table :phones do |t|
58
+ # t.string :camera_aperture, limit: 36
59
+ # end
60
+ # end
61
+ # end
62
+ #
63
+ # # RSpec
64
+ # RSpec.describe Phone, type: :model do
65
+ # it do
66
+ # should have_db_column(:camera_aperture).of_sql_type('varchar(36)')
67
+ # end
68
+ # end
69
+ #
70
+ # # Minitest (Shoulda)
71
+ # class PhoneTest < ActiveSupport::TestCase
72
+ # should have_db_column(:camera_aperture).of_sql_type('varchar(36)')
73
+ # end
74
+ #
51
75
  # ##### with_options
52
76
  #
53
77
  # Use `with_options` to assert that a column has been defined with
@@ -96,6 +120,11 @@ module Shoulda
96
120
  self
97
121
  end
98
122
 
123
+ def of_sql_type(sql_column_type)
124
+ @options[:sql_column_type] = sql_column_type
125
+ self
126
+ end
127
+
99
128
  def with_options(opts = {})
100
129
  validate_options(opts)
101
130
  OPTIONS.each do |attribute|
@@ -110,6 +139,7 @@ module Shoulda
110
139
  @subject = subject
111
140
  column_exists? &&
112
141
  correct_column_type? &&
142
+ correct_sql_column_type? &&
113
143
  correct_precision? &&
114
144
  correct_limit? &&
115
145
  correct_default? &&
@@ -129,12 +159,9 @@ module Shoulda
129
159
 
130
160
  def description
131
161
  desc = "have db column named #{@column}"
132
- if @options.key?(:column_type)
133
- desc << " of type #{@options[:column_type]}"
134
- end
135
- if @options.key?(:precision)
136
- desc << " of precision #{@options[:precision]}"
137
- end
162
+ desc << " of type #{@options[:column_type]}" if @options.key?(:column_type)
163
+ desc << " of sql_type #{@options[:sql_column_type]}" if @options.key?(:sql_column_type)
164
+ desc << " of precision #{@options[:precision]}" if @options.key?(:precision)
138
165
  desc << " of limit #{@options[:limit]}" if @options.key?(:limit)
139
166
  desc << " of default #{@options[:default]}" if @options.key?(:default)
140
167
  desc << " of null #{@options[:null]}" if @options.key?(:null)
@@ -178,6 +205,19 @@ module Shoulda
178
205
  end
179
206
  end
180
207
 
208
+ def correct_sql_column_type?
209
+ return true unless @options.key?(:sql_column_type)
210
+
211
+ if matched_column.sql_type.to_s == @options[:sql_column_type].to_s
212
+ true
213
+ else
214
+ @missing =
215
+ "#{model_class} has a db column named #{@column} " <<
216
+ "of sql type #{matched_column.sql_type}, not #{@options[:sql_column_type]}."
217
+ false
218
+ end
219
+ end
220
+
181
221
  def correct_precision?
182
222
  return true unless @options.key?(:precision)
183
223
 
@@ -197,17 +197,28 @@ module Shoulda
197
197
  def matched_index
198
198
  @_matched_index ||=
199
199
  if expected_columns.one?
200
- actual_indexes.detect do |index|
200
+ sorted_indexes.detect do |index|
201
201
  Array.wrap(index.columns) == expected_columns
202
202
  end
203
203
  else
204
- actual_indexes.detect do |index|
204
+ sorted_indexes.detect do |index|
205
205
  index.columns == expected_columns
206
206
  end
207
207
  end
208
208
  end
209
209
 
210
- def actual_indexes
210
+ def sorted_indexes
211
+ if qualifiers.include?(:unique)
212
+ # return indexes with unique matching the qualifier first
213
+ unsorted_indexes.sort_by do |index|
214
+ index.unique == qualifiers[:unique] ? 0 : 1
215
+ end
216
+ else
217
+ unsorted_indexes
218
+ end
219
+ end
220
+
221
+ def unsorted_indexes
211
222
  model.connection.indexes(table_name)
212
223
  end
213
224
 
@@ -2,7 +2,7 @@ module Shoulda
2
2
  module Matchers
3
3
  module ActiveRecord
4
4
  # The `have_implicit_order_column` matcher tests that the model has `implicit_order_column`
5
- # assigned to one of the table columns. (Rails 6+ only)
5
+ # assigned to one of the table columns.
6
6
  #
7
7
  # class Product < ApplicationRecord
8
8
  # self.implicit_order_column = :created_at
@@ -20,10 +20,8 @@ module Shoulda
20
20
  #
21
21
  # @return [HaveImplicitOrderColumnMatcher]
22
22
  #
23
- if RailsShim.active_record_gte_6?
24
- def have_implicit_order_column(column_name)
25
- HaveImplicitOrderColumnMatcher.new(column_name)
26
- end
23
+ def have_implicit_order_column(column_name)
24
+ HaveImplicitOrderColumnMatcher.new(column_name)
27
25
  end
28
26
 
29
27
  # @private