shoulda-matchers 5.0.0 → 5.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 319b0cb3944a37309b11169bd71ffddde2d6af35b262795e7c16466d6d2c497c
4
- data.tar.gz: 22ac49d578766e24a18826bcde86443a09afa41c1cfc9ee7932f94f214623a36
3
+ metadata.gz: d083d22f49f99bf60d6c6bdfed7ea08ea1f838a90b4aed439d84108ebc814af9
4
+ data.tar.gz: 90cc8440d8c63da1c338306b3d9f129cc9af8dc1a80277d9e7f5b233f2bbe115
5
5
  SHA512:
6
- metadata.gz: 05a067ef5aa288661855f6257a55ce887bdf19f6684cd965c145669815a3d432a236cd7fb88e21647d0251e4e971b6c45b03af04def19b5cefbef26279276d81
7
- data.tar.gz: f06e9c331d39d82fd450332cb0b81e108852c7fac2a28927ada2fbccc11b31226206023f69eb6ecccd7dceaaca4c9b3410b818ed86babd3f7f37bb4c778013df
6
+ metadata.gz: '09b1dcf7e19179b50992d34cf73a86e72727cf27a1db911f2adb98fe291dc4d3d711da962d3efdbec60d2b1f280dcecd441e725a7e6a701a152ff60fdd8e340c'
7
+ data.tar.gz: 8ba54626246e133cfd86b84aba3d3b05487166d6359b2ba97256af242a057b5ff6cd40d5eae35bd35f619c9a9b0f39cc2c8b6508e299b44e2605e3cdf4f64078
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2021 Tammer Saleh and thoughtbot, inc.
1
+ Copyright (c) 2006-2022 Tammer Saleh and thoughtbot, inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
data/README.md CHANGED
@@ -19,7 +19,7 @@ complex, and error-prone.
19
19
 
20
20
  ## Quick links
21
21
 
22
- 📖 **[Read the documentation for the latest version][rubydocs].**
22
+ 📖 **[Read the documentation for the latest version][rubydocs].**
23
23
  📢 **[See what's changed in recent versions][changelog].**
24
24
 
25
25
  [rubydocs]: https://matchers.shoulda.io/docs
@@ -117,7 +117,7 @@ Otherwise, add `shoulda-matchers` to your Gemfile:
117
117
 
118
118
  ```ruby
119
119
  group :test do
120
- gem 'shoulda-matchers', '~> 4.0'
120
+ gem 'shoulda-matchers', '~> 5.0'
121
121
  end
122
122
  ```
123
123
 
@@ -492,7 +492,7 @@ Albuk][guialbuk].
492
492
 
493
493
  ## Copyright/License
494
494
 
495
- Shoulda Matchers is copyright © 2006-2021 Tammer Saleh and [thoughtbot,
495
+ Shoulda Matchers is copyright © 2006-2022 Tammer Saleh and [thoughtbot,
496
496
  inc][thoughtbot-website]. It is free and opensource software and may be
497
497
  redistributed under the terms specified in the [LICENSE](LICENSE) file.
498
498
 
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
1
3
  module Shoulda
2
4
  module Matchers
3
5
  module ActiveModel
@@ -31,6 +33,8 @@ module Shoulda
31
33
  },
32
34
  }.freeze
33
35
 
36
+ delegate :failure_message, :failure_message_when_negated, to: :submatchers
37
+
34
38
  def initialize(numericality_matcher, value, operator)
35
39
  super(nil)
36
40
  unless numericality_matcher.respond_to? :diff_to_compare
@@ -72,49 +76,24 @@ module Shoulda
72
76
 
73
77
  def matches?(subject)
74
78
  @subject = subject
75
- all_bounds_correct?
76
- end
77
-
78
- def failure_message
79
- last_failing_submatcher.failure_message
80
- end
81
-
82
- def failure_message_when_negated
83
- last_failing_submatcher.failure_message_when_negated
79
+ submatchers.matches?(subject)
84
80
  end
85
81
 
86
82
  def comparison_description
