shoulda-matchers 4.1.1 → 4.4.1

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +163 -79
  3. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +2 -2
  4. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +5 -1
  5. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +1 -1
  6. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +2 -21
  7. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +27 -3
  8. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +32 -0
  9. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +6 -9
  10. data/lib/shoulda/matchers/active_model/validation_matcher.rb +25 -0
  11. data/lib/shoulda/matchers/active_record.rb +3 -0
  12. data/lib/shoulda/matchers/active_record/association_matcher.rb +23 -7
  13. data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +2 -2
  14. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +12 -6
  15. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +20 -3
  16. data/lib/shoulda/matchers/active_record/have_attached_matcher.rb +147 -0
  17. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +1 -1
  18. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +106 -0
  19. data/lib/shoulda/matchers/active_record/have_rich_text_matcher.rb +79 -0
  20. data/lib/shoulda/matchers/active_record/have_secure_token_matcher.rb +28 -9
  21. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +2 -6
  22. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +2 -2
  23. data/lib/shoulda/matchers/independent.rb +0 -1
  24. data/lib/shoulda/matchers/rails_shim.rb +42 -44
  25. data/lib/shoulda/matchers/util.rb +9 -2
  26. data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
  27. data/lib/shoulda/matchers/version.rb +1 -1
  28. metadata +9 -8
  29. data/MIT-LICENSE +0 -22
  30. data/lib/shoulda/matchers/independent/delegate_method_matcher/stubbed_target.rb +0 -37
@@ -269,7 +269,7 @@ module Shoulda
269
269
  end
270
270
 
271
271
  def type_cast_default
272
- Shoulda::Matchers::RailsShim.type_cast_default_for(model, self)
272
+ model.column_defaults[name]
273
273
  end
274
274
 
275
275
  def primary?
