shoulda-matchers 5.1.0 → 6.4.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/LICENSE +1 -1
- data/README.md +41 -18
- data/lib/shoulda/matchers/action_controller/permit_matcher.rb +7 -9
- data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +1 -1
- data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +46 -1
- data/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
- data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +7 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +3 -5
- data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +53 -0
- data/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
- data/lib/shoulda/matchers/active_model/qualifiers.rb +1 -0
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +2 -7
- data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +532 -0
- data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +5 -5
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +29 -14
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +65 -10
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +76 -86
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +29 -4
- data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +6 -7
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
- data/lib/shoulda/matchers/active_model/validator.rb +4 -0
- data/lib/shoulda/matchers/active_model.rb +4 -1
- data/lib/shoulda/matchers/active_record/association_matcher.rb +543 -15
- data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +34 -4
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +9 -1
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -0
- data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +4 -0
- data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +23 -19
- data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +27 -23
- data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +338 -30
- data/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +24 -13
- data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
- data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
- data/lib/shoulda/matchers/active_record/uniqueness/model.rb +13 -1
- data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +82 -70
- data/lib/shoulda/matchers/active_record.rb +2 -0
- data/lib/shoulda/matchers/doublespeak/double_collection.rb +2 -6
- data/lib/shoulda/matchers/doublespeak/world.rb +2 -6
- data/lib/shoulda/matchers/doublespeak.rb +0 -1
- data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +13 -15
- data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +7 -5
- data/lib/shoulda/matchers/integrations/libraries/routing.rb +5 -3
- data/lib/shoulda/matchers/rails_shim.rb +22 -6
- data/lib/shoulda/matchers/util/word_wrap.rb +2 -2
- data/lib/shoulda/matchers/util.rb +18 -20
- data/lib/shoulda/matchers/version.rb +1 -1
- data/lib/shoulda/matchers.rb +2 -2
- data/shoulda-matchers.gemspec +2 -2
- metadata +15 -9
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -157
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a3a788cc37a73b615004a4e88dacb051413e00f9a752b78f00cfa8b529e4149
|
4
|
+
data.tar.gz: 99e5319049435d5ae107d068cecacd547bbd5135e1f1966bf8e15c40e5cde84f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f2b4708f133c2e0a790c99de4608d976d8dfe8704436adeea539e816a18bc6bca7612e7ff669bc4830f8f617550a0db7a70574f611a791dc27879d66f6ed09c
|
7
|
+
data.tar.gz: 460ac28a8c3833b30ec3171f62a5d5225712132837caeb1c620df49cdcc1fc2d92614a8a652ac93f7707b06d4fe24c5c9ee70fc9759b8b34a3929f845931a2b5
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
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
|
-
[github-actions-badge]: https://img.shields.io/github/workflow/status/thoughtbot/shoulda-matchers/
|
5
|
+
[github-actions-badge]: https://img.shields.io/github/actions/workflow/status/thoughtbot/shoulda-matchers/ci.yml?branch=main
|
6
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
|
@@ -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', '~>
|
58
|
+
gem 'shoulda-matchers', '~> 6.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', '~>
|
120
|
+
gem 'shoulda-matchers', '~> 6.0'
|
121
121
|
end
|
122
122
|
```
|
123
123
|
|
@@ -167,9 +167,9 @@ end
|
|
167
167
|
Most of the matchers provided by this gem are useful in a Rails context, and as
|
168
168
|
such, can be used for different parts of a Rails app:
|
169
169
|
|
170
|
-
* [database models backed by ActiveRecord](#
|
170
|
+
* [database models backed by ActiveRecord](#activerecord-matchers)
|
171
171
|
* [non-database models, form objects, etc. backed by
|
172
|
-
ActiveModel](#
|
172
|
+
ActiveModel](#activemodel-matchers)
|
173
173
|
* [controllers](#actioncontroller-matchers)
|
174
174
|
* [routes](#routing-matchers) (RSpec only)
|
175
175
|
* [Rails-specific features like `delegate`](#independent-matchers)
|
@@ -374,6 +374,8 @@ about any of them, make sure to [consult the documentation][rubydocs]!
|
|
374
374
|
tests usage of `validates_numericality_of`.
|
375
375
|
* **[validate_presence_of](lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb)**
|
376
376
|
tests usage of `validates_presence_of`.
|
377
|
+
* **[validate_comparison_of](lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb)**
|
378
|
+
tests usage of `validates_comparison_of`.
|
377
379
|
|
378
380
|
### ActiveRecord matchers
|
379
381
|
|
@@ -383,8 +385,10 @@ about any of them, make sure to [consult the documentation][rubydocs]!
|
|
383
385
|
tests your `belongs_to` associations.
|
384
386
|
* **[define_enum_for](lib/shoulda/matchers/active_record/define_enum_for_matcher.rb)**
|
385
387
|
tests usage of the `enum` macro.
|
386
|
-
* **[have_and_belong_to_many](lib/shoulda/matchers/active_record/association_matcher.rb
|
388
|
+
* **[have_and_belong_to_many](lib/shoulda/matchers/active_record/association_matcher.rb)**
|
387
389
|
tests your `has_and_belongs_to_many` associations.
|
390
|
+
* **[have_delegated_type](lib/shoulda/matchers/active_record/association_matcher.rb#L687)**
|
391
|
+
tests usage of the `delegated_type` macro.
|
388
392
|
* **[have_db_column](lib/shoulda/matchers/active_record/have_db_column_matcher.rb)**
|
389
393
|
tests that the table that backs your model has a specific column.
|
390
394
|
* **[have_db_index](lib/shoulda/matchers/active_record/have_db_index_matcher.rb)**
|
@@ -407,6 +411,10 @@ about any of them, make sure to [consult the documentation][rubydocs]!
|
|
407
411
|
usage of the `serialize` macro.
|
408
412
|
* **[validate_uniqueness_of](lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb)**
|
409
413
|
tests usage of `validates_uniqueness_of`.
|
414
|
+
* **[normalize](lib/shoulda/matchers/active_record/normalize_matcher.rb)** tests
|
415
|
+
usage of the `normalize` macro
|
416
|
+
* **[encrypt](lib/shoulda/matchers/active_record/encrypt_matcher.rb)**
|
417
|
+
tests usage of the `encrypts` macro.
|
410
418
|
|
411
419
|
### ActionController matchers
|
412
420
|
|
@@ -468,8 +476,8 @@ machine, understanding the codebase, and creating a good pull request.
|
|
468
476
|
|
469
477
|
## Compatibility
|
470
478
|
|
471
|
-
Shoulda Matchers is tested and supported against Ruby
|
472
|
-
|
479
|
+
Shoulda Matchers is tested and supported against Ruby 3.0+, Rails
|
480
|
+
6.1+, RSpec 3.x, and Minitest 5.x.
|
473
481
|
|
474
482
|
- For Ruby < 2.4 and Rails < 4.1 compatibility, please use [v3.1.3][v3.1.3].
|
475
483
|
- For Ruby < 3.0 and Rails < 6.1 compatibility, please use [v4.5.1][v4.5.1].
|
@@ -484,30 +492,45 @@ Shoulda Matchers follows Semantic Versioning 2.0 as defined at
|
|
484
492
|
|
485
493
|
## Team
|
486
494
|
|
487
|
-
Shoulda Matchers is maintained by [
|
488
|
-
|
495
|
+
Shoulda Matchers is currently maintained by [Pedro Paiva][VSPPedro] and [Matheus
|
496
|
+
Sales][matsales28]. Previous maintainers include [Elliot Winkler][mcmire],
|
497
|
+
[Gui Albuk][guialbuk], [Jason Draper][drapergeek], [Melissa Xie][mxie],
|
498
|
+
[Gabe Berke-Williams][gabebw], [Ryan McGeary][rmm5t], [Joe Ferris][jferris], and
|
499
|
+
[Tammer Saleh][tammersaleh].
|
489
500
|
|
501
|
+
[VSPPedro]: https://github.com/VSPPedro
|
502
|
+
[matsales28]: https://github.com/matsales28
|
490
503
|
[mcmire]: https://github.com/mcmire
|
491
504
|
[guialbuk]: https://github.com/guialbuk
|
505
|
+
[drapergeek]: https://github.com/drapergeek
|
506
|
+
[mxie]: https://github.com/mxie
|
507
|
+
[gabebw]: https://github.com/gabebw
|
508
|
+
[rmm5t]: https://github.com/rmm5t
|
509
|
+
[jferris]: https://github.com/jferris
|
510
|
+
[tammersaleh]: https://github.com/tammersaleh
|
492
511
|
|
493
512
|
## Copyright/License
|
494
513
|
|
495
|
-
Shoulda Matchers is copyright ©
|
514
|
+
Shoulda Matchers is copyright © Tammer Saleh and [thoughtbot,
|
496
515
|
inc][thoughtbot-website]. It is free and opensource software and may be
|
497
516
|
redistributed under the terms specified in the [LICENSE](LICENSE) file.
|
498
517
|
|
499
518
|
[thoughtbot-website]: https://thoughtbot.com
|
500
519
|
|
520
|
+
<!-- START /templates/footer.md -->
|
501
521
|
## About thoughtbot
|
502
522
|
|
503
|
-
![thoughtbot]
|
504
|
-
|
505
|
-
[thoughtbot-logo]: https://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg
|
523
|
+

|
506
524
|
|
525
|
+
This repo is maintained and funded by thoughtbot, inc.
|
507
526
|
The names and logos for thoughtbot are trademarks of thoughtbot, inc.
|
508
527
|
|
509
|
-
We
|
510
|
-
|
528
|
+
We love open source software!
|
529
|
+
See [our other projects][community].
|
530
|
+
We are [available for hire][hire].
|
511
531
|
|
512
532
|
[community]: https://thoughtbot.com/community?utm_source=github
|
513
|
-
[hire]: https://thoughtbot.com?utm_source=github
|
533
|
+
[hire]: https://thoughtbot.com/hire-us?utm_source=github
|
534
|
+
|
535
|
+
|
536
|
+
<!-- END /templates/footer.md -->
|
@@ -11,7 +11,7 @@ module Shoulda
|
|
11
11
|
module Matchers
|
12
12
|
module ActionController
|
13
13
|
# The `permit` matcher tests that an action in your controller receives a
|
14
|
-
#
|
14
|
+
# allowlist of parameters using Rails' Strong Parameters feature
|
15
15
|
# (specifically that `permit` was called with the correct arguments).
|
16
16
|
#
|
17
17
|
# Here's an example:
|
@@ -276,16 +276,14 @@ module Shoulda
|
|
276
276
|
:context, :subparameter_name, :parameters_double_registry
|
277
277
|
|
278
278
|
def expectation
|
279
|
-
|
279
|
+
String.new('restrict parameters ').tap do |message|
|
280
|
+
if subparameter_name
|
281
|
+
message << "on #{subparameter_name.inspect} "
|
282
|
+
end
|
280
283
|
|
281
|
-
|
282
|
-
|
284
|
+
message << 'to '\
|
285
|
+
"#{format_parameter_names(expected_permitted_parameter_names)}"
|
283
286
|
end
|
284
|
-
|
285
|
-
message << 'to '\
|
286
|
-
"#{format_parameter_names(expected_permitted_parameter_names)}"
|
287
|
-
|
288
|
-
message
|
289
287
|
end
|
290
288
|
|
291
289
|
def reality
|
@@ -83,25 +83,23 @@ module Shoulda
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def expectation_description
|
86
|
-
|
87
|
-
|
88
|
-
string <<
|
89
|
-
if key_set?
|
90
|
-
" #{store.name}[#{key.inspect}]"
|
91
|
-
else
|
92
|
-
" any key in #{store.name}"
|
93
|
-
end
|
94
|
-
|
95
|
-
if expected_value_set?
|
86
|
+
String.new('set').tap do |string|
|
96
87
|
string <<
|
97
|
-
if
|
98
|
-
"
|
88
|
+
if key_set?
|
89
|
+
" #{store.name}[#{key.inspect}]"
|
99
90
|
else
|
100
|
-
"
|
91
|
+
" any key in #{store.name}"
|
101
92
|
end
|
102
|
-
end
|
103
93
|
|
104
|
-
|
94
|
+
if expected_value_set?
|
95
|
+
string <<
|
96
|
+
if expected_value.is_a?(Regexp)
|
97
|
+
" to a value matching #{expected_value.inspect}"
|
98
|
+
else
|
99
|
+
" to #{expected_value.inspect}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
105
103
|
end
|
106
104
|
end
|
107
105
|
end
|
@@ -169,6 +169,36 @@ module Shoulda
|
|
169
169
|
# on(:create)
|
170
170
|
# end
|
171
171
|
#
|
172
|
+
# ##### against
|
173
|
+
#
|
174
|
+
# Use `against` if the validation is on an attribute
|
175
|
+
# other than the attribute being validated:
|
176
|
+
#
|
177
|
+
# class UserProfile
|
178
|
+
# include ActiveModel::Model
|
179
|
+
# attr_accessor :website_url
|
180
|
+
#
|
181
|
+
# alias_attribute :url, :website_url
|
182
|
+
#
|
183
|
+
# validates_format_of :url, with: URI.regexp
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
# # RSpec
|
187
|
+
# RSpec.describe UserProfile, type: :model do
|
188
|
+
# it do
|
189
|
+
# should allow_value('https://foo.com').
|
190
|
+
# for(:website_url).
|
191
|
+
# against(:url)
|
192
|
+
# end
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# # Minitest (Shoulda)
|
196
|
+
# class UserProfileTest < ActiveSupport::TestCase
|
197
|
+
# should allow_value('https://foo.com').
|
198
|
+
# for(:website_url).
|
199
|
+
# against(:url)
|
200
|
+
# end
|
201
|
+
#
|
172
202
|
# ##### with_message
|
173
203
|
#
|
174
204
|
# Use `with_message` if you are using a custom validation message.
|
@@ -349,6 +379,12 @@ module Shoulda
|
|
349
379
|
self
|
350
380
|
end
|
351
381
|
|
382
|
+
def against(attribute)
|
383
|
+
@attribute_to_check_message_against = attribute if attribute.present?
|
384
|
+
|
385
|
+
self
|
386
|
+
end
|
387
|
+
|
352
388
|
def with_message(message, given_options = {})
|
353
389
|
if message.present?
|
354
390
|
@expects_custom_validation_message = true
|
@@ -402,6 +438,10 @@ module Shoulda
|
|
402
438
|
@result.nil?
|
403
439
|
end
|
404
440
|
|
441
|
+
def has_any_errors?
|
442
|
+
validator.record.errors.any?
|
443
|
+
end
|
444
|
+
|
405
445
|
def failure_message
|
406
446
|
attribute_setter = result.attribute_setter
|
407
447
|
|
@@ -480,6 +520,11 @@ module Shoulda
|
|
480
520
|
message << " it produced these validation errors instead:\n\n"
|
481
521
|
message << validator.all_formatted_validation_error_messages
|
482
522
|
end
|
523
|
+
elsif validator.has_any_errors?
|
524
|
+
message << ", placing a validation error on :#{attribute_setter.attribute_name}"
|
525
|
+
message << '. The Example was invalid,'
|
526
|
+
message << " but it had errors involving other attributes:\n\n"
|
527
|
+
message << validator.all_formatted_validation_error_messages
|
483
528
|
else
|
484
529
|
message << ', but it was valid instead.'
|
485
530
|
end
|
@@ -541,7 +586,7 @@ module Shoulda
|
|
541
586
|
end
|
542
587
|
|
543
588
|
def default_failure_message_preface
|
544
|
-
|
589
|
+
String.new.tap do |preface|
|
545
590
|
if descriptions_for_preset_values.any?
|
546
591
|
preface << 'After setting '
|
547
592
|
preface << descriptions_for_preset_values.to_sentence
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
|
3
|
+
module Shoulda
|
4
|
+
module Matchers
|
5
|
+
module ActiveModel
|
6
|
+
# @private
|
7
|
+
class ComparisonMatcher < ValidationMatcher
|
8
|
+
ERROR_MESSAGES = {
|
9
|
+
:> => {
|
10
|
+
label: :greater_than,
|
11
|
+
assertions: [false, false, true],
|
12
|
+
},
|
13
|
+
:>= => {
|
14
|
+
label: :greater_than_or_equal_to,
|
15
|
+
assertions: [false, true, true],
|
16
|
+
},
|
17
|
+
:< => {
|
18
|
+
label: :less_than,
|
19
|
+
assertions: [true, false, false],
|
20
|
+
},
|
21
|
+
:<= => {
|
22
|
+
label: :less_than_or_equal_to,
|
23
|
+
assertions: [true, true, false],
|
24
|
+
},
|
25
|
+
:== => {
|
26
|
+
label: :equal_to,
|
27
|
+
assertions: [false, true, false],
|
28
|
+
},
|
29
|
+
:!= => {
|
30
|
+
label: :other_than,
|
31
|
+
assertions: [true, false, true],
|
32
|
+
},
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
delegate :failure_message, :failure_message_when_negated, to: :comparison_submatchers
|
36
|
+
|
37
|
+
def initialize(matcher, value, operator)
|
38
|
+
super(nil)
|
39
|
+
unless matcher.respond_to? :diff_to_compare
|
40
|
+
raise ArgumentError, 'matcher is invalid'
|
41
|
+
end
|
42
|
+
|
43
|
+
@matcher = matcher
|
44
|
+
@value = value
|
45
|
+
@operator = operator
|
46
|
+
@message = ERROR_MESSAGES[operator][:label]
|
47
|
+
end
|
48
|
+
|
49
|
+
def simple_description
|
50
|
+
description = ''
|
51
|
+
|
52
|
+
if expects_strict?
|
53
|
+
description = ' strictly'
|
54
|
+
end
|
55
|
+
|
56
|
+
description +
|
57
|
+
"disallow :#{attribute} from being a number that is not " +
|
58
|
+
"#{comparison_expectation} #{@value}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def for(attribute)
|
62
|
+
@attribute = attribute
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_message(message)
|
67
|
+
@expects_custom_validation_message = true
|
68
|
+
@message = message
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def expects_custom_validation_message?
|
73
|
+
@expects_custom_validation_message
|
74
|
+
end
|
75
|
+
|
76
|
+
def matches?(subject)
|
77
|
+
@subject = subject
|
78
|
+
comparison_submatchers.matches?(subject)
|
79
|
+
end
|
80
|
+
|
81
|
+
def comparison_description
|
82
|
+
"#{comparison_expectation} #{@value}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def comparison_submatchers
|
86
|
+
@_comparison_submatchers ||=
|
87
|
+
NumericalityMatchers::Submatchers.new(build_comparison_submatchers)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def build_comparison_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: option_value })
|
96
|
+
matcher
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def comparison_combos
|
101
|
+
diffs_to_compare.zip(submatcher_method_names)
|
102
|
+
end
|
103
|
+
|
104
|
+
def submatcher_method_names
|
105
|
+
assertions.map do |value|
|
106
|
+
if value
|
107
|
+
:allow_value_matcher
|
108
|
+
else
|
109
|
+
:disallow_value_matcher
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def assertions
|
115
|
+
ERROR_MESSAGES[@operator][:assertions]
|
116
|
+
end
|
117
|
+
|
118
|
+
def option_value
|
119
|
+
if defined?(@_option_value)
|
120
|
+
@_option_value
|
121
|
+
else
|
122
|
+
@_option_value =
|
123
|
+
case @value
|
124
|
+
when Proc then @value.call(@subject)
|
125
|
+
when Symbol then @subject.send(@value)
|
126
|
+
else @value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def diffs_to_compare
|
132
|
+
diff_to_compare = @matcher.diff_to_compare
|
133
|
+
values = case option_value
|
134
|
+
when String then diffs_when_string(diff_to_compare)
|
135
|
+
else [-1, 0, 1].map { |sign| option_value + (diff_to_compare * sign) }
|
136
|
+
end
|
137
|
+
|
138
|
+
if @matcher.given_numeric_column?
|
139
|
+
values
|
140
|
+
else
|
141
|
+
values.map(&:to_s)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def diffs_when_string(diff_to_compare)
|
146
|
+
[-1, 0, 1].map do |sign|
|
147
|
+
option_value[0..-2] + (option_value[-1].ord + diff_to_compare * sign).chr
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def comparison_expectation
|
152
|
+
ERROR_MESSAGES[@operator][:label].to_s.tr('_', ' ')
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -47,6 +47,8 @@ module Shoulda
|
|
47
47
|
did_not_authenticate_correct_password: 'expected %{subject} to'\
|
48
48
|
' authenticate the correct %{attribute}',
|
49
49
|
method_not_found: 'expected %{subject} to respond to %{methods}',
|
50
|
+
should_not_have_secure_password: 'expected %{subject} to'\
|
51
|
+
' not %{description}!',
|
50
52
|
}.freeze
|
51
53
|
|
52
54
|
def initialize(attribute)
|
@@ -69,6 +71,11 @@ module Shoulda
|
|
69
71
|
failure.nil?
|
70
72
|
end
|
71
73
|
|
74
|
+
def failure_message_when_negated
|
75
|
+
MESSAGES[:should_not_have_secure_password] %
|
76
|
+
{ subject: @subject.class, description: description }
|
77
|
+
end
|
78
|
+
|
72
79
|
protected
|
73
80
|
|
74
81
|
attr_reader :subject
|
@@ -55,11 +55,9 @@ module Shoulda
|
|
55
55
|
private
|
56
56
|
|
57
57
|
def disallow_value_matcher
|
58
|
-
@_disallow_value_matcher ||=
|
59
|
-
|
60
|
-
|
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
|
+
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,53 @@
|
|
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
|
+
failing_submatcher.failure_message
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure_message_when_negated
|
21
|
+
non_failing_submatcher.failure_message_when_negated
|
22
|
+
end
|
23
|
+
|
24
|
+
def add(submatcher)
|
25
|
+
@submatchers << submatcher
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def failing_submatchers
|
31
|
+
@_failing_submatchers ||= @submatchers.reject do |submatcher|
|
32
|
+
submatcher.matches?(@subject)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def non_failing_submatchers
|
37
|
+
@_non_failing_submatchers ||= @submatchers.reject do |submatcher|
|
38
|
+
submatcher.does_not_match?(@subject)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def failing_submatcher
|
43
|
+
failing_submatchers.last
|
44
|
+
end
|
45
|
+
|
46
|
+
def non_failing_submatcher
|
47
|
+
non_failing_submatchers.last
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
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
|