87
83
  "#{comparison_expectation} #{@value}"
88
84
  end
89
85
 
90
- private
91
-
92
- def all_bounds_correct?
93
- failing_submatchers.empty?
94
- end
95
-
96
- def failing_submatchers
97
- submatchers_and_results.
98
- select { |x| !x[:matched] }.
99
- map { |x| x[:matcher] }
100
- end
101
-
102
- def last_failing_submatcher
103
- failing_submatchers.last
104
- end
105
-
106
86
  def submatchers
107
- @_submatchers ||=
108
- comparison_combos.map do |diff, submatcher_method_name|
109
- matcher = __send__(submatcher_method_name, diff, nil)
110
- matcher.with_message(@message, values: { count: @value })
111
- matcher
112
- end
87
+ @_submatchers ||= NumericalityMatchers::Submatchers.new(build_submatchers)
113
88
  end
114
89
 
115
- def submatchers_and_results
116
- @_submatchers_and_results ||= submatchers.map do |matcher|
117
- { matcher: matcher, matched: matcher.matches?(@subject) }
90
+ private
91
+
92
+ def build_submatchers
93
+ comparison_combos.map do |diff, submatcher_method_name|
94
+ matcher = __send__(submatcher_method_name, diff, nil)
95
+ matcher.with_message(@message, values: { count: @value })
96
+ matcher
118
97
  end
119
98
  end
120
99
 
@@ -55,11 +55,9 @@ module Shoulda
55
55
  private
56
56
 
57
57
  def disallow_value_matcher
58
- @_disallow_value_matcher ||= begin
59
- DisallowValueMatcher.new(disallowed_value).tap do |matcher|
60
- matcher.for(attribute)
61
- wrap_disallow_value_matcher(matcher)
62
- end
58
+ @_disallow_value_matcher ||= DisallowValueMatcher.new(disallowed_value).tap do |matcher|
59
+ matcher.for(attribute)
60
+ wrap_disallow_value_matcher(matcher)
63
61
  end
64
62
  end
65
63
  end
@@ -0,0 +1,71 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Shoulda
4
+ module Matchers
5
+ module ActiveModel
6
+ module NumericalityMatchers
7
+ # @private
8
+ class RangeMatcher < ValidationMatcher
9
+ OPERATORS = [:>=, :<=].freeze
10
+
11
+ delegate :failure_message, to: :submatchers
12
+
13
+ def initialize(numericality_matcher, attribute, range)
14
+ super(attribute)
15
+ unless numericality_matcher.respond_to? :diff_to_compare
16
+ raise ArgumentError, 'numericality_matcher is invalid'
17
+ end
18
+
19
+ @numericality_matcher = numericality_matcher
20
+ @range = range
21
+ @attribute = attribute
22
+ end
23
+
24
+ def matches?(subject)
25
+ @subject = subject
26
+ submatchers.matches?(subject)
27
+ end
28
+
29
+ def simple_description
30
+ description = ''
31
+
32
+ if expects_strict?
33
+ description << ' strictly'
34
+ end
35
+
36
+ description +
37
+ "disallow :#{attribute} from being a number that is not " +
38
+ range_description
39
+ end
40
+
41
+ def range_description
42
+ "from #{Shoulda::Matchers::Util.inspect_range(@range)}"
43
+ end
44
+
45
+ def submatchers
46
+ @_submatchers ||= NumericalityMatchers::Submatchers.new(build_submatchers)
47
+ end
48
+
49
+ private
50
+
51
+ def build_submatchers
52
+ submatcher_combos.map do |value, operator|
53
+ build_comparison_submatcher(value, operator)
54
+ end
55
+ end
56
+
57
+ def submatcher_combos
58
+ @range.minmax.zip(OPERATORS)
59
+ end
60
+
61
+ def build_comparison_submatcher(value, operator)
62
+ NumericalityMatchers::ComparisonMatcher.new(@numericality_matcher, value, operator).
63
+ for(@attribute).
64
+ with_message(@message).
65
+ on(@context)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,43 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ module NumericalityMatchers
5
+ # @private
6
+ class Submatchers
7
+ def initialize(submatchers)
8
+ @submatchers = submatchers
9
+ end
10
+
11
+ def matches?(subject)
12
+ @subject = subject
13
+ failing_submatchers.empty?
14
+ end
15
+
16
+ def failure_message
17
+ last_failing_submatcher.failure_message
18
+ end
19
+
20
+ def failure_message_when_negated
21
+ last_failing_submatcher.failure_message_when_negated
22
+ end
23
+
24
+ def add(submatcher)
25
+ @submatchers << submatcher
26
+ end
27
+
28
+ def last_failing_submatcher
29
+ failing_submatchers.last
30
+ end
31
+
32
+ private
33
+
34
+ def failing_submatchers
35
+ @_failing_submatchers ||= @submatchers.reject do |submatcher|
36
+ submatcher.matches?(@subject)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,26 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ module Qualifiers
5
+ # @private
6
+ module AllowBlank
7
+ def initialize(*args)
8
+ super
9
+ @expects_to_allow_blank = false
10
+ end
11
+
12
+ def allow_blank
13
+ @expects_to_allow_blank = true
14
+ self
15
+ end
16
+
17
+ protected
18
+
19
+ def expects_to_allow_blank?
20
+ @expects_to_allow_blank
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -9,5 +9,6 @@ module Shoulda
9
9
  end
