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 +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
|