ultra-fast-box 0.0.1

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 (140) hide show
  1. checksums.yaml +7 -0
  2. data/shoulda-matchers-8.0.1/LICENSE +22 -0
  3. data/shoulda-matchers-8.0.1/README.md +567 -0
  4. data/shoulda-matchers-8.0.1/docs/errors/NonCaseSwappableValueError.md +111 -0
  5. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/callback_matcher.rb +121 -0
  6. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +70 -0
  7. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/flash_store.rb +86 -0
  8. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/permit_matcher.rb +445 -0
  9. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +96 -0
  10. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/render_template_matcher.rb +84 -0
  11. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +145 -0
  12. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +115 -0
  13. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +148 -0
  14. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/route_matcher.rb +207 -0
  15. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/route_params.rb +62 -0
  16. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/session_store.rb +34 -0
  17. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/set_flash_matcher.rb +228 -0
  18. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/set_session_matcher.rb +163 -0
  19. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +107 -0
  20. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller.rb +24 -0
  21. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +45 -0
  22. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error.rb +23 -0
  23. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +232 -0
  24. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +62 -0
  25. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +40 -0
  26. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +48 -0
  27. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb +14 -0
  28. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb +14 -0
  29. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher.rb +695 -0
  30. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
  31. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +78 -0
  32. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/errors.rb +52 -0
  33. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +125 -0
  34. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/helpers.rb +37 -0
  35. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +45 -0
  36. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +67 -0
  37. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +45 -0
  38. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +44 -0
  39. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
  40. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +53 -0
  41. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers.rb +9 -0
  42. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
  43. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/allow_nil.rb +26 -0
  44. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb +101 -0
  45. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +21 -0
  46. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers.rb +14 -0
  47. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +180 -0
  48. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +128 -0
  49. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +567 -0
  50. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +185 -0
  51. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +272 -0
  52. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +664 -0
  53. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +575 -0
  54. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +711 -0
  55. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +428 -0
  56. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +62 -0
  57. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validation_matcher.rb +195 -0
  58. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validation_message_finder.rb +63 -0
  59. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validator.rb +127 -0
  60. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model.rb +96 -0
  61. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +217 -0
  62. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matcher.rb +2134 -0
  63. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +74 -0
  64. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +62 -0
  65. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb +41 -0
  66. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +105 -0
  67. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +101 -0
  68. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +89 -0
  69. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +142 -0
  70. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +71 -0
  71. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +41 -0
  72. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +76 -0
  73. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +42 -0
  74. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +65 -0
  75. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers.rb +9 -0
  76. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +784 -0
  77. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
  78. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_attached_matcher.rb +343 -0
  79. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +366 -0
  80. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +287 -0
  81. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +104 -0
  82. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +95 -0
  83. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_rich_text_matcher.rb +83 -0
  84. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_secure_token_matcher.rb +132 -0
  85. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  86. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/serialize_matcher.rb +203 -0
  87. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/model.rb +58 -0
  88. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/namespace.rb +39 -0
  89. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/test_model_creator.rb +49 -0
  90. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/test_models.rb +55 -0
  91. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness.rb +14 -0
  92. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +1197 -0
  93. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record.rb +37 -0
  94. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/configuration.rb +31 -0
  95. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/double.rb +103 -0
  96. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/double_collection.rb +60 -0
  97. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +31 -0
  98. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/method_call.rb +44 -0
  99. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/object_double.rb +43 -0
  100. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +32 -0
  101. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/stub_implementation.rb +31 -0
  102. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/world.rb +63 -0
  103. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak.rb +38 -0
  104. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/error.rb +29 -0
  105. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/independent/delegate_method_matcher/target_not_defined_error.rb +15 -0
  106. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/independent/delegate_method_matcher.rb +540 -0
  107. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/independent.rb +11 -0
  108. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/configuration.rb +70 -0
  109. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/configuration_error.rb +9 -0
  110. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/inclusion.rb +20 -0
  111. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/action_controller.rb +33 -0
  112. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/active_model.rb +26 -0
  113. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/active_record.rb +26 -0
  114. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/missing_library.rb +19 -0
  115. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/rails.rb +31 -0
  116. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/routing.rb +29 -0
  117. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries.rb +16 -0
  118. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/rails.rb +12 -0
  119. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/registry.rb +28 -0
  120. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/active_support_test_case.rb +37 -0
  121. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/minitest_4.rb +36 -0
  122. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/minitest_5.rb +37 -0
  123. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/missing_test_framework.rb +40 -0
  124. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/rspec.rb +29 -0
  125. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/test_unit.rb +36 -0
  126. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks.rb +16 -0
  127. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations.rb +43 -0
  128. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/matcher_collection.rb +99 -0
  129. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/matcher_context.rb +36 -0
  130. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/rails_shim.rb +190 -0
  131. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/routing.rb +10 -0
  132. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/util/word_wrap.rb +211 -0
  133. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/util.rb +110 -0
  134. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/version.rb +6 -0
  135. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/warn.rb +28 -0
  136. data/shoulda-matchers-8.0.1/lib/shoulda/matchers.rb +25 -0
  137. data/shoulda-matchers-8.0.1/lib/shoulda-matchers.rb +1 -0
  138. data/shoulda-matchers-8.0.1/shoulda-matchers.gemspec +43 -0
  139. data/ultra-fast-box.gemspec +12 -0
  140. metadata +179 -0