10
10
 
11
11
  require_relative 'qualifiers/allow_nil'
12
+ require_relative 'qualifiers/allow_blank'
12
13
  require_relative 'qualifiers/ignore_interference_by_writer'
13
14
  require_relative 'qualifiers/ignoring_interference_by_writer'
@@ -108,7 +108,9 @@ module Shoulda
108
108
  obj
109
109
  end
110
110
  elsif array_column?
111
- ['an arbitary value']
111
+ ['an arbitrary value']
112
+ elsif enum_column?
113
+ enum_values.first
112
114
  else
113
115
  case column_type
114
116
  when :integer, :float then 1
@@ -116,6 +118,7 @@ module Shoulda
116
118
  when :datetime, :time, :timestamp then Time.current
117
119
  when :date then Date.new
118
120
  when :binary then '0'
121
+ when :uuid then SecureRandom.uuid
119
122
  else 'an arbitrary value'
120
123
  end
121
124
  end
@@ -145,6 +148,15 @@ module Shoulda
145
148
  @subject.class.columns_hash[@attribute.to_s].respond_to?(:array) &&
146
149
  @subject.class.columns_hash[@attribute.to_s].array
147
150
  end
151
+
152
+ def enum_column?
153
+ @subject.class.respond_to?(:defined_enums) &&
154
+ @subject.class.defined_enums.key?(@attribute.to_s)
155
+ end
156
+
157
+ def enum_values
158
+ @subject.class.defined_enums[@attribute.to_s].values
159
+ end
148
160
  end
149
161
  end
150
162
  end
@@ -33,9 +33,9 @@ module Shoulda
33
33
  #
34
34
  # class Game
35
35
  # include ActiveModel::Model
36
- # attr_accessor :supported_os
36
+ # attr_accessor :floors_with_enemies
37
37
  #
38
- # validates_exclusion_of :supported_os, in: 5..8
38
+ # validates_exclusion_of :floors_with_enemies, in: 5..8
39
39
  # end
40
40
  #
41
41
  # # RSpec
@@ -3,6 +3,8 @@ require 'date'
3
3
 
4
4
  module Shoulda
5
5
  module Matchers
6
+ class ExampleClass; end
7
+
6
8
  module ActiveModel
7
9
  # The `validate_inclusion_of` matcher tests usage of the
8
10
  # `validates_inclusion_of` validation, asserting that an attribute can
@@ -43,12 +45,12 @@ module Shoulda
43
45
  #
44
46
  # # RSpec
45
47
  # RSpec.describe Issue, type: :model do
