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
@@ -0,0 +1,151 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ # The `normalize` matcher is used to ensure attribute normalizations
5
+ # are transforming attribute values as expected.
6
+ #
7
+ # Take this model for example:
8
+ #
9
+ # class User < ActiveRecord::Base
10
+ # normalizes :email, with: -> email { email.strip.downcase }
11
+ # end
12
+ #
13
+ # You can use `normalize` providing an input and defining the expected
14
+ # normalization output:
15
+ #
16
+ # # RSpec
17
+ # RSpec.describe User, type: :model do
18
+ # it do
19
+ # should normalize(:email).from(" ME@XYZ.COM\n").to("me@xyz.com")
20
+ # end
21
+ # end
22
+ #
23
+ # # Minitest (Shoulda)
24
+ # class User < ActiveSupport::TestCase
25
+ # should normalize(:email).from(" ME@XYZ.COM\n").to("me@xyz.com")
26
+ # end
27
+ #
28
+ # You can use `normalize` to test multiple attributes at once:
29
+ #
30
+ # class User < ActiveRecord::Base
31
+ # normalizes :email, :handle, with: -> value { value.strip.downcase }
32
+ # end
33
+ #
34
+ # # RSpec
35
+ # RSpec.describe User, type: :model do
36
+ # it do
37
+ # should normalize(:email, :handle).from(" Example\n").to("example")
38
+ # end
39
+ # end
40
+ #
41
+ # # Minitest (Shoulda)
42
+ # class User < ActiveSupport::TestCase
43
+ # should normalize(:email, :handle).from(" Example\n").to("example")
44
+ # end
45
+ #
46
+ # If the normalization accepts nil values with the `apply_to_nil` option,
47
+ # you just need to use `.from(nil).to("Your expected value here")`.
48
+ #
49
+ # class User < ActiveRecord::Base
50
+ # normalizes :name, with: -> name { name&.titleize || 'Untitled' },
51
+ # apply_to_nil: true
52
+ # end
53
+ #
54
+ # # RSpec
55
+ # RSpec.describe User, type: :model do
56
+ # it { should normalize(:name).from("jane doe").to("Jane Doe") }
57
+ # it { should normalize(:name).from(nil).to("Untitled") }
58
+ # end
59
+ #
60
+ # # Minitest (Shoulda)
61
+ # class User < ActiveSupport::TestCase
62
+ # should normalize(:name).from("jane doe").to("Jane Doe")
63
+ # should normalize(:name).from(nil).to("Untitled")
64
+ # end
65
+ #
66
+ # @return [NormalizeMatcher]
67
+ #
68
+ def normalize(*attributes)
69
+ if attributes.empty?
70
+ raise ArgumentError, 'need at least one attribute'
71
+ else
72
+ NormalizeMatcher.new(*attributes)
73
+ end
74
+ end
75
+
76
+ # @private
77
+ class NormalizeMatcher
78
+ attr_reader :attributes, :from_value, :to_value, :failure_message,
79
+ :failure_message_when_negated
80
+
81
+ def initialize(*attributes)
82
+ @attributes = attributes
83
+ end
84
+
85
+ def description
86
+ %(
87
+ normalize #{attributes.to_sentence(last_word_connector: ' and ')} from
88
+ ‹#{from_value.inspect}› to ‹#{to_value.inspect}›
89
+ ).squish
90
+ end
91
+
92
+ def from(value)
93
+ @from_value = value
94
+
95
+ self
96
+ end
97
+
98
+ def to(value)
99
+ @to_value = value
100
+
101
+ self
102
+ end
103
+
104
+ def matches?(subject)
105
+ attributes.all? { |attribute| attribute_matches?(subject, attribute) }
106
+ end
107
+
108
+ def does_not_match?(subject)
109
+ attributes.all? { |attribute| attribute_does_not_match?(subject, attribute) }
110
+ end
111
+
112
+ private
113
+
114
+ def attribute_matches?(subject, attribute)
115
+ return true if normalize_attribute?(subject, attribute)
116
+
117
+ @failure_message = build_failure_message(
118
+ attribute,
119
+ subject.class.normalize_value_for(attribute, from_value),
120
+ )
121
+ false
122
+ end
123
+
124
+ def attribute_does_not_match?(subject, attribute)
125
+ return true unless normalize_attribute?(subject, attribute)
126
+
127
+ @failure_message_when_negated = build_failure_message_when_negated(attribute)
128
+ false
129
+ end
130
+
131
+ def normalize_attribute?(subject, attribute)
132
+ subject.class.normalize_value_for(attribute, from_value) == to_value
133
+ end
134
+
135
+ def build_failure_message(attribute, attribute_value)
136
+ %(
137
+ Expected to normalize #{attribute.inspect} from ‹#{from_value.inspect}› to
138
+ ‹#{to_value.inspect}› but it was normalized to ‹#{attribute_value.inspect}›
139
+ ).squish
140
+ end
141
+
142
+ def build_failure_message_when_negated(attribute)
143
+ %(
144
+ Expected to not normalize #{attribute.inspect} from ‹#{from_value.inspect}› to
145
+ ‹#{to_value.inspect}› but it was normalized
146
+ ).squish
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -29,7 +29,7 @@ module Shoulda
29
29
  end