@@ -0,0 +1,695 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # The `allow_value` matcher (or its alias, `allow_values`) is used to
5
+ # ensure that an attribute is valid or invalid if set to one or more
6
+ # values.
7
+ #
8
+ # Take this model for example:
9
+ #
10
+ # class UserProfile
11
+ # include ActiveModel::Model
12
+ # attr_accessor :website_url
13
+ #
14
+ # validates_format_of :website_url, with: URI.regexp
15
+ # end
16
+ #
17
+ # You can use `allow_value` to test one value at a time:
18
+ #
19
+ # # RSpec
20
+ # RSpec.describe UserProfile, type: :model do
21
+ # it { should allow_value('https://foo.com').for(:website_url) }
22
+ # it { should allow_value('https://bar.com').for(:website_url) }
23
+ # end
24
+ #
25
+ # # Minitest (Shoulda)
26
+ # class UserProfileTest < ActiveSupport::TestCase
27
+ # should allow_value('https://foo.com').for(:website_url)
28
+ # should allow_value('https://bar.com').for(:website_url)
29
+ # end
30
+ #
31
+ # You can also test multiple values in one go, if you like. In the
32
+ # positive sense, this makes an assertion that none of the values cause the
33
+ # record to be invalid. In the negative sense, this makes an assertion
34
+ # that none of the values cause the record to be valid:
35
+ #
36
+ # # RSpec
37
+ # RSpec.describe UserProfile, type: :model do
38
+ # it do
39
+ # should allow_values('https://foo.com', 'https://bar.com').
40
+ # for(:website_url)
41
+ # end
42
+ #
43
+ # it do
44
+ # should_not allow_values('foo', 'buz').
45
+ # for(:website_url)
46
+ # end
47
+ # end
48
+ #
49
+ # # Minitest (Shoulda)
50
+ # class UserProfileTest < ActiveSupport::TestCase
51
+ # should allow_values('https://foo.com', 'https://bar.com/baz').
52
+ # for(:website_url)
53
+ #
54
+ # should_not allow_values('foo', 'buz').
55
+ # for(:website_url)
56
+ # end
57
+ #
58
+ # #### Caveats
59
+ #
60
+ # When using `allow_value` or any matchers that depend on it, you may
61
+ # encounter an AttributeChangedValueError. This exception is raised if the
62
+ # matcher, in attempting to set a value on the attribute, detects that
63
+ # the value set is different from the value that the attribute returns
64
+ # upon reading it back.
65
+ #
66
+ # This usually happens if the writer method (`foo=`, `bar=`, etc.) for
67
+ # that attribute has custom logic to ignore certain incoming values or
68
+ # change them in any way. Here are three examples we've seen:
69
+ #
70
+ # * You're attempting to assert that an attribute should not allow nil,
71
+ # yet the attribute's writer method contains a conditional to do nothing
72
+ # if the attribute is set to nil:
73
+ #
74
+ # class Foo
75
+ # include ActiveModel::Model
76
+ #
77
+ # attr_reader :bar
78
+ #
79
+ # def bar=(value)
80
+ # return if value.nil?
81
+ # @bar = value
82
+ # end
83
+ # end
84
+ #
85
+ # RSpec.describe Foo, type: :model do
86
+ # it do
87
+ # foo = Foo.new
88
+ # foo.bar = "baz"
89
+ # # This will raise an AttributeChangedValueError since `foo.bar` is now "123"
90
+ # expect(foo).not_to allow_value(nil).for(:bar)
91
+ # end
92
+ # end
93
+ #
94
+ # * You're attempting to assert that a numeric attribute should not allow
95
+ # a string that contains non-numeric characters, yet the writer method
96
+ # for that attribute strips out non-numeric characters:
97
+ #
98
+ # class Foo
99
+ # include ActiveModel::Model
100
+ #
101
+ # attr_reader :bar
102
+ #
103
+ # def bar=(value)
104
+ # @bar = value.gsub(/\D+/, '')
105
+ # end
106
+ # end
107
+ #
108
+ # RSpec.describe Foo, type: :model do
109
+ # it do
110
+ # foo = Foo.new
111
+ # # This will raise an AttributeChangedValueError since `foo.bar` is now "123"
112
+ # expect(foo).not_to allow_value("abc123").for(:bar)
113
+ # end
114
+ # end
115
+ #
116
+ # * You're passing a value to `allow_value` that the model typecasts into
117
+ # another value:
118
+ #
119
+ # RSpec.describe Foo, type: :model do
120
+ # # Assume that `attr` is a string
121
+ # # This will raise an AttributeChangedValueError since `attr` typecasts `[]` to `"[]"`
122
+ # it { should_not allow_value([]).for(:attr) }
123
+ # end
124
+ #
125
+ # Fortunately, if you understand why this is happening, and wish to get
126
+ # around this exception, it is possible to do so. You can use the
127
+ # `ignoring_interference_by_writer` qualifier like so:
128
+ #
129
+ # it do
130
+ # should_not allow_value([]).
131
+ # for(:attr).
132
+ # ignoring_interference_by_writer
133
+ # end
134
+ #
135
+ # Please note, however, that this qualifier won't magically cause your
136
+ # test to pass. It may just so happen that the final value that ends up
137
+ # being set causes the model to fail validation. In that case, you'll have
138
+ # to figure out what to do. You may need to write your own test, or
139
+ # perhaps even remove your test altogether.
140
+ #
141
+ # #### Qualifiers
142
+ #
143
+ # ##### on
144
+ #
145
+ # Use `on` if your validation applies only under a certain context.
146
+ #
147
+ # class UserProfile
148
+ # include ActiveModel::Model
149
+ # attr_accessor :birthday_as_string
150
+ #
151
+ # validates_format_of :birthday_as_string,
152
+ # with: /^(\d+)-(\d+)-(\d+)$/,
153
+ # on: :create
154
+ # end
155
+ #
156
+ # # RSpec
157
+ # RSpec.describe UserProfile, type: :model do
158
+ # it do
159
+ # should allow_value('2013-01-01').
160
+ # for(:birthday_as_string).
161
+ # on(:create)
162
+ # end
163
+ # end
164
+ #
165
+ # # Minitest (Shoulda)
166
+ # class UserProfileTest < ActiveSupport::TestCase
167
+ # should allow_value('2013-01-01').
168
+ # for(:birthday_as_string).
169
+ # on(:create)
170
+ # end
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
+ #
202
+ # ##### with_message
203
+ #
204
+ # Use `with_message` if you are using a custom validation message.
205
+ #
206
+ # class UserProfile
207
+ # include ActiveModel::Model
208
+ # attr_accessor :state
209
+ #
210
+ # validates_format_of :state,
211
+ # with: /^(open|closed)$/,
212
+ # message: 'State must be open or closed'
213
+ # end
214
+ #
215
+ # # RSpec
216
+ # RSpec.describe UserProfile, type: :model do
217
+ # it do
218
+ # should allow_value('open', 'closed').
219
+ # for(:state).
220
+ # with_message('State must be open or closed')
221
+ # end
222
+ # end
223
+ #
224
+ # # Minitest (Shoulda)
225
+ # class UserProfileTest < ActiveSupport::TestCase
226
+ # should allow_value('open', 'closed').
227
+ # for(:state).
228
+ # with_message('State must be open or closed')
229
+ # end
230
+ #
231
+ # Use `with_message` with a regexp to perform a partial match:
232
+ #
233
+ # class UserProfile
234
+ # include ActiveModel::Model
235
+ # attr_accessor :state
236
+ #
237
+ # validates_format_of :state,
238
+ # with: /^(open|closed)$/,
239
+ # message: 'State must be open or closed'
240
+ # end
241
+ #
242
+ # # RSpec
243
+ # RSpec.describe UserProfile, type: :model do
244
+ # it do
245
+ # should allow_value('open', 'closed').
246
+ # for(:state).
247
+ # with_message(/open or closed/)
248
+ # end
249
+ # end
250
+ #
251
+ # # Minitest (Shoulda)
252
+ # class UserProfileTest < ActiveSupport::TestCase
253
+ # should allow_value('open', 'closed').
254
+ # for(:state).
255
+ # with_message(/open or closed/)
256
+ # end
257
+ #
258
+ # Use `with_message` with the `:against` option if the attribute the
259
+ # validation message is stored under is different from the attribute
260
+ # being validated:
261
+ #
262
+ # class UserProfile
263
+ # include ActiveModel::Model
264
+ # attr_accessor :sports_team
265
+ #
266
+ # validate :sports_team_must_be_valid
267
+ #
268
+ # private
269
+ #
270
+ # def sports_team_must_be_valid
271
+ # if sports_team !~ /^(Broncos|Titans)$/i
272
+ # self.errors.add :chosen_sports_team,
273
+ # 'Must be either a Broncos fan or a Titans fan'
274
+ # end
275
+ # end
276
+ # end
277
+ #
278
+ # # RSpec
279
+ # RSpec.describe UserProfile, type: :model do
280
+ # it do
281
+ # should allow_value('Broncos', 'Titans').
282
+ # for(:sports_team).
283
+ # with_message('Must be either a Broncos or Titans fan',
284
+ # against: :chosen_sports_team
285
+ # )
286
+ # end
287
+ # end
288
+ #
289
+ # # Minitest (Shoulda)
290
+ # class UserProfileTest < ActiveSupport::TestCase
291
+ # should allow_value('Broncos', 'Titans').
292
+ # for(:sports_team).
293
+ # with_message('Must be either a Broncos or Titans fan',
294
+ # against: :chosen_sports_team
295
+ # )
296
+ # end
297
+ #
298
+ # ##### ignoring_interference_by_writer
299
+ #
300
+ # Use `ignoring_interference_by_writer` to bypass an
301
+ # AttributeChangedValueError that you have encountered. Please read the
302
+ # Caveats section above for more information.
303
+ #
304
+ # class Address < ActiveRecord::Base
305
+ # # Address has a zip_code field which is a string
306
+ # end
307
+ #
308
+ # # RSpec
309
+ # RSpec.describe Address, type: :model do
310
+ # it do
311
+ # should_not allow_value([]).
312
+ # for(:zip_code).
313
+ # ignoring_interference_by_writer
314
+ # end
315
+ # end
316
+ #
317
+ # # Minitest (Shoulda)
318
+ # class AddressTest < ActiveSupport::TestCase
319
+ # should_not allow_value([]).
320
+ # for(:zip_code).
321
+ # ignoring_interference_by_writer
322
+ # end
323
+ #
324
+ # @return [AllowValueMatcher]
325
+ #
326
+ def allow_value(*values)
327
+ if values.empty?
328
+ raise ArgumentError, 'need at least one argument'
329
+ else
330
+ AllowValueMatcher.new(*values)
331
+ end
332
+ end
333
+ # @private
334
+ alias_method :allow_values, :allow_value
335
+
336
+ # @private
337
+ class AllowValueMatcher
338
+ include Helpers
339
+ include Qualifiers::IgnoringInterferenceByWriter
340
+
341
+ attr_reader(
342
+ :after_setting_value_callback,
343
+ :attribute_to_check_message_against,
344
+ :attribute_to_set,
345
+ :context,
346
+ :instance,
347
+ )
348
+
349
+ attr_writer(
350
+ :attribute_changed_value_message,
351
+ :failure_message_preface,
352
+ :values_to_preset,
353
+ )
354
+
355
+ def initialize(*values)
356
+ super
357
+ @values_to_set = values
358
+ @options = {}
359
+ @after_setting_value_callback = -> {}
360
+ @expects_strict = false
361
+ @expects_custom_validation_message = false
362
+ @context = nil
363
+ @values_to_preset = {}
364
+ @failure_message_preface = nil
365
+ @attribute_changed_value_message = nil
366
+ end
367
+
368
+ def for(attribute_name)
369
+ @attribute_to_set = attribute_name
370
+ @attribute_to_check_message_against = attribute_name
371
+ self
372
+ end
373
+
374
+ def on(context)
375
+ if context.present?
376
+ @context = context
377
+ end
378
+
379
+ self
380
+ end
381
+
382
+ def against(attribute)
383
+ @attribute_to_check_message_against = attribute if attribute.present?
384
+
385
+ self
386
+ end
387
+
388
+ def with_message(message, given_options = {})
389
+ if message.present?
390
+ @expects_custom_validation_message = true
391
+ options[:expected_message] = message
392
+ options[:expected_message_values] = given_options.fetch(:values, {})
393
+
394
+ if given_options.key?(:against)
395
+ @attribute_to_check_message_against = given_options[:against]
396
+ end
397
+ end
398
+
399
+ self
400
+ end
401
+
402
+ def expected_message
403
+ if options.key?(:expected_message)
404
+ if Symbol === options[:expected_message]
405
+ default_expected_message
406
+ else
407
+ options[:expected_message]
408
+ end
409
+ end
410
+ end
411
+
412
+ def expects_custom_validation_message?
413
+ @expects_custom_validation_message
414
+ end
415
+
416
+ def strict(expects_strict = true)
417
+ @expects_strict = expects_strict
418
+ self
419
+ end
420
+
421
+ def expects_strict?
422
+ @expects_strict
423
+ end
424
+
425
+ def _after_setting_value(&callback)
426
+ @after_setting_value_callback = callback
427
+ end
428
+
429
+ def matches?(instance)
430
+ @instance = instance
431
+ @result = run(:first_failing)
432
+ @result.nil?
433
+ end
434
+
435
+ def does_not_match?(instance)
436
+ @instance = instance
437
+ @result = run(:first_passing)
438
+ @result.nil?
439
+ end
440
+
441
+ def has_any_errors?
442
+ validator.record.errors.any?
443
+ end
444
+
445
+ def failure_message
446
+ attribute_setter = result.attribute_setter
447
+
448
+ if result.attribute_setter.unsuccessfully_checked?
449
+ message = attribute_setter.failure_message
450
+ else
451
+ validator = result.validator
452
+ message = failure_message_preface.call
453
+ message << ' valid, but it was invalid instead,'
454
+
455
+ if validator.captured_validation_exception?
456
+ message << ' raising a validation exception with the message '
457
+ message << validator.validation_exception_message.inspect
458
+ message << '.'
459
+ else
460
+ message << " producing these validation errors:\n\n"
461
+ message << validator.formatted_validation_error_messages
462
+ end
463
+ end
464
+
465
+ if include_attribute_changed_value_message?
466
+ message << "\n\n#{attribute_changed_value_message.call}"
467
+ end
468
+
469
+ Shoulda::Matchers.word_wrap(message)
470
+ end
471
+
472
+ def failure_message_when_negated # rubocop:disable Metrics/MethodLength
473
+ attribute_setter = result.attribute_setter
474
+
475
+ if attribute_setter.unsuccessfully_checked?
476
+ message = attribute_setter.failure_message
477
+ else
478
+ validator = result.validator
479
+ message = "#{failure_message_preface.call} invalid"
480
+
481
+ if validator.type_of_message_matched?
482
+ if validator.has_messages?
483
+ message << ' and to'
484
+
485
+ if validator.captured_validation_exception? # rubocop:disable Metrics/BlockNesting
486
+ message << ' raise a validation exception with message'
487
+ else
488
+ message << ' produce'
489
+
490
+ message <<
491
+ if expected_message.is_a?(Regexp) # rubocop:disable Metrics/BlockNesting
492
+ ' a'
493
+ else
494
+ ' the'
495
+ end
496
+
497
+ message << ' validation error'
498
+ end
499
+
500
+ if expected_message.is_a?(Regexp) # rubocop:disable Metrics/BlockNesting
501
+ message << ' matching '
502
+ message << Shoulda::Matchers::Util.inspect_value(
503
+ expected_message,
504
+ )
505
+ else
506
+ message << " #{expected_message.inspect}"
507
+ end
508
+
509
+ unless validator.captured_validation_exception? # rubocop:disable Metrics/BlockNesting
510
+ message << " on :#{attribute_to_check_message_against}"
511
+ end
512
+
513
+ message << '. The record was indeed invalid, but'
514
+
515
+ if validator.captured_validation_exception? # rubocop:disable Metrics/BlockNesting
516
+ message << ' the exception message was '
517
+ message << validator.validation_exception_message.inspect
518
+ message << ' instead.'
519
+ else
520
+ message << " it produced these validation errors instead:\n\n"
521
+ message << validator.all_formatted_validation_error_messages
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
528
+ else
529
+ message << ', but it was valid instead.'
530
+ end
531
+ elsif validator.captured_validation_exception?
532
+ message << ' and to produce validation errors, but the record'
533
+ message << ' raised a validation exception instead.'
534
+ else
535
+ message << ' and to raise a validation exception, but the record'
536
+ message << ' produced validation errors instead.'
537
+ end
538
+ end
539
+
540
+ if include_attribute_changed_value_message?
541
+ message << "\n\n#{attribute_changed_value_message.call}"
542
+ end
543
+
544
+ Shoulda::Matchers.word_wrap(message)
545
+ end
546
+
547
+ def description
548
+ ValidationMatcher::BuildDescription.call(self, simple_description)
549
+ end
550
+
551
+ def simple_description
552
+ "allow :#{attribute_to_set} to be #{inspected_values_to_set}"
553
+ end
554
+
555
+ def model
556
+ instance.class
557
+ end
558
+
559
+ def last_attribute_setter_used
560
+ result.attribute_setter
561
+ end
562
+
563
+ def last_value_set
564
+ last_attribute_setter_used.value_written
565
+ end
566
+
567
+ protected
568
+
569
+ attr_reader(
570
+ :options,
571
+ :result,
572
+ :values_to_preset,
573
+ :values_to_set,
574
+ )
575
+
576
+ private
577
+
578
+ def run(strategy)
579
+ attribute_setters_for_values_to_preset.first_failing ||
580
+ attribute_setters_and_validators_for_values_to_set.
581
+ public_send(strategy)
582
+ end
583
+
584
+ def failure_message_preface
585
+ @failure_message_preface || method(:default_failure_message_preface)
586
+ end
587
+
588
+ def default_failure_message_preface
589
+ String.new.tap do |preface|
590
+ if descriptions_for_preset_values.any?
591
+ preface << 'After setting '
592
+ preface << descriptions_for_preset_values.to_sentence
593
+ preface << ', then '
594
+ else
595
+ preface << 'After '
596
+ end
597
+
598
+ preface << 'setting '
599
+ preface << description_for_resulting_attribute_setter
600
+
601
+ unless preface.end_with?('--')
602
+ preface << ','
603
+ end
604
+
605
+ preface << " the matcher expected the #{model.name} to be"
606
+ end
607
+ end
608
+
609
+ def include_attribute_changed_value_message?
610
+ !ignore_interference_by_writer.never? &&
611
+ result.attribute_setter.attribute_changed_value?
612
+ end
613
+
614
+ def attribute_changed_value_message
615
+ @attribute_changed_value_message ||
616
+ method(:default_attribute_changed_value_message)
617
+ end
618
+
619
+ def default_attribute_changed_value_message
620
+ <<-MESSAGE.strip
621
+ As indicated in the message above, :#{result.attribute_setter.attribute_name}
622
+ seems to be changing certain values as they are set, and this could have
623
+ something to do with why this test is failing. If you've overridden the writer
624
+ method for this attribute, then you may need to change it to make this test
625
+ pass, or do something else entirely.
626
+ MESSAGE
627
+ end
628
+
629
+ def descriptions_for_preset_values
630
+ attribute_setters_for_values_to_preset.
631
+ map(&:attribute_setter_description)
632
+ end
633
+
634
+ def description_for_resulting_attribute_setter
635
+ result.attribute_setter_description
636
+ end
637
+
638
+ def attribute_setters_for_values_to_preset
639
+ @_attribute_setters_for_values_to_preset ||=
640
+ AttributeSetters.new(self, values_to_preset)
641
+ end
642
+
643
+ def attribute_setters_and_validators_for_values_to_set
644
+ @_attribute_setters_and_validators_for_values_to_set ||=
645
+ AttributeSettersAndValidators.new(
646
+ self,
647
+ values_to_set.map { |value| [attribute_to_set, value] },
648
+ )
649
+ end
650
+
651
+ def inspected_values_to_set
652
+ Shoulda::Matchers::Util.inspect_values(values_to_set).to_sentence(
653
+ two_words_connector: ' or ',
654
+ last_word_connector: ', or ',
655
+ )
656
+ end
657
+
658
+ def default_expected_message
659
+ if expects_strict?
660
+ "#{human_attribute_name} #{default_attribute_message}"
661
+ else
662
+ default_attribute_message
663
+ end
664
+ end
665
+
666
+ def default_attribute_message
667
+ default_error_message(
668
+ options[:expected_message],
669
+ default_attribute_message_values,
670
+ )
671
+ end
672
+
673
+ def default_attribute_message_values
674
+ defaults = {
675
+ model_name:,
676
+ instance:,
677
+ attribute: attribute_to_check_message_against,
678
+ }
679
+
680
+ defaults.merge!(options[:expected_message_values])
681
+ end
682
+
683
+ def model_name
684
+ instance.class.to_s.underscore
685
+ end
686
+
687
+ def human_attribute_name
688
+ instance.class.human_attribute_name(
689
+ attribute_to_check_message_against,
690
+ )
691
+ end
692
+ end
693
+ end
694
+ end
695
+ end