@@ -0,0 +1,106 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
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)
6
+ #
7
+ # class Product < ApplicationRecord
8
+ # self.implicit_order_column = :created_at
9
+ # end
10
+ #
11
+ # # RSpec
12
+ # RSpec.describe Product, type: :model do
13
+ # it { should have_implicit_order_column(:created_at) }
14
+ # end
15
+ #
16
+ # # Minitest (Shoulda)
17
+ # class ProductTest < ActiveSupport::TestCase
18
+ # should have_implicit_order_column(:created_at)
19
+ # end
20
+ #
21
+ # @return [HaveImplicitOrderColumnMatcher]
22
+ #
23
+ if RailsShim.active_record_gte_6?
24
+ def have_implicit_order_column(column_name)
25
+ HaveImplicitOrderColumnMatcher.new(column_name)
26
+ end
27
+ end
28
+
29
+ # @private
30
+ class HaveImplicitOrderColumnMatcher
31
+ attr_reader :failure_message
32
+
33
+ def initialize(column_name)
34
+ @column_name = column_name
35
+ end
36
+
37
+ def matches?(subject)
38
+ @subject = subject
39
+ check_column_exists!
40
+ check_implicit_order_column_matches!
41
+ true
42
+ rescue SecondaryCheckFailedError => error
43
+ @failure_message = Shoulda::Matchers.word_wrap(
44
+ "Expected #{model.name} to #{expectation}, " +
45
+ "but that could not be proved: #{error.message}."
46
+ )
47
+ false
48
+ rescue PrimaryCheckFailedError => error
49
+ @failure_message = Shoulda::Matchers.word_wrap(
50
+ "Expected #{model.name} to #{expectation}, but #{error.message}."
51
+ )
52
+ false
53
+ end
54
+
55
+ def failure_message_when_negated
56
+ Shoulda::Matchers.word_wrap(
57
+ "Expected #{model.name} not to #{expectation}, but it did."
58
+ )
59
+ end
60
+
61
+ def description
62
+ expectation
63
+ end
64
+
65
+ private
66
+
67
+ attr_reader :column_name, :subject
68
+
69
+ def check_column_exists!
70
+ matcher = HaveDbColumnMatcher.new(column_name)
71
+
72
+ if !matcher.matches?(@subject)
73
+ raise SecondaryCheckFailedError.new(
74
+ "The :#{model.table_name} table does not have a " +
75
+ ":#{column_name} column"
76
+ )
77
+ end
78
+ end
79
+
80
+ def check_implicit_order_column_matches!
81
+ if model.implicit_order_column.to_s != column_name.to_s
82
+ message =
83
+ if model.implicit_order_column.nil?
84
+ "implicit_order_column is not set"
85
+ else
86
+ "it is :#{model.implicit_order_column}"
87
+ end
88
+
89
+ raise PrimaryCheckFailedError.new(message)
90
+ end
91
+ end
92
+
93
+ def model
94
+ subject.class
95
+ end
96
+
97
+ def expectation
98
+ "have an implicit_order_column of :#{column_name}"
99
+ end
100
+
101
+ class SecondaryCheckFailedError < StandardError; end
102
+ class PrimaryCheckFailedError < StandardError; end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,79 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ # The `have_rich_text` matcher tests usage of the
5
+ # `has_rich_text` macro.
6
+ #
7
+ # #### Example
8
+ #
9
+ # class Post < ActiveRecord
10
+ # has_rich_text :content
11
+ # end
12
+ #
13
+ # # RSpec
14
+ # RSpec.describe Post, type: :model do
15
+ # it { is_expected.to have_rich_text(:content) }
16
+ # end
17
+ #
18
+ # # Minitest (Shoulda)
19
+ # class PostTest < ActiveSupport::TestCase
20
+ # should have_rich_text(:content)
21
+ # end
22
+ #
23
+ # @return [HaveRichText]
24
+ #
25
+ def have_rich_text(rich_text_attribute)
26
+ HaveRichText.new(rich_text_attribute)
27
+ end
28
+
29
+ # @private
30
+ class HaveRichText
31
+ def initialize(rich_text_attribute)
32
+ @rich_text_attribute = rich_text_attribute
33
+ end
34
+
35
+ def description
36
+ "have configured :#{rich_text_attribute} as a ActionText::RichText association"
37
+ end
38
+
39
+ def failure_message
40
+ "Expected #{subject.class} to #{error_description}"
41
+ end
42
+
43
+ def failure_message_when_negated
44
+ "Did not expect #{subject.class} to have ActionText::RichText :#{rich_text_attribute}"
45
+ end
46
+
47
+ def matches?(subject)
48
+ @subject = subject
49
+ @error = run_checks
50
+ @error.nil?
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :error, :rich_text_attribute, :subject
56
+
57
+ def run_checks
58
+ if !has_attribute?
59
+ ":#{rich_text_attribute} does not exist"
60
+ elsif !has_expected_action_text?
61
+ :default
62
+ end
63
+ end
64
+
65
+ def has_attribute?
66
+ @subject.respond_to?(rich_text_attribute.to_s)
67
+ end
68
+
69
+ def has_expected_action_text?
70
+ @subject.send(rich_text_attribute).class.name == 'ActionText::RichText'
71
+ end
72
+
73
+ def error_description
74
+ error == :default ? description : "#{description} but #{error}"
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -4,12 +4,7 @@ module Shoulda
4
4
  # The `have_secure_token` matcher tests usage of the
5
5
  # `has_secure_token` macro.
6
6
  #
7
- # #### Example
8
- #
9
7
  # class User < ActiveRecord
10
- # attr_accessor :token
11
- # attr_accessor :auth_token
12
- #
13
8
  # has_secure_token
14
9
  # has_secure_token :auth_token
15
10
  # end
@@ -26,14 +21,32 @@ module Shoulda
26
21
  # should have_secure_token(:auth_token)
27
22
  # end
28
23
  #
24
+ # #### Qualifiers
25
+ #
26
+ # ##### ignoring_check_for_db_index
27
+ #
28
+ # By default, this matcher tests that an index is defined on your token
29
+ # column. Use `ignoring_check_for_db_index` if this is not the case.
30
+ #
31
+ # class User < ActiveRecord
32
+ # has_secure_token :auth_token
33
+ # end
34
+ #
35
+ # # RSpec
36
+ # RSpec.describe User, type: :model do
37
+ # it { should have_secure_token(:auth_token).ignoring_check_for_db_index }
38
+ # end
39
+ #
40
+ # # Minitest (Shoulda)
41
+ # class UserTest < ActiveSupport::TestCase
42
+ # should have_secure_token(:auth_token).ignoring_check_for_db_index
43
+ # end
44
+ #
29
45
  # @return [HaveSecureToken]