30
30
 
31
31
  def symlink_to(parent)
32
- namespace.set(name, parent.dup)
32
+ namespace.set(name, Class.new(parent))
33
33
  end
34
34
 
35
35
  def to_s
@@ -411,9 +411,29 @@ module Shoulda
411
411
  if scopes_match?
412
412
  true
413
413
  else
414
- @failure_reason = 'Expected the validation '
414
+ @failure_reason = String.new('Expected the validation ').tap do |failure_reason_string|
415
+ failure_reason_string <<
416
+ if expected_scopes.empty?
417
+ 'not to be scoped to anything, '
418
+ else
419
+ "to be scoped to #{inspected_expected_scopes}, "
420
+ end
421
+
422
+ if actual_sets_of_scopes.any?
423
+ failure_reason_string << 'but it was scoped to '
424
+ failure_reason_string << "#{inspected_actual_scopes} instead."
425
+ else
426
+ failure_reason_string << 'but it was not scoped to anything.'
427
+ end
428
+ end
415
429
 
416
- @failure_reason <<
430
+ false
431
+ end
432
+ end
433
+
434
+ def build_failure_reason
435
+ String.new('Expected the validation ').tap do |failure_reason_string|
436
+ failure_reason_string <<
417
437
  if expected_scopes.empty?
418
438
  'not to be scoped to anything, '
419
439
  else
@@ -421,27 +441,25 @@ module Shoulda
421
441
  end
422
442
 
423
443
  if actual_sets_of_scopes.any?
424
- @failure_reason << 'but it was scoped to '
425
- @failure_reason << "#{inspected_actual_scopes} instead."
444
+ failure_reason_string << 'but it was scoped to '
445
+ failure_reason_string << "#{inspected_actual_scopes} instead."
426
446
  else
427
- @failure_reason << 'but it was not scoped to anything.'
447
+ failure_reason_string << 'but it was not scoped to anything.'
428
448
  end
429
-
430
- false
431
449
  end
432
450
  end
433
451
 
434
452
  def does_not_match_scopes_configuration?
435
453
  if scopes_match?
436
- @failure_reason = 'Expected the validation '
437
-
438
- if expected_scopes.empty?
439
- @failure_reason << 'to be scoped to nothing, '
440
- @failure_reason << 'but it was scoped to '
441
- @failure_reason << "#{inspected_actual_scopes} instead."
442
- else
443
- @failure_reason << 'not to be scoped to '
444
- @failure_reason << inspected_expected_scopes
454
+ @failure_reason = String.new('Expected the validation ').tap do |failure_reason|
455
+ if expected_scopes.empty?
456
+ failure_reason << 'to be scoped to nothing, '
457
+ failure_reason << 'but it was scoped to '
458
+ failure_reason << "#{inspected_actual_scopes} instead."
459
+ else
460
+ failure_reason << 'not to be scoped to '
461
+ failure_reason << inspected_expected_scopes
462
+ end
445
463
  end
