shoulda-matchers 4.5.1 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +15 -13
  4. data/lib/shoulda/matchers/action_controller/callback_matcher.rb +0 -87
  5. data/lib/shoulda/matchers/action_controller/flash_store.rb +2 -4
  6. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +3 -6
  7. data/lib/shoulda/matchers/active_model/helpers.rb +1 -1
  8. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +13 -34
  9. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +3 -5
  10. data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
  11. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +43 -0
  12. data/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
  13. data/lib/shoulda/matchers/active_model/qualifiers.rb +1 -0
  14. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +14 -2
  15. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +2 -2
  16. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +6 -4
  17. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +1 -1
  18. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +47 -1
  19. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +31 -6
  20. data/lib/shoulda/matchers/active_model/validator.rb +1 -6
  21. data/lib/shoulda/matchers/active_model.rb +2 -1
  22. data/lib/shoulda/matchers/active_record/association_matcher.rb +20 -22
  23. data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +1 -1
  24. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +1 -1
  25. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +125 -22
  26. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +21 -3
  27. data/lib/shoulda/matchers/rails_shim.rb +14 -39
  28. data/lib/shoulda/matchers/util/word_wrap.rb +2 -2
  29. data/lib/shoulda/matchers/version.rb +1 -1
  30. data/shoulda-matchers.gemspec +3 -3
  31. metadata +10 -8
  32. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +0 -161
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a62f760706ab97ba90edd181dbdf330ce6e8203c8ec3ec37a258022e202a53a6
4
- data.tar.gz: 68f333b57646a8a437b73ff43f617efa9231674fb92f68de73365eb1408932f0
3
+ metadata.gz: d083d22f49f99bf60d6c6bdfed7ea08ea1f838a90b4aed439d84108ebc814af9
4
+ data.tar.gz: 90cc8440d8c63da1c338306b3d9f129cc9af8dc1a80277d9e7f5b233f2bbe115
5
5
  SHA512:
6
- metadata.gz: ea01851ee8521ff08ab6b1caea192c161e34286e5fd361cbceb6b84404813fa952acb73c70481c3140b2c5116ffce8bb386bf1feea70754f827a3918292b1bfb
7
- data.tar.gz: 286ac5e78d534651889c5e3ed3843e1afa50d0fd77ed896c4ca3cf3622596cb7e1f47d2ae053e38100ce443dbfd80fbf1a8d099ecdfbbcf8d70912dbe3f42ad3
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
@@ -1,9 +1,9 @@
1
- # Shoulda Matchers [![Gem Version][version-badge]][rubygems] [![Build Status][travis-badge]][travis] [![Total Downloads][downloads-total]][rubygems] [![Downloads][downloads-badge]][rubygems]
1
+ # Shoulda Matchers [![Gem Version][version-badge]][rubygems] [![Build Status][github-actions-badge]][github-actions] [![Total Downloads][downloads-total]][rubygems] [![Downloads][downloads-badge]][rubygems]
2
2
 
3
3
  [version-badge]: https://img.shields.io/gem/v/shoulda-matchers.svg
4
4
  [rubygems]: https://rubygems.org/gems/shoulda-matchers
5
- [travis-badge]: https://img.shields.io/travis/thoughtbot/shoulda-matchers/master.svg
6
- [travis]: https://travis-ci.org/thoughtbot/shoulda-matchers
5
+ [github-actions-badge]: https://img.shields.io/github/workflow/status/thoughtbot/shoulda-matchers/Test
6
+ [github-actions]: https://github.com/thoughtbot/shoulda-matchers/actions
7
7
  [downloads-total]: https://img.shields.io/gem/dt/shoulda-matchers.svg
8
8
  [downloads-badge]: https://img.shields.io/gem/dtv/shoulda-matchers.svg
9
9
  [downloads-badge]: https://img.shields.io/gem/dtv/shoulda-matchers.svg
@@ -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
@@ -55,7 +55,7 @@ Start by including `shoulda-matchers` in your Gemfile:
55
55
 
