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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +41 -18
  4. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +7 -9
  5. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +1 -1
  6. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
  7. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +46 -1
  8. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
  9. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +7 -0
  10. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +3 -5
  11. data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
  12. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +53 -0
  13. data/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
  14. data/lib/shoulda/matchers/active_model/qualifiers.rb +1 -0
  15. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +2 -7
  16. data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +532 -0
  17. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +5 -5
  18. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +29 -14
  19. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +65 -10
  20. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +76 -86
  21. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +29 -4
  22. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +6 -7
  23. data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
  24. data/lib/shoulda/matchers/active_model/validator.rb +4 -0
  25. data/lib/shoulda/matchers/active_model.rb +4 -1
  26. data/lib/shoulda/matchers/active_record/association_matcher.rb +543 -15
  27. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +34 -4
  28. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +9 -1
  29. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -0
  30. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +4 -0
  31. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +23 -19
  32. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +27 -23
  33. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +338 -30
  34. data/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
  35. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
  36. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +24 -13
  37. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
  38. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -1
  39. data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  40. data/lib/shoulda/matchers/active_record/uniqueness/model.rb +13 -1
  41. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +82 -70
  42. data/lib/shoulda/matchers/active_record.rb +2 -0
  43. data/lib/shoulda/matchers/doublespeak/double_collection.rb +2 -6
  44. data/lib/shoulda/matchers/doublespeak/world.rb +2 -6
  45. data/lib/shoulda/matchers/doublespeak.rb +0 -1
  46. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +13 -15
  47. data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +7 -5
  48. data/lib/shoulda/matchers/integrations/libraries/routing.rb +5 -3
  49. data/lib/shoulda/matchers/rails_shim.rb +22 -6
  50. data/lib/shoulda/matchers/util/word_wrap.rb +2 -2
  51. data/lib/shoulda/matchers/util.rb +18 -20
  52. data/lib/shoulda/matchers/version.rb +1 -1
  53. data/lib/shoulda/matchers.rb +2 -2
  54. data/shoulda-matchers.gemspec +2 -2
  55. metadata +15 -9
  56. 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: 188abbfbc6cac3e26997733b3d88b34ea74e9747e25f137738190bf15d033751
4
- data.tar.gz: 73136b35573cffac784cadd7e3193e8e9a0934f0e69fa751f536ed589db01024
3
+ metadata.gz: 0a3a788cc37a73b615004a4e88dacb051413e00f9a752b78f00cfa8b529e4149
4
+ data.tar.gz: 99e5319049435d5ae107d068cecacd547bbd5135e1f1966bf8e15c40e5cde84f
5
5
  SHA512:
6
- metadata.gz: 55f3ccad9ce3d4affcb6ccc38020872bc000710f887765d7b78962bcdd339d79a2a1316e3a9d2cb33c62887e66bab9e54801d79c872f6cab7a2459a32ddce4bf
7
- data.tar.gz: e5a862e62f25dfe284f0325f20600bbc4a3567ef78a0b7742685d1a9af9d03147ab650a259be287bd4566719444c7d998c0106874b7c3ac43efbd79996191838
6
+ metadata.gz: 5f2b4708f133c2e0a790c99de4608d976d8dfe8704436adeea539e816a18bc6bca7612e7ff669bc4830f8f617550a0db7a70574f611a791dc27879d66f6ed09c
7
+ data.tar.gz: 460ac28a8c3833b30ec3171f62a5d5225712132837caeb1c620df49cdcc1fc2d92614a8a652ac93f7707b06d4fe24c5c9ee70fc9759b8b34a3929f845931a2b5
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2021 Tammer Saleh and thoughtbot, inc.
1
+ Copyright (c) Tammer Saleh and thoughtbot, inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
data/README.md CHANGED
@@ -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/Test
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', '~> 5.0'
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', '~> 4.0'
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](#activemodel-matchers)
170
+ * [database models backed by ActiveRecord](#activerecord-matchers)
171
171
  * [non-database models, form objects, etc. backed by
172
- ActiveModel](#activerecord-matchers)
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#L827)**
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 2.6+, Rails
472
- 5.2+, RSpec 3.x, and Minitest 5.x.
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 [Elliot Winkler][mcmire] and [Gui
488
- Albuk][guialbuk].
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 © 2006-2021 Tammer Saleh and [thoughtbot,
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][thoughtbot-logo]
504
-
505
- [thoughtbot-logo]: https://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg
523
+ ![thoughtbot](https://thoughtbot.com/thoughtbot-logo-for-readmes.svg)
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 are passionate about open source software. See [our other
510
- projects][community]. We are [available for hire][hire].
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
- # whitelist of parameters using Rails' Strong Parameters feature
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
- message = 'restrict parameters '
279
+ String.new('restrict parameters ').tap do |message|
280
+ if subparameter_name
281
+ message << "on #{subparameter_name.inspect} "
282
+ end
280
283
 
281
- if subparameter_name
282
- message << "on #{subparameter_name.inspect} "
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
@@ -133,7 +133,7 @@ module Shoulda
133
133
  when :missing then 404
134
134
  when :error then 500..599
135
135
  when Symbol
136
- ::Rack::Utils::SYMBOL_TO_STATUS_CODE[potential_symbol]
136
+ ::Rack::Utils.status_code(potential_symbol)
137
137
  else
138
138
  potential_symbol
139
139
  end
@@ -83,25 +83,23 @@ module Shoulda
83
83
  end
84
84
 
85
85
  def expectation_description
86
- string = 'set'
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 expected_value.is_a?(Regexp)
98
- " to a value matching #{expected_value.inspect}"
88
+ if key_set?
89
+ " #{store.name}[#{key.inspect}]"
99
90
  else
100
- " to #{expected_value.inspect}"
91
+ " any key in #{store.name}"
101
92
  end
102
- end
103
93
 
104
- string
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
- ''.tap do |preface|
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 ||= begin
59
- DisallowValueMatcher.new(disallowed_value).tap do |matcher|
60
- matcher.for(attribute)
61
- wrap_disallow_value_matcher(matcher)
62
- end
58
+ @_disallow_value_matcher ||= DisallowValueMatcher.new(disallowed_value).tap do |matcher|
59
+ matcher.for(attribute)
60
+ wrap_disallow_value_matcher(matcher)
63
61
  end
64
62
  end
65
63
  end
@@ -0,0 +1,71 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Shoulda
4
+ module Matchers
5
+ module ActiveModel
6
+ module NumericalityMatchers
7
+ # @private
8
+ class RangeMatcher < ValidationMatcher
9
+ OPERATORS = [:>=, :<=].freeze
10
+
11
+ delegate :failure_message, to: :submatchers
12
+
13
+ def initialize(numericality_matcher, attribute, range)
14
+ super(attribute)
15
+ unless numericality_matcher.respond_to? :diff_to_compare
16
+ raise ArgumentError, 'numericality_matcher is invalid'
17
+ end
18
+
19
+ @numericality_matcher = numericality_matcher
20
+ @range = range
21
+ @attribute = attribute
22
+ end
23
+
24
+ def matches?(subject)
25
+ @subject = subject
26
+ submatchers.matches?(subject)
27
+ end
28
+
29
+ def simple_description
30
+ description = ''
31
+
32
+ if expects_strict?
33
+ description << ' strictly'
34
+ end
35
+
36
+ description +
37
+ "disallow :#{attribute} from being a number that is not " +
38
+ range_description
39
+ end
40
+
41
+ def range_description
42
+ "from #{Shoulda::Matchers::Util.inspect_range(@range)}"
43
+ end
44
+
45
+ def submatchers
46
+ @_submatchers ||= NumericalityMatchers::Submatchers.new(build_submatchers)
47
+ end
48
+
49
+ private
50
+
51
+ def build_submatchers
52
+ submatcher_combos.map do |value, operator|
53
+ build_comparison_submatcher(value, operator)
54
+ end
55
+ end
56
+
57
+ def submatcher_combos
58
+ @range.minmax.zip(OPERATORS)
59
+ end
60
+
61
+ def build_comparison_submatcher(value, operator)
62
+ 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
@@ -9,5 +9,6 @@ module Shoulda
9
9
  end
10
10
 
11
11
  require_relative 'qualifiers/allow_nil'
12
+ require_relative 'qualifiers/allow_blank'
12
13
  require_relative 'qualifiers/ignore_interference_by_writer'
13
14
  require_relative 'qualifiers/ignoring_interference_by_writer'