446
464
 
447
465
  false
@@ -618,20 +636,18 @@ module Shoulda
618
636
  else
619
637
  inspected_scopes = scopes_missing_on_model.map(&:inspect)
620
638
 
621
- reason = ''
622
-
623
- reason << inspected_scopes.to_sentence
624
-
625
- reason <<
626
- if inspected_scopes.many?
627
- ' do not seem to be attributes'
628
- else
629
- ' does not seem to be an attribute'
630
- end
639
+ @failure_reason = String.new.tap do |reason|
640
+ reason << inspected_scopes.to_sentence
631
641
 
632
- reason << " on #{model.name}."
642
+ reason <<
643
+ if inspected_scopes.many?
644
+ ' do not seem to be attributes'
645
+ else
646
+ ' does not seem to be an attribute'
647
+ end
633
648
 
634
- @failure_reason = reason
649
+ reason << " on #{model.name}."
650
+ end
635
651
 
636
652
  false
637
653
  end
@@ -643,20 +659,18 @@ module Shoulda
643
659
  else
644
660
  inspected_scopes = scopes_present_on_model.map(&:inspect)
645
661
 
646
- reason = ''
647
-
648
- reason << inspected_scopes.to_sentence
649
-
650
- reason <<
651
- if inspected_scopes.many?
652
- ' seem to be attributes'
653
- else
654
- ' seems to be an attribute'
655
- end
662
+ @failure_reason = String.new.tap do |reason|
663
+ reason << inspected_scopes.to_sentence
656
664
 
657
- reason << " on #{model.name}."
665
+ reason <<
666
+ if inspected_scopes.many?
667
+ ' seem to be attributes'
668
+ else
669
+ ' seems to be an attribute'
670
+ end
658
671
 
659
- @failure_reason = reason
672
+ reason << " on #{model.name}."
673
+ end
660
674
 
661
675
  false
662
676
  end
@@ -936,45 +950,43 @@ module Shoulda
936
950
  end
937
951
 
938
952
  def failure_message_preface # rubocop:disable Metrics/MethodLength
939
- prefix = ''
940
-
941
- if @existing_record_created
942
- prefix << "After taking the given #{model.name}"
953
+ String.new.tap do |prefix|
954
+ if @existing_record_created
955
+ prefix << "After taking the given #{model.name}"
956
+
957
+ if attribute_setter_for_existing_record
958
+ prefix << ', setting '
959
+ prefix << description_for_attribute_setter(
960
+ attribute_setter_for_existing_record,
961
+ )
962
+ else
963
+ prefix << ", whose :#{attribute} is "
964
+ prefix << "‹#{existing_value_read.inspect}›"
965
+ end
943
966
 
944
- if attribute_setter_for_existing_record
945
- prefix << ', setting '
967
+ prefix << ', and saving it as the existing record, then'
968
+ elsif attribute_setter_for_existing_record
969
+ prefix << "Given an existing #{model.name},"
970
+ prefix << ' after setting '
946
971
  prefix << description_for_attribute_setter(
947
972
  attribute_setter_for_existing_record,
948
973
  )
974
+ prefix << ', then'
949
975
  else
950
- prefix << ", whose :#{attribute} is "
951
- prefix << "‹#{existing_value_read.inspect}›"
976
+ prefix << "Given an existing #{model.name} whose :#{attribute}"
977
+ prefix << ' is '
978
+ prefix << Shoulda::Matchers::Util.inspect_value(
979
+ existing_value_read,
980
+ )
981
+ prefix << ', after'
952
982
  end
953
983
 