56
56
  ```ruby
57
57
  group :test do
58
- gem 'shoulda-matchers', '~> 4.0'
58
+ gem 'shoulda-matchers', '~> 5.0'
59
59
  end
60
60
  ```
61
61
 
@@ -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
 
@@ -430,11 +430,11 @@ about any of them, make sure to [consult the documentation][rubydocs]!
430
430
  makes assertions on the `session` hash.
431
431
  * **[set_flash](lib/shoulda/matchers/action_controller/set_flash_matcher.rb)**
432
432
  makes assertions on the `flash` hash.
433
- * **[use_after_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L79)**
433
+ * **[use_after_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L29)**
434
434
  tests that an `after_action` callback is defined in your controller.
435
- * **[use_around_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L129)**
435
+ * **[use_around_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L75)**
436
436
  tests that an `around_action` callback is defined in your controller.
437
- * **[use_before_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L54)**
437
+ * **[use_before_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L4)**
438
438
  tests that a `before_action` callback is defined in your controller.
439
439
 
440
440
  ### Routing matchers
@@ -468,12 +468,14 @@ machine, understanding the codebase, and creating a good pull request.
468
468
 
469
469
  ## Compatibility
470
470
 
471
- Shoulda Matchers is [tested][travis] and supported against Ruby 2.4+, Rails
472
- 4.2+, RSpec 3.x, and Minitest 5.x.
471
+ Shoulda Matchers is tested and supported against Ruby 2.6+, Rails
472
+ 5.2+, RSpec 3.x, and Minitest 5.x.
473
473
 
474
- For Ruby < 2.4 and Rails < 4.1 compatibility, please use [v3.1.3][v3.1.3].
474
+ - For Ruby < 2.4 and Rails < 4.1 compatibility, please use [v3.1.3][v3.1.3].
475
+ - For Ruby < 3.0 and Rails < 6.1 compatibility, please use [v4.5.1][v4.5.1].
475
476
 
476
477
  [v3.1.3]: https://github.com/thoughtbot/shoulda-matchers/tree/v3.1.3
478
+ [v4.5.1]: https://github.com/thoughtbot/shoulda-matchers/tree/v4.5.1
477
479
 
478
480
  ## Versioning
479
481
 
@@ -490,7 +492,7 @@ Albuk][guialbuk].
490
492
 
491
493
  ## Copyright/License
492
494
 
