shoulda-matchers 4.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e90bda8ad7aa6bef803eddafa1a0aee31f4228372a5d4ba30255682cc841ed6
4
- data.tar.gz: 33c33b0e10964e0dfd3d80e9e680b361a96f817205cd513786779593e3681ae7
3
+ metadata.gz: 1856891b0a96aaa5ed99a48490288b2bbd3547c7779ab1d8477a33b61cbb0c7d
4
+ data.tar.gz: b3768f6532318b37de78e6959a1c7be2e28e19b4618d966a7ca0dab0cfa96ceb
5
5
  SHA512:
6
- metadata.gz: 71099155dc17393673681f6255d4dc52735182de139ccf798939b2a3f8ea310e5547286f91ddbd914b98d58fccb7cecc2a0277ecdab1febd6ecf7b918de888b0
7
- data.tar.gz: 1b4624af4740f43192336f624f748c3506131d1ce57f29c65591dce5f0ceee8e0d12a5860a81ddadadec4e423e94662f166a5d91dc7df82050d53353e82994a7
6
+ metadata.gz: dc99bd232c8f9a3eeff2ea7b82b36821339b20090b40c750c282b48d413b99028fcb69ec775ecb37a43d7e6d59a0c090213c35ede384ed95303d010a5e00ed90
7
+ data.tar.gz: 56dbd1bd6978b59743e6936118a535e276b7672f12a0b46b69b3baf164b6dcac0688ee26ec66ccb11b2d0af8d1374567d401ef1f46d3f54bd6d3ccee8cf55085
data/README.md CHANGED
@@ -1,20 +1,39 @@
1
1
  # Shoulda Matchers [![Gem Version][version-badge]][rubygems] [![Build Status][travis-badge]][travis] ![Downloads][downloads-badge] [![Hound][hound-badge]][hound]
2
2
 
3
+ [version-badge]: https://img.shields.io/gem/v/shoulda-matchers.svg
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
7
+ [downloads-badge]: https://img.shields.io/gem/dtv/shoulda-matchers.svg
8
+ [hound-badge]: https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg
9
+ [hound]: https://houndci.com
10
+
3
11
  [![shoulda-matchers][logo]][website]
4
12
 