954
- prefix << ', and saving it as the existing record, then'
955
- elsif attribute_setter_for_existing_record
956
- prefix << "Given an existing #{model.name},"
957
- prefix << ' after setting '
958
- prefix << description_for_attribute_setter(
959
- attribute_setter_for_existing_record,
960
- )
961
- prefix << ', then'
962
- else
963
- prefix << "Given an existing #{model.name} whose :#{attribute}"
964
- prefix << ' is '
965
- prefix << Shoulda::Matchers::Util.inspect_value(
966
- existing_value_read,
967
- )
968
- prefix << ', after'
969
- end
970
-
971
- prefix << " making a new #{model.name} and setting "
984
+ prefix << " making a new #{model.name} and setting "
972
985
 
973
- prefix << descriptions_for_attribute_setters_for_new_record
986
+ prefix << descriptions_for_attribute_setters_for_new_record
974
987
 
975
- prefix << ", the matcher expected the new #{model.name} to be"
976
-
977
- prefix
988
+ prefix << ", the matcher expected the new #{model.name} to be"
989
+ end
978
990
  end
979
991
 
980
992
  def attribute_changed_value_message
@@ -24,6 +24,8 @@ require 'shoulda/matchers/active_record/define_enum_for_matcher'
24
24
  require 'shoulda/matchers/active_record/uniqueness'
25
25
  require 'shoulda/matchers/active_record/validate_uniqueness_of_matcher'
26
26
  require 'shoulda/matchers/active_record/have_attached_matcher'
27
+ require 'shoulda/matchers/active_record/normalize_matcher'
28
+ require 'shoulda/matchers/active_record/encrypt_matcher'
27
29
 
28
30
  module Shoulda
29
31
  module Matchers
@@ -18,15 +18,11 @@ module Shoulda
18
18
  end
19
19
 
20
20
  def activate
21
- doubles_by_method_name.each do |_method_name, double|
22
- double.activate
23
- end
21
+ doubles_by_method_name.each_value(&:activate)
24
22
  end
25
23
 
26
24
  def deactivate
27
- doubles_by_method_name.each do |_method_name, double|
28
- double.deactivate
29
- end
25
+ doubles_by_method_name.each_value(&:deactivate)
30
26
  end
31
27
 
32
28
  def calls_by_method_name
@@ -39,15 +39,11 @@ module Shoulda
39
39
  private
40
40
 
41
41
  def activate
42
- double_collections_by_class.each do |_klass, double_collection|
43
- double_collection.activate
44
- end
42
+ double_collections_by_class.each_value(&:activate)
45
43
  end
46
44
 
47
45
  def deactivate
48
- double_collections_by_class.each do |_klass, double_collection|
49
- double_collection.deactivate
50
- end
46
+ double_collections_by_class.each_value(&:deactivate)
51
47
  end
52
48
 
53
49
  def double_collections_by_class
@@ -400,7 +400,7 @@ module Shoulda
400
400
  false
401
401
  rescue NoMethodError => e
402
402
  if e.message =~
403
- /undefined method `#{delegate_method}' for nil:NilClass/
403
+ /undefined method `#{delegate_method}' for nil/
404
404
  false
405
405
  else
406
406
  raise e
@@ -440,22 +440,20 @@ module Shoulda
440
440
  end
441
441
 
442
442
  def formatted_calls_on_delegate_object
443
- string = ''
444
-
445
- if calls_on_delegate_object.any?
446
- string << "\n\n"
447
- calls_on_delegate_object.each_with_index do |call, i|
448
- name = call.method_name
449
- args = call.args.map(&:inspect).join(', ')
450
- string << "#{i + 1}) #{name}(#{args})\n"
443
+ String.new.tap do |string|
444
+ if calls_on_delegate_object.any?
445
+ string << "\n\n"
446
+ calls_on_delegate_object.each_with_index do |call, i|
447
+ name = call.method_name
448
+ args = call.args.map(&:inspect).join(', ')
449
+ string << "#{i + 1}) #{name}(#{args})\n"
450
+ end
451
+ else
452
+ string << ' (none)'
451
453
  end
452
- else
453
- string << ' (none)'
454
- end
455
454
 
