shoulda-matchers 4.5.1 → 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.
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