493
- Shoulda Matchers is copyright © 2006-2021 Tammer Saleh and [thoughtbot,
495
+ Shoulda Matchers is copyright © 2006-2022 Tammer Saleh and [thoughtbot,
494
496
  inc][thoughtbot-website]. It is free and opensource software and may be
495
497
  redistributed under the terms specified in the [LICENSE](LICENSE) file.
496
498
 
@@ -1,64 +1,6 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  module ActionController
4
- # The `use_before_filter` matcher is used to test that a before_filter
5
- # callback is defined within your controller.
6
- #
7
- # class UsersController < ApplicationController
8
- # before_filter :authenticate_user!
9
- # end
10
- #
11
- # # RSpec
12
- # RSpec.describe UsersController, type: :controller do
13
- # it { should use_before_filter(:authenticate_user!) }
14
- # it { should_not use_before_filter(:prevent_ssl) }
15
- # end
16
- #
17
- # # Minitest (Shoulda)
18
- # class UsersControllerTest < ActionController::TestCase
19
- # should use_before_filter(:authenticate_user!)
20
- # should_not use_before_filter(:prevent_ssl)
21
- # end
22
- #
23
- # @note This method is only available when using shoulda-matchers under
24
- # Rails 4.x.
25
- # @return [CallbackMatcher]
26
- #
27
- if RailsShim.action_pack_lt_5?
28
- def use_before_filter(callback)
29
- CallbackMatcher.new(callback, :before, :filter)
30
- end
31
- end
32
-
33
- # The `use_after_filter` matcher is used to test that an after_filter
34
- # callback is defined within your controller.
35
- #
36
- # class IssuesController < ApplicationController
37
- # after_filter :log_activity
38
- # end
39
- #
40
- # # RSpec
41
- # RSpec.describe IssuesController, type: :controller do
42
- # it { should use_after_filter(:log_activity) }
43
- # it { should_not use_after_filter(:destroy_user) }
44
- # end
45
- #
46
- # # Minitest (Shoulda)
47
- # class IssuesControllerTest < ActionController::TestCase
48
- # should use_after_filter(:log_activity)
49
- # should_not use_after_filter(:destroy_user)
50
- # end
51
- #
52
- # @note This method is only available when using shoulda-matchers under
53
- # Rails 4.x.
54
- # @return [CallbackMatcher]
55
- #
56
- if RailsShim.action_pack_lt_5?
57
- def use_after_filter(callback)
58
- CallbackMatcher.new(callback, :after, :filter)
59
- end
60
- end
61
-
62
4
  # The `use_before_action` matcher is used to test that a before_action
63
5
  # callback is defined within your controller.
64
6
  #
@@ -109,35 +51,6 @@ module Shoulda
109
51
  CallbackMatcher.new(callback, :after, :action)
110
52
  end
111
53
 
112
- # The `use_around_filter` matcher is used to test that an around_filter
113
- # callback is defined within your controller.
114
- #
115
- # class ChangesController < ApplicationController
116
- # around_filter :wrap_in_transaction
117
- # end
118
- #
119
- # # RSpec
120
- # RSpec.describe ChangesController, type: :controller do
121
- # it { should use_around_filter(:wrap_in_transaction) }
122
- # it { should_not use_around_filter(:save_view_context) }
123
- # end
124
- #
125
- # # Minitest (Shoulda)
126
- # class ChangesControllerTest < ActionController::TestCase
127
- # should use_around_filter(:wrap_in_transaction)
128
- # should_not use_around_filter(:save_view_context)
129
- # end
130
- #
131
- # @note This method is only available when using shoulda-matchers under
132
- # Rails 4.x.
133
- # @return [CallbackMatcher]
134
- #
135
- if RailsShim.action_pack_lt_5?
136
- def use_around_filter(callback)
137
- CallbackMatcher.new(callback, :around, :filter)
138
- end
139
- end
140
-
141
54
  # The `use_around_action` matcher is used to test that an around_action
142
55
  # callback is defined within your controller.
143
56
  #
@@ -1,3 +1,4 @@
1
+ require 'active_support/core_ext/module/delegation'
1
2
  module Shoulda
2
3
  module Matchers
3
4
  module ActionController
@@ -34,10 +35,7 @@ module Shoulda
34
35
  expected_value === actual_value
35
36
  end
36
37
  end
37
-
38
- def empty?
39
- flash.empty?
40
- end
38
+ delegate :empty?, to: :flash
41
39
 
42
40
  def use_now!
43
41
  @use_now = true
@@ -251,12 +251,9 @@ module Shoulda
251
251
  parameters_double_registry.register
252
252
 
253
253
  Doublespeak.with_doubles_activated do
254
- Shoulda::Matchers::RailsShim.make_controller_request(
255
- context,
256
- verb,
257
- action,
258
- request_params,
259
- )
254
+ params = { params: request_params }
255
+
256
+ context.__send__(verb, action, **params)
260
257
  end
261
258
 
262
259
  unpermitted_parameter_names.empty?
@@ -8,7 +8,7 @@ module Shoulda
8
8
  end
9
9
 
10
10
  def format_validation_errors(errors)
11
- list_items = errors.keys.map do |attribute|
11
+ list_items = errors.to_hash.keys.map do |attribute|
12
12
  messages = errors[attribute]
13
13
  "* #{attribute}: #{messages}"
14
14
  end
@@ -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,14 +108,17 @@ 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
115
117
  when :decimal then BigDecimal(1, 0)
116
- when :datetime, :time, :timestamp then Time.now
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
@@ -643,7 +681,7 @@ module Shoulda
643
681
  if number_of_submatchers_for_failure_message > 1
644
682
  "In checking that #{model.name} #{submatcher_description}, " +
645
683
  failure_message[0].downcase +
646
- failure_message[1..-1]
684
+ failure_message[1..]
647
685
  else
648
686
  failure_message
649
687
  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