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
@@ -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