30
46
  #
31
-
32
- # rubocop:disable Style/PredicateName
33
47
  def have_secure_token(token_attribute = :token)
34
48
  HaveSecureTokenMatcher.new(token_attribute)
35
49
  end
36
- # rubocop:enable Style/PredicateName
37
50
 
38
51
  # @private
39
52
  class HaveSecureTokenMatcher
@@ -41,6 +54,7 @@ module Shoulda
41
54
 
42
55
  def initialize(token_attribute)
43
56
  @token_attribute = token_attribute
57
+ @options = { ignore_check_for_db_index: false }
44
58
  end
45
59
 
46
60
  def description
@@ -65,6 +79,11 @@ module Shoulda
65
79
  @errors.empty?
66
80
  end
67
81
 
82
+ def ignoring_check_for_db_index
83
+ @options[:ignore_check_for_db_index] = true
84
+ self
85
+ end
86
+
68
87
  private
69
88
 
70
89
  def run_checks
@@ -75,7 +94,7 @@ module Shoulda
75
94
  if !has_expected_db_column?
76
95
  @errors << "missing correct column #{token_attribute}:string"
77
96
  end
78
- if !has_expected_db_index?
97
+ if !@options[:ignore_check_for_db_index] && !has_expected_db_index?
79
98
  @errors << "missing unique index for #{table_and_column}"
80
99
  end
81
100
  @errors
@@ -183,15 +183,11 @@ module Shoulda
183
183
  end
184
184
 
185
185
  def attribute_is_serialized?
186
- serialized_attributes.include?(@name)
186
+ !!serialization_coder
187
187
  end
188
188
 
189
189
  def serialization_coder
190
- serialized_attributes[@name]
191
- end
192
-
193
- def serialized_attributes
194
- Shoulda::Matchers::RailsShim.serialized_attributes_for(model)
190
+ RailsShim.attribute_serialization_coder_for(model, @name)
195
191
  end
196
192
 
197
193
  def model
@@ -396,7 +396,7 @@ module Shoulda
396
396
  end
397
397
 
398
398
  def validations
399
- model._validators[@attribute].select do |validator|
399
+ model.validators_on(@attribute).select do |validator|
400
400
  validator.is_a?(::ActiveRecord::Validations::UniquenessValidator)
401
401
  end
402
402
  end
@@ -820,7 +820,7 @@ module Shoulda
820
820
  elsif previous_value.respond_to?(:next)
821
821
  previous_value.next
822
822
  elsif previous_value.respond_to?(:to_datetime)
823
- previous_value.to_datetime.next
823
+ previous_value.to_datetime.in(60).next
824
824
  elsif boolean_value?(previous_value)
825
825
  !previous_value
826
826
  else
@@ -1,5 +1,4 @@
1
1
  require 'shoulda/matchers/independent/delegate_method_matcher'
2
- require 'shoulda/matchers/independent/delegate_method_matcher/stubbed_target'
3
2
  require 'shoulda/matchers/independent/delegate_method_matcher/target_not_defined_error'
4
3
 
5
4
  module Shoulda
@@ -3,10 +3,6 @@ module Shoulda
3
3
  # @private
4
4
  module RailsShim
5
5
  class << self
6
- def action_pack_gte_4_1?
7
- Gem::Requirement.new('>= 4.1').satisfied_by?(action_pack_version)
8
- end
9
-
10
6
  def action_pack_gte_5?
11
7
  Gem::Requirement.new('>= 5').satisfied_by?(action_pack_version)
12
8
  end
@@ -25,6 +21,10 @@ module Shoulda
25
21
  Gem::Requirement.new('>= 5').satisfied_by?(active_record_version)
26
22
  end
27
23
 
24
+ def active_record_gte_6?
25
+ Gem::Requirement.new('>= 6').satisfied_by?(active_record_version)
26
+ end
27
+
28
28
  def active_record_version