46
- # it { should validate_inclusion_of(:state).in_range(1..5) }
48
+ # it { should validate_inclusion_of(:priority).in_range(1..5) }
47
49
  # end
48
50
  #
49
51
  # # Minitest (Shoulda)
50
52
  # class IssueTest < ActiveSupport::TestCase
51
- # should validate_inclusion_of(:state).in_range(1..5)
53
+ # should validate_inclusion_of(:priority).in_range(1..5)
52
54
  # end
53
55
  #
54
56
  # #### Caveats
@@ -269,7 +271,7 @@ module Shoulda
269
271
  # @private
270
272
  class ValidateInclusionOfMatcher < ValidationMatcher
271
273
  BLANK_VALUES = ['', ' ', "\n", "\r", "\t", "\f"].freeze
272
- ARBITRARY_OUTSIDE_STRING = 'shoulda-matchers test string'.freeze
274
+ ARBITRARY_OUTSIDE_STRING = Shoulda::Matchers::ExampleClass.name
273
275
  ARBITRARY_OUTSIDE_INTEGER = 123456789
274
276
  ARBITRARY_OUTSIDE_DECIMAL = BigDecimal('0.123456789')
275
277
  ARBITRARY_OUTSIDE_DATE = Date.jd(9999999)
@@ -355,7 +357,7 @@ EOT
355
357
  description = "validate that :#{@attribute}"
356
358
 
357
359
  description <<
358
- if @array.many?
360
+ if @array.count > 1
359
361
  " is either #{inspected_array}"
360
362
  else
361
363
  " is #{inspected_array}"
@@ -239,7 +239,7 @@ module Shoulda
239
239
  #
240
240
  # @return [ValidateLengthOfMatcher]
241
241
  #
242
- # # ##### allow_blank
242
+ # ##### allow_blank
243
243
  #
244
244
  # Use `allow_blank` to assert that the attribute allows blank.
245
245
  #
@@ -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.
@@ -426,6 +453,13 @@ module Shoulda
426
453
  self
427
454
  end
428
455
 
456
+ def is_in(range)
457
+ prepare_submatcher(
458
+ NumericalityMatchers::RangeMatcher.new(self, @attribute, range),
459
+ )
460
+ self
461
+ end
462
+
429
463
  def with_message(message)
430
464
  @expects_custom_validation_message = true
431
465
  @expected_message = message
@@ -457,6 +491,10 @@ module Shoulda
457
491
  description << "validate that :#{@attribute} looks like "
458
492
  description << Shoulda::Matchers::Util.a_or_an(full_allowed_type)
459
493
 
494
+ if range_description.present?
495
+ description << " #{range_description}"
496
+ end
497
+
460
498
  if comparison_descriptions.present?
461
499
  description << " #{comparison_descriptions}"
462
500
  end
@@ -673,6 +711,14 @@ module Shoulda
673
711
  end
674
712
  end
675
713
 
714
+ def range_description
715
+ range_submatcher = @submatchers.detect do |submatcher|
716
+ submatcher.respond_to? :range_description
717
+ end
718
+
719
+ range_submatcher&.range_description
720
+ end
721
+
676
722
  def model
677
723
  @subject.class
678
724
  end
@@ -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
 
@@ -26,6 +26,8 @@ require 'shoulda/matchers/active_model/numericality_matchers/comparison_matcher'
26
26
  require 'shoulda/matchers/active_model/numericality_matchers/odd_number_matcher'
27
27
  require 'shoulda/matchers/active_model/numericality_matchers/even_number_matcher'
28
28
  require 'shoulda/matchers/active_model/numericality_matchers/only_integer_matcher'
29
+ require 'shoulda/matchers/active_model/numericality_matchers/range_matcher'
30
+ require 'shoulda/matchers/active_model/numericality_matchers/submatchers'
29
31
  require 'shoulda/matchers/active_model/errors'
30
32
  require 'shoulda/matchers/active_model/have_secure_password_matcher'
31
33
 
@@ -116,7 +116,7 @@ module Shoulda
116
116
  ## ##### with_prefix