456
- string.rstrip!
457
-
458
- string
455
+ string.rstrip!
456
+ end
459
457
  end
460
458
  end
461
459
  end
@@ -12,15 +12,17 @@ module Shoulda
12
12
  def integrate_with(test_framework)
13
13
  test_framework.include(matchers_module, type: :controller)
14
14
 
15
- include_into(::ActionController::TestCase, matchers_module) do
16
- def subject # rubocop:disable Lint/NestedMethodDefinition
17
- @controller
15
+ tap do |instance|
16
+ ActiveSupport.on_load(:action_controller_test_case, run_once: true) do
17
+ instance.include_into(::ActionController::TestCase, instance.matchers_module) do
18
+ def subject # rubocop:disable Lint/NestedMethodDefinition
19
+ @controller
20
+ end
21
+ end
18
22
  end
19
23
  end
20
24
  end
21
25
 
22
- private
23
-
24
26
  def matchers_module
25
27
  Shoulda::Matchers::ActionController
26
28
  end
@@ -12,11 +12,13 @@ module Shoulda
12
12
  def integrate_with(test_framework)
13
13
  test_framework.include(matchers_module, type: :routing)
14
14
 
15
- include_into(::ActionController::TestCase, matchers_module)
15
+ tap do |instance|
16
+ ActiveSupport.on_load(:action_controller_test_case, run_once: true) do
17
+ instance.include_into(::ActionController::TestCase, instance.matchers_module)
18
+ end
19
+ end
16
20
  end
17
21
 
18
- private
19
-
20
22
  def matchers_module
21
23
  Shoulda::Matchers::Routing
22
24
  end
@@ -1,7 +1,7 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- module RailsShim # rubocop:disable Metrics/ModuleLength
4
+ module RailsShim
5
5
  class << self
6
6
  def action_pack_version
7
7
  Gem::Version.new(::ActionPack::VERSION::STRING)
@@ -9,10 +9,6 @@ module Shoulda
9
9
  Gem::Version.new('0')
10
10
  end
11
11
 
12
- def active_record_gte_6?
13
- Gem::Requirement.new('>= 6').satisfied_by?(active_record_version)
14
- end
15
-
16
12
  def active_record_version
17
13
  Gem::Version.new(::ActiveRecord::VERSION::STRING)
18
14
  rescue NameError
@@ -60,9 +56,10 @@ module Shoulda
60
56
  end
61
57
 
62
58
  def serialized_attributes_for(model)
59
+ type_serialized_defined = Object.const_defined?('ActiveRecord::Type::Serialized')
63
60
  attribute_types_for(model).
64
61
  inject({}) do |hash, (attribute_name, attribute_type)|
65
- if attribute_type.is_a?(::ActiveRecord::Type::Serialized)
62
+ if type_serialized_defined && attribute_type.is_a?(::ActiveRecord::Type::Serialized)
66
63
  hash.merge(attribute_name => attribute_type.coder)
67
64
  else
68
65
  hash
@@ -146,6 +143,10 @@ module Shoulda
146
143
  model.respond_to?(:attribute_types)
147
144
  end
148
145
 
146
+ def validates_column_options?
147
+ Gem::Requirement.new('>= 7.1.0').satisfied_by?(active_record_version)
148
+ end
149
+
149
150
  private
150
151
 
151
152
  def simply_generate_validation_message(
@@ -172,6 +173,7 @@ module Shoulda
172
173
  I18n.translate(primary_translation_key, translate_options)
173
174
  end
174
175
 
176
+ # @private
175
177
  class FakeAttributeType
176
178
  def initialize(model, attribute_name)
177
179
  @model = model
@@ -41,7 +41,7 @@ module Shoulda
41
41
 
42
42
  # @private
43
43
  class Text < ::String
44
- LIST_ITEM_REGEXP = /\A((?:[a-z0-9]+(?:\)|\.)|\*) )/.freeze
44
+ LIST_ITEM_REGEXP = /\A((?:[a-z0-9]+(?:\)|\.)|\*) )/
45
45
 