29
29
  Gem::Version.new(::ActiveRecord::VERSION::STRING)
30
30
  rescue NameError
@@ -69,30 +69,20 @@ module Shoulda
69
69
  end
70
70
 
71
71
  def serialized_attributes_for(model)
72
- if defined?(::ActiveRecord::Type::Serialized)
73
- # Rails 5+
74
- serialized_columns = model.columns.select do |column|
75
- model.type_for_attribute(column.name).is_a?(
76
- ::ActiveRecord::Type::Serialized,
77
- )
78
- end
79
-
80
- serialized_columns.inject({}) do |hash, column|
81
- hash[column.name.to_s] = model.type_for_attribute(column.name).coder
82
- hash
72
+ attribute_types_for(model).
73
+ inject({}) do |hash, (attribute_name, attribute_type)|
74
+ if attribute_type.is_a?(::ActiveRecord::Type::Serialized)
75
+ hash.merge(attribute_name => attribute_type.coder)
76
+ else
77
+ hash
78
+ end
83
79
  end
84
- else
85
- model.serialized_attributes
86
- end
80
+ rescue NotImplementedError
81
+ {}
87
82
  end
88
83
 
89
- def type_cast_default_for(model, column)
90
- if model.respond_to?(:column_defaults)
91
- # Rails 4.2
92
- model.column_defaults[column.name]
93
- else
94
- column.default
95
- end
84
+ def attribute_serialization_coder_for(model, attribute_name)
85
+ serialized_attributes_for(model)[attribute_name.to_s]
96
86
  end
97
87
 
98
88
  def tables_and_views(connection)
@@ -104,11 +94,7 @@ module Shoulda
104
94
  end
105
95
 
106
96
  def verb_for_update
107
- if action_pack_gte_4_1?
108
- :patch
109
- else
110
- :put
111
- end
97
+ :patch
112
98
  end
113
99
 
114
100
  def validation_message_key_for_association_required_option
@@ -156,14 +142,35 @@ module Shoulda
156
142
  nil
157
143
  end
158
144
 
145
+ def attribute_types_for(model)
146
+ if model.respond_to?(:attribute_types)
147
+ model.attribute_types
148
+ elsif model.respond_to?(:type_for_attribute)
149
+ model.columns.inject({}) do |hash, column|
150
+ key = column.name.to_s
151
+ value = model.type_for_attribute(column.name)
152
+ hash.merge(key => value)
153
+ end
154
+ else
155
+ raise NotImplementedError
156
+ end
157
+ end
158
+
159
159
  def attribute_type_for(model, attribute_name)
160
- if supports_full_attributes_api?(model)
161
- model.attribute_types[attribute_name.to_s]
160
+ attribute_types_for(model)[attribute_name.to_s]
161
+ rescue NotImplementedError
162
+ if model.respond_to?(:type_for_attribute)
163
+ model.type_for_attribute(attribute_name)
162
164
  else
163
- LegacyAttributeType.new(model, attribute_name)
165
+ FakeAttributeType.new(model, attribute_name)
164
166
  end
165
167
  end
166
168
 
169
+ def supports_full_attributes_api?(model)
170
+ defined?(::ActiveModel::Attributes) &&
171
+ model.respond_to?(:attribute_types)
172
+ end
173
+
167
174
  private
168
175
 
169
176
  def simply_generate_validation_message(
@@ -188,23 +195,14 @@ module Shoulda
188
195
  I18n.translate(primary_translation_key, translate_options)
189
196
  end
190
197
 
191
- def supports_full_attributes_api?(model)
192
- defined?(::ActiveModel::Attributes) &&
193
- model.respond_to?(:attribute_types)
194
- end
195
-
196
- class LegacyAttributeType
198
+ class FakeAttributeType
197
199
  def initialize(model, attribute_name)
198
200
  @model = model
199
201
  @attribute_name = attribute_name
200
202
  end
201
203
 
202
204
  def coder
203
- if model.respond_to?(:serialized_attributes)
204
- ActiveSupport::Deprecation.silence do
205
- model.serialized_attributes[attribute_name.to_s]
206
- end
207
- end
205
+ nil
208
206
  end
209
207
 
210
208
  private