117
117
  #
118
118
  # Use `with_prefix` to test that the enum is defined with a `_prefix`
119
- # option (Rails 5 only). Can take either a boolean or a symbol:
119
+ # option (Rails 6+ only). Can take either a boolean or a symbol:
120
120
  #
121
121
  # class Issue < ActiveRecord::Base
122
122
  # enum status: [:open, :closed], _prefix: :old
@@ -163,6 +163,30 @@ module Shoulda
163
163
  # with_suffix
164
164
  # end
165
165
  #
166
+ # ##### without_scopes
167
+ #
168
+ # Use `without_scopes` to test that the enum is defined with
169
+ # '_scopes: false' option (Rails 5 only). Can take either a boolean or a
170
+ # symbol:
171
+ #
172
+ # class Issue < ActiveRecord::Base
173
+ # enum status: [:open, :closed], _scopes: false
174
+ # end
175
+ #
176
+ # # RSpec
177
+ # RSpec.describe Issue, type: :model do
178
+ # it do
179
+ # should define_enum_for(:status).
180
+ # without_scopes
181
+ # end
182
+ # end
183
+ #
184
+ # # Minitest (Shoulda)
185
+ # class ProcessTest < ActiveSupport::TestCase
186
+ # should define_enum_for(:status).
187
+ # without_scopes
188
+ # end
189
+ #
166
190
  # @return [DefineEnumForMatcher]
167
191
  #
168
192
  def define_enum_for(attribute_name)
@@ -173,7 +197,7 @@ module Shoulda
173
197
  class DefineEnumForMatcher
174
198
  def initialize(attribute_name)
175
199
  @attribute_name = attribute_name
176
- @options = { expected_enum_values: [] }
200
+ @options = { expected_enum_values: [], scopes: true }
177
201
  end
178
202
 
179
203
  def description
@@ -226,13 +250,19 @@ module Shoulda
226
250
  self
227
251
  end
228
252
 
253
+ def without_scopes
254
+ options[:scopes] = false
255
+ self
256
+ end
257
+
229
258
  def matches?(subject)
230
259
  @record = subject
231
260
 
232
261
  enum_defined? &&
233
262
  enum_values_match? &&
234
263
  column_type_matches? &&
235
- enum_value_methods_exist?
264
+ enum_value_methods_exist? &&
265
+ scope_presence_matches?
236
266
  end
237
267
 
238
268
  def failure_message
@@ -294,6 +324,10 @@ module Shoulda
294
324
  expectation << "_#{expected_suffix}".inspect
295
325
  end
296
326
 
327
+ if exclude_scopes?
328
+ expectation << ' with no scopes'
329
+ end
330
+
297
331
  expectation
298
332
  else
299
333
  simple_description
@@ -387,37 +421,81 @@ module Shoulda
387
421
  end
388
422
 
389
423
  def enum_value_methods_exist?
390
- passed = expected_singleton_methods.all? do |method|
391
- model.singleton_methods.include?(method)
424
+ if instance_methods_exist?
425
+ true
426
+ else
427
+ message = missing_methods_message
428
+
429
+ message << " (we can't tell which)"
430
+
431
+ @failure_message_continuation = message
432
+
433
+ false
392
434
  end
435
+ end
393
436
 
394
- if passed
437
+ def scope_presence_matches?
438
+ if exclude_scopes?
439
+ if singleton_methods_exist?
440
+ message = "#{attribute_name.inspect} does map to these values "
441
+ message << 'but class scope methods were present'
442
+
443
+ @failure_message_continuation = message
444
+
445
+ false
446
+ else
447
+ true
448
+ end
449
+ elsif singleton_methods_exist?
395
450
  true
396
451
  else
397
- message = "#{attribute_name.inspect} does map to these "
398
- message << 'values, but the enum is '
452
+ if enum_defined?
453
+ message = 'But the class scope methods are not present'
454
+ else
455
+ message = missing_methods_message
399
456
 
