shoulda-matchers 4.0.1 → 4.1.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 +4 -4
- data/README.md +189 -87
- data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +1 -0
- data/lib/shoulda/matchers/active_model/qualifiers.rb +1 -0
- data/lib/shoulda/matchers/active_model/qualifiers/allow_nil.rb +26 -0
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +2 -1
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +171 -25
- data/lib/shoulda/matchers/active_record/association_matcher.rb +11 -7
- data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +120 -63
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +161 -45
- data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +3 -13
- data/lib/shoulda/matchers/configuration.rb +12 -1
- data/lib/shoulda/matchers/integrations/configuration.rb +7 -3
- data/lib/shoulda/matchers/rails_shim.rb +37 -0
- data/lib/shoulda/matchers/util.rb +1 -1
- data/lib/shoulda/matchers/util/word_wrap.rb +4 -3
- data/lib/shoulda/matchers/version.rb +1 -1
- data/shoulda-matchers.gemspec +20 -13
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1856891b0a96aaa5ed99a48490288b2bbd3547c7779ab1d8477a33b61cbb0c7d
|
4
|
+
data.tar.gz: b3768f6532318b37de78e6959a1c7be2e28e19b4618d966a7ca0dab0cfa96ceb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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!
|
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!
|
92
|
-
|
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
|
-
|
96
|
-
|
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
|
-
|
101
|
-
below](#matchers).
|
218
|
+
In this case, you would want to use `before` rather than `subject`:
|
102
219
|
|
103
|
-
|
220
|
+
``` ruby
|
221
|
+
RSpec.describe PostsController, type: :controller do
|
222
|
+
describe 'GET #index' do
|
223
|
+
before { get :index }
|
104
224
|
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
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
|
-
|
120
|
-
|
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
|
-
|
124
|
-
|
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
|
-
|
274
|
+
### `should` vs `is_expected.to`
|
141
275
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
271
|
-
|
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
|
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/
|
@@ -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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
151
|
-
|
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
|
162
|
-
allows_value_of(value,
|
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(
|
219
|
+
raise ActiveModel::CouldNotSetPasswordError.create(model)
|
165
220
|
end
|
166
221
|
|
167
|
-
def allows_original_or_typecast_value?(value
|
168
|
-
allows_value_of(
|
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
|
172
|
-
disallows_value_of(value,
|
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(
|
229
|
+
raise ActiveModel::CouldNotSetPasswordError.create(model)
|
175
230
|
end
|
176
231
|
|
177
|
-
def disallows_original_or_typecast_value?(value
|
178
|
-
disallows_value_of(
|
232
|
+
def disallows_original_or_typecast_value?(value)
|
233
|
+
disallows_value_of(value, @expected_message)
|
179
234
|
end
|
180
235
|
|
181
|
-
def
|
236
|
+
def disallowed_values
|
182
237
|
if collection?
|
183
|
-
[]
|
238
|
+
[Array.new]
|
184
239
|
else
|
185
|
-
|
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
|
191
|
-
[:has_many, :has_and_belongs_to_many].include?(
|
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
|
198
|
-
|
199
|
-
|
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
|