46
46
  def indented?
47
47
  self =~ /\A +/
@@ -7,10 +7,9 @@ module Shoulda
7
7
  MAXIMUM_LENGTH_OF_VALUE_TO_DISPLAY = 500
8
8
 
9
9
  def self.deconstantize(path)
10
- if (
11
- defined?(ActiveSupport::Inflector) &&
12
- ActiveSupport::Inflector.respond_to?(:deconstantize)
13
- )
10
+ if defined?(ActiveSupport::Inflector) &&
11
+ ActiveSupport::Inflector.respond_to?(:deconstantize)
12
+
14
13
  ActiveSupport::Inflector.deconstantize(path)
15
14
  else
16
15
  path.to_s[0...(path.to_s.rindex('::') || 0)]
@@ -18,10 +17,9 @@ module Shoulda
18
17
  end
19
18
 
20
19
  def self.safe_constantize(camel_cased_word)
21
- if (
22
- defined?(ActiveSupport::Inflector) &&
23
- ActiveSupport::Inflector.respond_to?(:safe_constantize)
24
- )
20
+ if defined?(ActiveSupport::Inflector) &&
21
+ ActiveSupport::Inflector.respond_to?(:safe_constantize)
22
+
25
23
  ActiveSupport::Inflector.safe_constantize(camel_cased_word)
26
24
  else
27
25
  begin
@@ -68,21 +66,21 @@ module Shoulda
68
66
  end
69
67
 
70
68
  def self.inspect_range(range)
71
- "#{inspect_value(range.first)} to #{inspect_value(range.last)}"
69
+ "#{inspect_value(range.begin)} to #{inspect_value(range.end)}"
72
70
  end
73
71
 
74
72
  def self.inspect_hash(hash)
75
- output = '‹{'
76
-
77
- output << hash.map { |key, value|
78
- if key.is_a?(Symbol)
79
- "#{key}: #{value.inspect}"
80
- else
81
- "#{key.inspect} => #{value.inspect}"
82
- end
83
- }.join(', ')
73
+ String.new('‹{').tap do |output|
74
+ output << hash.map { |key, value|
75
+ if key.is_a?(Symbol)
76
+ "#{key}: #{value.inspect}"
77
+ else
78
+ "#{key.inspect} => #{value.inspect}"
79
+ end
80
+ }.join(', ')
84
81
 
85
- output << '}›'
82
+ output << '}›'
83
+ end
86
84
  end
87
85
 
88
86
  def self.dummy_value_for(column_type, array: false)
@@ -94,7 +92,7 @@ module Shoulda
94
92
  0
95
93
  when :date
96
94
  Date.new(2100, 1, 1)
97
- when :datetime, :timestamp
95
+ when :datetime, :timestamp, :timestamptz
98
96
  DateTime.new(2100, 1, 1)
99
97
  when :time
100
98
  Time.new(2000, 1, 1)
@@ -1,6 +1,6 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- VERSION = '5.3.0'.freeze
4
+ VERSION = '6.2.0'.freeze
5
5
  end
6
6
  end
@@ -14,8 +14,8 @@ require 'shoulda/matchers/active_model'
14
14
  require 'shoulda/matchers/active_record'
15
15
  require 'shoulda/matchers/routing'
16
16
 
17
- module Shoulda
18
- module Matchers
17
+ module Shoulda # :nodoc:
18
+ module Matchers # :nodoc:
19
19
  class << self
20
20
  # @private
21
21
  attr_accessor :assertion_exception_class
@@ -36,6 +36,6 @@ Gem::Specification.new do |s|
36
36
  'shoulda-matchers.gemspec']
37
37
  s.require_paths = ['lib']
38
38
 
39
- s.required_ruby_version = '>= 2.6.0'
39
+ s.required_ruby_version = '>= 3.0.5'
40
40
  s.add_dependency('activesupport', '>= 5.2.0')
41
41
  end