400
- if expected_prefix
401
- if expected_suffix
402
- message << 'configured with either a different prefix or '
403
- message << 'suffix, or no prefix or suffix at all'
404
- else
405
- message << 'configured with either a different prefix or no '
406
- message << 'prefix at all'
407
- end
408
- elsif expected_suffix
409
- message << 'configured with either a different suffix or no '
410
- message << 'suffix at all'
457
+ message << 'or the class scope methods are not present'
458
+ message << " (we can't tell which)"
411
459
  end
412
460
 
413
- message << " (we can't tell which)"
414
-
415
461
  @failure_message_continuation = message
416
462
 
417
463
  false
418
464
  end
419
465
  end
420
466
 
467
+ def missing_methods_message
468
+ message = "#{attribute_name.inspect} does map to these "
469
+ message << 'values, but the enum is '
470
+
471
+ if expected_prefix
472
+ if expected_suffix
473
+ message << 'configured with either a different prefix or '
474
+ message << 'suffix, or no prefix or suffix at all'
475
+ else
476
+ message << 'configured with either a different prefix or no '
477
+ message << 'prefix at all'
478
+ end
479
+ elsif expected_suffix
480
+ message << 'configured with either a different suffix or no '
481
+ message << 'suffix at all'
482
+ else
483
+ ''
484
+ end
485
+ end
486
+
487
+ def singleton_methods_exist?
488
+ expected_singleton_methods.all? do |method|
489
+ model.singleton_methods.include?(method)
490
+ end
491
+ end
492
+
493
+ def instance_methods_exist?
494
+ expected_instance_methods.all? do |method|
495
+ record.methods.include?(method)
496
+ end
497
+ end
498
+
421
499
  def expected_singleton_methods
422
500
  expected_enum_value_names.map do |name|
423
501
  [expected_prefix, name, expected_suffix].
@@ -427,6 +505,18 @@ module Shoulda
427
505
  end
428
506
  end
429
507
 
508
+ def expected_instance_methods
509
+ methods = expected_enum_value_names.map do |name|
510
+ [expected_prefix, name, expected_suffix].
511
+ select(&:present?).
512
+ join('_')
513
+ end
514
+
515
+ methods.flat_map do |m|
516
+ ["#{m}?".to_sym, "#{m}!".to_sym]
517
+ end
518
+ end
519
+
430
520
  def expected_prefix
431
521
  if options.include?(:prefix)
432
522
  if options[:prefix] == true
@@ -447,6 +537,10 @@ module Shoulda
447
537
  end
448
538
  end
449
539
 
540
+ def exclude_scopes?
541
+ !options[:scopes]
542
+ end
543
+
450
544
  def to_hash(value)
451
545
  if value.is_a?(Array)
452
546
  value.each_with_index.inject({}) do |hash, (item, index)|
@@ -52,7 +52,7 @@ module Shoulda
52
52
  #
53
53
  # Use `with_options` to assert that a column has been defined with
54
54
  # certain options (`:precision`, `:limit`, `:default`, `:null`, `:scale`,
55
- # or `:primary`).
55
+ # `:primary` or `:array`).
56
56
  #
57
57
  # class CreatePhones < ActiveRecord::Migration
58
58
  # def change
@@ -84,7 +84,7 @@ module Shoulda
84
84
 
85
85
  # @private
86
86
  class HaveDbColumnMatcher
87
- OPTIONS = %i(precision limit default null scale primary).freeze
87
+ OPTIONS = %i(precision limit default null scale primary array).freeze
88
88
 
89
89
  def initialize(column)
90
90
  @column = column
@@ -115,7 +115,8 @@ module Shoulda
115
115
  correct_default? &&
116
116
  correct_null? &&
117
117
  correct_scale? &&
118
- correct_primary?
118
+ correct_primary? &&
119
+ correct_array?
119
120
  end
120
121
 
121
122
  def failure_message
@@ -258,6 +259,23 @@ module Shoulda
258
259
  end
259
260
  end
260
261
 