5
- Shoulda Matchers provides RSpec- and Minitest-compatible one-liners that test
6
- common Rails functionality. These tests, if written by hand, would be much
7
- longer, more complex, and error-prone.
13
+ [logo]: https://matchers.shoulda.io/images/shoulda-matchers-logo.png
14
+ [website]: https://matchers.shoulda.io/
15
+
16
+ Shoulda Matchers provides RSpec- and Minitest-compatible one-liners to test
17
+ common Rails functionality that, if written by hand, would be much longer, more
18
+ complex, and error-prone.
19
+
20
+ ## Quick links
21
+
22
+ 📖 **[Read the documentation for the latest version (4.1.0)][rubydocs].**
23
+ 📢 **[See what's changed in a recent version][news].**
8
24
 
9
- **[View the documentation for the latest version (4.0.0).][rubydocs]**
25
+ [rubydocs]: http://matchers.shoulda.io/docs
26
+ [news]: NEWS.md
10
27
 
11
- ---
28
+ ## Table of contents
12
29
 
13
30
  * [Getting started](#getting-started)
14
31
  * [RSpec](#rspec)
15
- * [Availability of matchers in various example groups](#availability-of-matchers-in-various-example-groups)
16
- * [<code>should</code> vs <code>is_expected.to</code>](#should-vs-is_expectedto)
17
32
  * [Minitest](#minitest)
33
+ * [Usage](#usage)
34
+ * [On the subject of `subject`](#on-the-subject-of-subject)
35
+ * [Availability of RSpec matchers in example groups](#availability-of-rspec-matchers-in-example-groups)
36
+ * [`should` vs `is_expected.to`](#should-vs-is_expectedto)
18
37
  * [Matchers](#matchers)
19
38
  * [ActiveModel matchers](#activemodel-matchers)
20
39
  * [ActiveRecord matchers](#activerecord-matchers)
@@ -35,7 +54,6 @@ Start by including `shoulda-matchers` in your Gemfile:
35
54
  ```ruby
36
55
  group :test do
37
56
  gem 'shoulda-matchers'
38
- gem 'rails-controller-testing'
39
57
  end
40
58
  ```
41
59
 
@@ -61,14 +79,7 @@ Shoulda::Matchers.configure do |config|
61
79
  end
62
80
  ```
63
81
 
64
- Now you're ready to use matchers in your tests! For instance, you might decide
65
- to add a matcher to one of your models:
66
-
67
- ```ruby
68
- RSpec.describe Person, type: :model do
69
- it { should validate_presence_of(:name) }
70
- end
71
- ```
82
+ Now you're ready to [use matchers in your tests](#usage)!
72
83
 
73
84
  #### Non-Rails apps
74
85
 
@@ -88,23 +99,144 @@ Shoulda::Matchers.configure do |config|
88
99
  end
89
100
  ```
90
101
 
91
- Now you're ready to use matchers in your tests! For instance, you might decide
92
- to add a matcher to one of your models:
102
+ Now you're ready to [use matchers in your tests](#usage)!
103
+
104
+ ### Minitest
105
+
106
+ Shoulda Matchers was originally a component of [Shoulda][shoulda], a gem that
107
+ also provides `should` and `context` syntax via
108
+ [`shoulda-context`][shoulda-context].
109
+
110
+ [shoulda]: https://github.com/thoughtbot/shoulda
111
+ [shoulda-context]: https://github.com/thoughtbot/shoulda-context
112
+
113
+ At the moment, `shoulda` has not been updated to support `shoulda-matchers` 3.x
114
+ and 4.x, so you'll want to add the following to your Gemfile:
93
115
 
94
116
  ```ruby
95
- RSpec.describe Person, type: :model do
96
- it { should validate_presence_of(:name) }
117
+ group :test do
118
+ gem 'shoulda', '~> 3.5'
119
+ gem 'shoulda-matchers', '~> 2.0'
120
+ gem 'rails-controller-testing'
121
+ end
122
+ ```
123
+
124
+ Now you're ready to [use matchers in your tests](#usage)!
125
+
126
+ ## Usage
127
+
128
+ The matchers provided by this gem are divided into different categories
129
+ depending on what you're testing within your Rails app:
130
+
131
+ * [database models backed by ActiveRecord](#activemodel-matchers)
132
+ * [non-database models, form objects, etc. backed by
133
+ ActiveModel](#activerecord-matchers)
134
+ * [controllers](#actioncontroller-matchers)
135
+ * [routes](#routing-matchers) (RSpec only)
136
+ * [usage of Rails-specific features like `delegate`](#independent-matchers)
137
+
138
+ All matchers are designed to be prepended primarily with the word `should`,
139
+ which is a special directive in both RSpec and Shoulda. For instance, a model
140
+ test case may look something like:
141
+
142
+ ``` ruby
143
+ # RSpec
144
+ RSpec.describe MenuItem, type: :model do
145
+ describe 'associations' do
146
+ it { should belong_to(:category).class_name('MenuCategory') }
147
+ end
148
+
149
+ describe 'validations' do
150
+ it { should validate_presence_of(:name) }
151
+ it { should validate_uniqueness_of(:name).scoped_to(:category_id) }
152
+ end
153
+ end
154
+
155
+ # Minitest (Shoulda)
156
+ class MenuItemTest < ActiveSupport::TestCase
157
+ context 'associations' do
158
+ should belong_to(:category).class_name('MenuCategory')
159
+ end
160
+
161
+ context 'validations' do
162
+ should validate_presence_of(:name)
163
+ should validate_uniqueness_of(:name).scoped_to(:category_id)
164
+ end
165
+ end
166
+ ```
167
+
168
+ For the full set of matchers you can use, [see below](#matchers).
169
+
170
+ ### On the subject of `subject`
171
+
172
+ For both RSpec and Shoulda, the **subject** is an implicit reference to the
173
+ object under test, and all of the matchers make use of it internally when they
174
+ are run. This is always set automatically by your test framework in any given
175
+ test case; however, in certain cases it can be advantageous to override the
176
+ subject. For instance, when testing validations in a model, it is customary to
177
+ provide a valid model instead of a fresh one:
178
+
179
+ ``` ruby
180
+ # RSpec
181
+ RSpec.describe Post, type: :model do
182
+ describe 'validations' do
183
+ # Here we're using FactoryBot, but you could use anything
184
+ subject { build(:post) }
185
+
186
+ it { should validate_presence_of(:title) }
187
+ end
188
+ end
189
+
190
+ # Minitest (Shoulda)
191
+ class PostTest < ActiveSupport::TestCase
192
+ context 'validations' do
193
+ subject { build(:post) }
194
+
195
+ should validate_presence_of(:title)
196
+ end
197
+ end
198
+ ```
199
+
200
+ When overriding the subject in this manner, then, it's important to provide the
201
+ correct object. **When in doubt, provide an instance of the class under test.**
202
+ This is particularly necessary for controller tests, where it is easy to
203
+ accidentally write something like:
204
+
205
+ ``` ruby
206
+ RSpec.describe PostsController, type: :controller do
207
+ describe 'GET #index' do
208
+ subject { get :index }
209
+
210
+ # This may work...
211
+ it { should have_http_status(:success) }
212
+ # ...but this will not!
213
+ it { should permit(:title, :body).for(:post) }
214
+ end
97
215
  end
98
216
  ```
99
217
 
100
- For more of an idea of what you can use, [see the list of matchers
101
- below](#matchers).
218
+ In this case, you would want to use `before` rather than `subject`:
102
219
 
103
- #### Availability of matchers in various example groups
220
+ ``` ruby
221
+ RSpec.describe PostsController, type: :controller do
222
+ describe 'GET #index' do
223
+ before { get :index }
104
224
 
105
- Regardless of your project, it's important to keep in mind that since
106
- shoulda-matchers provides four categories of matchers, there are four different
107
- levels where you can use these matchers:
225
+ # Notice that we have to assert have_http_status on the response here...
226
+ it { expect(response).to have_http_status(:success) }
227
+ # ...but we do not have to provide a subject for render_template
228
+ it { should render_template('index') }
229
+ end
230
+ end
231
+ ```
232
+
233
+ ### Availability of RSpec matchers in example groups
234
+
235
+ If you're using RSpec, then you're probably familiar with the concept of example
236
+ groups: these are different kinds of test cases, and each of them has special
237
+ behavior around them. As alluded to [above](#usage), this gem works in a similar
238
+ way, and there are matchers that are only available in certain types of example
239
+ groups:
108
240
 
109
241
  * ActiveRecord and ActiveModel matchers are available only in model example
110
242
  groups, i.e., those tagged with `type: :model` or in files located under
@@ -112,17 +244,19 @@ levels where you can use these matchers:
112
244
  * ActionController matchers are available only in controller example groups,
113
245
  i.e., those tagged with `type: :controller` or in files located under
114
246
  `spec/controllers`.
115
- * The `route` matcher is available also in routing example groups, i.e., those
247
+ * The `route` matcher is available in routing example groups, i.e., those
116
248
  tagged with `type: :routing` or in files located under `spec/routing`.
117
249
  * Independent matchers are available in all example groups.
118
250
 
119
- **⚠️ If you are using ActiveModel or ActiveRecord outside of Rails** and you want
120
- to use model matchers in certain example groups, you'll need to manually include
121
- them. Here's a good way of doing that:
251
+ As long as you're using Rails, you don't need to worry about this — everything
252
+ should "just work".
122
253
 
123
- ```ruby
124
- require 'shoulda-matchers'
254
+ **However, if you are using ActiveModel or ActiveRecord outside of Rails**, and
255
+ you want to use model matchers in certain example groups, you'll need to
256
+ manually include the module that holds those matchers. A good way to do this is
257
+ to place the following in your `spec_helper.rb`:
125
258
 
259
+ ```ruby
126
260
  RSpec.configure do |config|
127
261
  config.include(Shoulda::Matchers::ActiveModel, type: :model)
128
262
  config.include(Shoulda::Matchers::ActiveRecord, type: :model)
@@ -137,13 +271,13 @@ describe MySpecialModel, type: :model do
137
271
  end
138
272
  ```
139
273
 
140
- #### `should` vs `is_expected.to`
274
+ ### `should` vs `is_expected.to`
141
275
 
142
- Note that in this README and throughout the documentation we're using the
143
- `should` form of RSpec's one-liner syntax over `is_expected.to`. The `should`
144
- form works regardless of how you've configured RSpec — meaning you can still use
145
- it even when using the `expect` syntax. But if you prefer to use
146
- `is_expected.to`, you can do that too:
276
+ In this README and throughout the documentation, we're using the `should` form
277
+ of RSpec's one-liner syntax over `is_expected.to`. The `should` form works
278
+ regardless of how you've configured RSpec — meaning you can still use it even
279
+ when using the `expect` syntax. But if you prefer to use `is_expected.to`, you
280
+ can do that too:
147
281
 
148
282
  ```ruby
149
283
  RSpec.describe Person, type: :model do
@@ -151,36 +285,11 @@ RSpec.describe Person, type: :model do
151
285
  end
152
286
  ```
153
287
 
154
- ### Minitest
155
-
156
- Shoulda Matchers was originally a component of [Shoulda][shoulda], a gem that
157
- also provides `should` and `context` syntax via
158
- [`shoulda-context`][shoulda-context].
159
-
160
- At the moment, `shoulda` has not been updated to support `shoulda-matchers` 3.x
161
- and 4.x, so you'll want to add the following to your Gemfile:
162
-
163
- ```ruby
164
- group :test do
165
- gem 'shoulda', '~> 3.5'
166
- gem 'shoulda-matchers', '~> 2.0'
167
- end
168
- ```
169
-
170
- Now you're ready to use matchers in your tests! For instance, you might decide
171
- to add a matcher to one of your models:
172
-
173
- ```ruby
174
- class PersonTest < ActiveSupport::TestCase
175
- should validate_presence_of(:name)
176
- end
177
- ```
178
-
179
- For more of an idea of what you can use, [see the list of matchers
180
- below](#matchers).
181
-
182
288
  ## Matchers
183
289
 
290
+ The following is a list of matchers shipped with the gem. If you need details
291
+ about any of them, make sure to [consult the documentation][rubydocs]!
292
+
184
293
  ### ActiveModel matchers
185
294
 
186
295
  * **[allow_value](lib/shoulda/matchers/active_model/allow_value_matcher.rb)**
@@ -259,6 +368,11 @@ below](#matchers).
259
368
  * **[use_before_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L54)**
260
369
  tests that a `before_action` callback is defined in your controller.
261
370
 
371
+ ### Routing matchers
372
+
373
+ * **[route](lib/shoulda/matchers/action_controller/route_matcher.rb)** tests
374
+ your routes.
375
+
262
376
  ### Independent matchers
263
377
 
264
378
  * **[delegate_method](lib/shoulda/matchers/independent/delegate_method_matcher.rb)**
@@ -267,11 +381,10 @@ below](#matchers).
267
381
 
268
382
  ## Compatibility
269
383
 
270
- Shoulda Matchers 4 is tested and supported against Rails 5.x, Rails 4.2, RSpec
271
- 3.x, Minitest 5, Minitest 4, and Ruby 2.3+.
384
+ Shoulda Matchers is tested and supported against Ruby 2.4+, Rails 5.x, Rails
385
+ 4.2.x, RSpec 3.x, and Minitest 5.x.
272
386
 
273
- For Rails 4.0/4.1 and Ruby 2.0/2.1/2.2 compatibility, please use
274
- [shoulda-matchers 3.1.3][v3.1.3].
387
+ For Ruby < 2.4 and Rails < 4.1 compatibility, please use [v3.1.3][v3.1.3].
275
388
 
276
389
  [v3.1.3]: https://github.com/thoughtbot/shoulda-matchers/releases/tag/v3.1.3
277
390
 
@@ -280,6 +393,8 @@ For Rails 4.0/4.1 and Ruby 2.0/2.1/2.2 compatibility, please use
280
393
  Shoulda Matchers is open source, and we are grateful for
281
394
  [everyone][contributors] who's contributed so far.
282
395
 
396
+ [contributors]: https://github.com/thoughtbot/shoulda-matchers/contributors
397
+
283
398
  If you'd like to contribute, please take a look at the
284
399
  [instructions](CONTRIBUTING.md) for installing dependencies and crafting a good
285
400
  pull request.
@@ -296,32 +411,19 @@ Shoulda Matchers is copyright © 2006-2019
296
411
  and may be redistributed under the terms specified in the
297
412
  [MIT-LICENSE](MIT-LICENSE) file.
298
413
 
414
+ [thoughtbot-website]: https://thoughtbot.com
415
+
299
416
  ## About thoughtbot
300
417
 
301
418
  ![thoughtbot][thoughtbot-logo]
302
419
 
420
+ [thoughtbot-logo]: https://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg
421
+
303
422
  Shoulda Matchers is maintained and funded by thoughtbot, inc. The names and
304
423
  logos for thoughtbot are trademarks of thoughtbot, inc.
305
424
 
306
425
  We are passionate about open source software. See [our other
307
426
  projects][community]. We are [available for hire][hire].
308
427
 
309
- [rubydocs]: http://matchers.shoulda.io/docs/v4.0.0.rc1
310
428
  [community]: https://thoughtbot.com/community?utm_source=github
311
429
  [hire]: https://thoughtbot.com?utm_source=github
312
- [version-badge]: https://img.shields.io/gem/v/shoulda-matchers.svg
313
- [rubygems]: httpss://rubygems.org/gems/shoulda-matchers
314
- [travis-badge]: https://img.shields.io/travis/thoughtbot/shoulda-matchers/master.svg
315
- [travis]: https://travis-ci.org/thoughtbot/shoulda-matchers
316
- [downloads-badge]: https://img.shields.io/gem/dtv/shoulda-matchers.svg
317
- [contributors]: https://github.com/thoughtbot/shoulda-matchers/contributors
318
- [shoulda]: https://github.com/thoughtbot/shoulda
319
- [shoulda-context]: https://github.com/thoughtbot/shoulda-context
320
- [Zeus]: https://github.com/burke/zeus
321
- [Appraisal]: https://github.com/thoughtbot/appraisal
322
- [hound-badge]: https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg
323
- [hound]: https://houndci.com
324
- [thoughtbot-website]: https://thoughtbot.com
325
- [thoughtbot-logo]: https://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg
326
- [logo]: https://matchers.shoulda.io/images/shoulda-matchers-logo.png
327
- [website]: https://matchers.shoulda.io/
@@ -17,6 +17,7 @@ module Shoulda
17
17
  :ignore_interference_by_writer,
18
18
  :ignoring_interference_by_writer,
19
19
  :matches?,
20
+ :does_not_match?,
20
21
  :on,
21
22
  :strict,
22
23
  :with_message,
@@ -8,5 +8,6 @@ module Shoulda
8
8
  end
9
9
  end
10
10
 
11
+ require_relative 'qualifiers/allow_nil'
11
12
  require_relative 'qualifiers/ignore_interference_by_writer'
12
13
  require_relative 'qualifiers/ignoring_interference_by_writer'
@@ -0,0 +1,26 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ module Qualifiers
5
+ # @private
6
+ module AllowNil
7
+ def initialize(*args)
8
+ super
9
+ @expects_to_allow_nil = false
10
+ end
11
+
12
+ def allow_nil
13
+ @expects_to_allow_nil = true
14
+ self
15
+ end
16
+
17
+ protected
18
+
19
+ def expects_to_allow_nil?
20
+ @expects_to_allow_nil
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -562,7 +562,8 @@ module Shoulda
562
562
 
563
563
  def has_been_qualified?
564
564
  @submatchers.any? do |submatcher|
565
- submatcher.class.parent == NumericalityMatchers
565
+ Shoulda::Matchers::RailsShim.parent_of(submatcher.class) ==
566
+ NumericalityMatchers
566
567
  end
567
568
  end
568
569
 
@@ -57,6 +57,27 @@ module Shoulda
57
57
  #
58
58
  # #### Qualifiers
59
59
  #
60
+ # ##### allow_nil
61
+ #
62
+ # Use `allow_nil` if your model has an optional attribute.
63
+ #
64
+ # class Robot
65
+ # include ActiveModel::Model
66
+ # attr_accessor :nickname
67
+ #
68
+ # validates_presence_of :nickname, allow_nil: true
69
+ # end
70
+ #
71
+ # # RSpec
72
+ # RSpec.describe Robot, type: :model do
73
+ # it { should validate_presence_of(:nickname).allow_nil }
74
+ # end
75
+ #
76
+ # # Minitest (Shoulda)
77
+ # class RobotTest < ActiveSupport::TestCase
78
+ # should validate_presence_of(:nickname).allow_nil
79
+ # end
80
+ #
60
81
  # ##### on
61
82
  #
62
83
  # Use `on` if your validation applies only under a certain context.
@@ -111,6 +132,8 @@ module Shoulda
111
132
 
112
133
  # @private
113
134
  class ValidatePresenceOfMatcher < ValidationMatcher
135
+ include Qualifiers::AllowNil
136
+
114
137
  def initialize(attribute)
115
138
  super
116
139
  @expected_message = :blank
@@ -122,9 +145,16 @@ module Shoulda
122
145
  possibly_ignore_interference_by_writer
123
146
 
124
147
  if secure_password_being_validated?
125
- disallows_and_double_checks_value_of!(blank_value, @expected_message)
148
+ ignore_interference_by_writer.default_to(when: :blank?)
149
+
150
+ disallowed_values.all? do |value|
151
+ disallows_and_double_checks_value_of!(value)
152
+ end
126
153
  else
127
- disallows_original_or_typecast_value?(blank_value, @expected_message)
154
+ (!expects_to_allow_nil? || allows_value_of(nil)) &&
155
+ disallowed_values.all? do |value|
156
+ disallows_original_or_typecast_value?(value)
157
+ end
128
158
  end
129
159
  end
130
160
 
@@ -134,9 +164,16 @@ module Shoulda
134
164
  possibly_ignore_interference_by_writer
135
165
 
136
166
  if secure_password_being_validated?
137
- allows_and_double_checks_value_of!(blank_value, @expected_message)
167
+ ignore_interference_by_writer.default_to(when: :blank?)
168
+
169
+ disallowed_values.any? do |value|
170
+ allows_and_double_checks_value_of!(value)
171
+ end
138
172
  else
139
- allows_original_or_typecast_value?(blank_value, @expected_message)
173
+ (expects_to_allow_nil? && !allows_value_of(nil)) ||
174
+ disallowed_values.any? do |value|
175
+ allows_original_or_typecast_value?(value)
176
+ end
140
177
  end
141
178
  end
142
179
 
@@ -144,12 +181,30 @@ module Shoulda
144
181
  "validate that :#{@attribute} cannot be empty/falsy"
145
182
  end
146
183
 
184
+ def failure_message
185
+ message = super
186
+
187
+ if should_add_footnote_about_belongs_to?
188
+ message << "\n\n"
189
+ message << Shoulda::Matchers.word_wrap(<<-MESSAGE.strip, indent: 2)
190
+ You're getting this error because #{reason_for_existing_presence_validation}.
191
+ *This* presence validation doesn't use "can't be blank", the usual validation
192
+ message, but "must exist" instead.
193
+
194
+ With that said, did you know that the `belong_to` matcher can test this
195
+ validation for you? Instead of using `validate_presence_of`, try
196
+ #{suggestions_for_belongs_to}
197
+ MESSAGE
198
+ end
199
+
200
+ message
201
+ end
202
+
147
203
  private
148
204
 
149
205
  def secure_password_being_validated?
150
- defined?(::ActiveModel::SecurePassword) &&
151
- @subject.class.ancestors.include?(::ActiveModel::SecurePassword::InstanceMethodsOnActivation) &&
152
- @attribute == :password
206
+ Shoulda::Matchers::RailsShim.digestible_attributes_in(@subject).
207
+ include?(@attribute)
153
208
  end
154
209
 
155
210
  def possibly_ignore_interference_by_writer
@@ -158,45 +213,136 @@ module Shoulda
158
213
  end
159
214
  end
160
215
 
161
- def allows_and_double_checks_value_of!(value, message)
162
- allows_value_of(value, message)
216
+ def allows_and_double_checks_value_of!(value)
217
+ allows_value_of(value, @expected_message)
163
218
  rescue ActiveModel::AllowValueMatcher::AttributeChangedValueError
164
- raise ActiveModel::CouldNotSetPasswordError.create(@subject.class)
219
+ raise ActiveModel::CouldNotSetPasswordError.create(model)
165
220
  end
166
221
 
167
- def allows_original_or_typecast_value?(value, message)
168
- allows_value_of(blank_value, @expected_message)
222
+ def allows_original_or_typecast_value?(value)
223
+ allows_value_of(value, @expected_message)
169
224
  end
170
225
 
171
- def disallows_and_double_checks_value_of!(value, message)
172
- disallows_value_of(value, message)
226
+ def disallows_and_double_checks_value_of!(value)
227
+ disallows_value_of(value, @expected_message)
173
228
  rescue ActiveModel::AllowValueMatcher::AttributeChangedValueError
174
- raise ActiveModel::CouldNotSetPasswordError.create(@subject.class)
229
+ raise ActiveModel::CouldNotSetPasswordError.create(model)
175
230
  end
176
231
 
177
- def disallows_original_or_typecast_value?(value, message)
178
- disallows_value_of(blank_value, @expected_message)
232
+ def disallows_original_or_typecast_value?(value)
233
+ disallows_value_of(value, @expected_message)
179
234
  end
180
235
 
181
- def blank_value
236
+ def disallowed_values
182
237
  if collection?
183
- []
238
+ [Array.new]
184
239
  else
185
- nil
240
+ values = []
241
+
242
+ if !association_being_validated?
243
+ values << ''
244
+ end
245
+
246
+ if !expects_to_allow_nil?
247
+ values << nil
248
+ end
249
+
250
+ values
186
251
  end
187
252
  end
188
253
 
189
254
  def collection?
190
- if reflection
191
- [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
255
+ if association_reflection
256
+ [:has_many, :has_and_belongs_to_many].include?(
257
+ association_reflection.macro,
258
+ )
192
259
  else
193
260
  false
194
261
  end
195
262
  end
196
263
 
197
- def reflection
198
- @subject.class.respond_to?(:reflect_on_association) &&
199
- @subject.class.reflect_on_association(@attribute)
264
+ def should_add_footnote_about_belongs_to?
265
+ belongs_to_association_being_validated? &&
266
+ presence_validation_exists_on_attribute?
267
+ end
268
+
269
+ def reason_for_existing_presence_validation
270
+ if belongs_to_association_configured_to_be_required?
271
+ "you've instructed your `belongs_to` association to add a " +
272
+ 'presence validation to the attribute'
273
+ else
274
+ # assume ::ActiveRecord::Base.belongs_to_required_by_default == true
275
+ 'ActiveRecord is configured to add a presence validation to all ' +
276
+ '`belongs_to` associations, and this includes yours'
277
+ end
278
+ end
279
+
280
+ def suggestions_for_belongs_to
281
+ if belongs_to_association_configured_to_be_required?
282
+ <<~MESSAGE
283
+ one of the following instead, depending on your use case:
284
+
285
+ #{example_of_belongs_to(with: [:optional, false])}
286
+ #{example_of_belongs_to(with: [:required, true])}
287
+ MESSAGE
288
+ else
289
+ <<~MESSAGE
290
+ the following instead:
291
+
292
+ #{example_of_belongs_to}
293
+ MESSAGE
294
+ end
295
+ end
296
+
297
+ def example_of_belongs_to(with: nil)
298
+ initial_call = "should belong_to(:#{association_name})"
299
+ inside =
300
+ if with
301
+ "#{initial_call}.#{with.first}(#{with.second})"
302
+ else
303
+ initial_call
304
+ end
305
+
306
+ if Shoulda::Matchers.integrations.test_frameworks.any?(&:n_unit?)
307
+ inside
308
+ else
309
+ "it { #{inside} }"
310
+ end
311
+ end
312
+
313
+ def belongs_to_association_configured_to_be_required?
314
+ association_options[:optional] == false ||
315
+ association_options[:required] == true
316
+ end
317
+
318
+ def belongs_to_association_being_validated?
319
+ association_being_validated? &&
320
+ association_reflection.macro == :belongs_to
321
+ end
322
+
323
+ def association_being_validated?
324
+ !!association_reflection
325
+ end
326
+
327
+ def association_name
328
+ association_reflection.name
329
+ end
330
+
331
+ def association_options
332
+ association_reflection&.options
333
+ end
334
+
335
+ def association_reflection
336
+ model.respond_to?(:reflect_on_association) &&
337
+ model.reflect_on_association(@attribute)
338
+ end
339
+
340
+ def presence_validation_exists_on_attribute?
341
+ model._validators.include?(@attribute)
342
+ end
343
+
344
+ def model
345
+ @subject.class
200
346
  end
201
347
  end
202
348
  end