262
+ def correct_array?
263
+ return true unless @options.key?(:array)
264
+
265
+ if matched_column.array? == @options[:array]
266
+ true
267
+ else
268
+ @missing = "#{model_class} has a db column named #{@column} "
269
+ @missing <<
270
+ if @options[:primary]
271
+ 'that is not array, but should be'
272
+ else
273
+ 'that is array, but should not be'
274
+ end
275
+ false
276
+ end
277
+ end
278
+
261
279
  def matched_column
262
280
  @_matched_column ||= begin
263
281
  column = model_class.columns.detect do |each|
@@ -19,6 +19,20 @@ module Shoulda
19
19
  Gem::Version.new('0')
20
20
  end
21
21
 
22
+ def active_model_version
23
+ Gem::Version.new(::ActiveModel::VERSION::STRING)
24
+ rescue NameError
25
+ Gem::Version.new('0')
26
+ end
27
+
28
+ def active_model_st_6_1?
29
+ Gem::Requirement.new('< 6.1').satisfied_by?(active_model_version)
30
+ end
31
+
32
+ def active_model_lt_7?
33
+ Gem::Requirement.new('< 7').satisfied_by?(active_model_version)
34
+ end
35
+
22
36
  def generate_validation_message(
23
37
  record,
24
38
  attribute,
@@ -92,7 +92,7 @@ module Shoulda
92
92
  if line.list_item?
93
93
  combined_lines << line
94
94
  else
95
- combined_lines.last << (" #{line}").squeeze(' ')
95
+ combined_lines.last << " #{line}".squeeze(' ')
96
96
  end
97
97
 
98
98
  combined_lines
@@ -1,6 +1,6 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- VERSION = '5.0.0'.freeze
4
+ VERSION = '5.3.0'.freeze
5
5
  end
6
6
  end
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
26
26
 
27
27
  s.metadata = {
28
28
  'bug_tracker_uri' => 'https://github.com/thoughtbot/shoulda-matchers/issues',
29
- 'changelog_uri' => 'https://github.com/thoughtbot/shoulda-matchers/blob/master/CHANGELOG.md',
29
+ 'changelog_uri' => 'https://github.com/thoughtbot/shoulda-matchers/blob/main/CHANGELOG.md',
30
30
  'documentation_uri' => 'https://matchers.shoulda.io/docs',
31
31
  'homepage_uri' => 'https://matchers.shoulda.io',
32
32
  'source_code_uri' => 'https://github.com/thoughtbot/shoulda-matchers',
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoulda-matchers
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tammer Saleh
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2021-07-10 00:00:00.000000000 Z
17
+ date: 2022-12-16 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: activesupport
@@ -79,7 +79,10 @@ files:
79
79
  - lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb
80
80
  - lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb
81
81
  - lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb
82
+ - lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb
83
+ - lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb
82
84
  - lib/shoulda/matchers/active_model/qualifiers.rb
85
+ - lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb
83
86
  - lib/shoulda/matchers/active_model/qualifiers/allow_nil.rb
84
87
  - lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb
85
88
  - lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb
@@ -173,7 +176,7 @@ licenses:
173
176
  - MIT
174
177
  metadata:
175
178
  bug_tracker_uri: https://github.com/thoughtbot/shoulda-matchers/issues
176
- changelog_uri: https://github.com/thoughtbot/shoulda-matchers/blob/master/CHANGELOG.md
179
+ changelog_uri: https://github.com/thoughtbot/shoulda-matchers/blob/main/CHANGELOG.md
177
180
  documentation_uri: https://matchers.shoulda.io/docs
178
181
  homepage_uri: https://matchers.shoulda.io
179
182
  source_code_uri: https://github.com/thoughtbot/shoulda-matchers
@@ -192,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
195
  - !ruby/object:Gem::Version
193
196
  version: '0'
194
197
  requirements: []
195
- rubygems_version: 3.2.15
198
+ rubygems_version: 3.3.7
196
199
  signing_key:
197
200
  specification_version: 4
198
201
  summary: Simple one-liner tests for common Rails functionality