shoulda-matchers 5.3.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +26 -9
- data/lib/shoulda/matchers/action_controller/permit_matcher.rb +7 -9
- data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +10 -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/range_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +16 -6
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +0 -6
- data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +532 -0
- data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +3 -3
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +4 -3
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +64 -9
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +40 -96
- 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 +2 -1
- data/lib/shoulda/matchers/active_record/association_matcher.rb +31 -11
- 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 +0 -8
- 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/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/independent/delegate_method_matcher.rb +13 -15
- data/lib/shoulda/matchers/rails_shim.rb +8 -6
- data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
- data/lib/shoulda/matchers/util.rb +17 -19
- data/lib/shoulda/matchers/version.rb +1 -1
- data/lib/shoulda/matchers.rb +2 -2
- data/shoulda-matchers.gemspec +1 -1
- metadata +11 -8
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -136
@@ -0,0 +1,532 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveModel
|
4
|
+
# The `validate_comparison_of` matcher tests usage of the
|
5
|
+
# `validates_comparison_of` validation.
|
6
|
+
#
|
7
|
+
# class Person
|
8
|
+
# include ActiveModel::Model
|
9
|
+
# attr_accessor :gpa
|
10
|
+
#
|
11
|
+
# validates_comparison_of :gpa, greater_than: 10
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # RSpec
|
15
|
+
# RSpec.describe Person, type: :model do
|
16
|
+
# it { should validate_comparison_of(:gpa).greater_than(10) }
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # Minitest (Shoulda)
|
20
|
+
# class PersonTest < ActiveSupport::TestCase
|
21
|
+
# should validate_comparison_of(:gpa).greater_than(10)
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# #### Qualifiers
|
25
|
+
#
|
26
|
+
# ##### on
|
27
|
+
#
|
28
|
+
# Use `on` if your validation applies only under a certain context.
|
29
|
+
#
|
30
|
+
# class Person
|
31
|
+
# include ActiveModel::Model
|
32
|
+
# attribute :number_of_dependents, :integer
|
33
|
+
# attr_accessor :number_of_dependents
|
34
|
+
#
|
35
|
+
# validates_comparison_of :number_of_dependents, on: :create, greater_than: 0
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# # RSpec
|
39
|
+
# RSpec.describe Person, type: :model do
|
40
|
+
# it do
|
41
|
+
# should validate_comparison_of(:number_of_dependents).
|
42
|
+
# greater_than(0).
|
43
|
+
# on(:create)
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# # Minitest (Shoulda)
|
48
|
+
# class PersonTest < ActiveSupport::TestCase
|
49
|
+
# should validate_comparison_of(:number_of_dependents).greater_than(0).on(:create)
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# ##### is_less_than
|
53
|
+
#
|
54
|
+
# Use `is_less_than` to test usage of the the `:less_than` option. This
|
55
|
+
# asserts that the attribute can take a value which is less than the
|
56
|
+
# given value and cannot take a value which is greater than or equal to
|
57
|
+
# it. It can also accept methods or procs that returns a given value.
|
58
|
+
#
|
59
|
+
# class Person
|
60
|
+
# include ActiveModel::Model
|
61
|
+
# attribute :number_of_cars, :integer
|
62
|
+
# attr_accessor :number_of_cars
|
63
|
+
#
|
64
|
+
# validates_comparison_of :number_of_cars, less_than: :current_number_of_cars
|
65
|
+
#
|
66
|
+
# def current_number_of_cars
|
67
|
+
# 10
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# # RSpec
|
72
|
+
# RSpec.describe Person, type: :model do
|
73
|
+
# it do
|
74
|
+
# should validate_comparison_of(:number_of_cars).
|
75
|
+
# is_less_than(:current_number_of_cars)
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# # Minitest (Shoulda)
|
80
|
+
# class PersonTest < ActiveSupport::TestCase
|
81
|
+
# should validate_comparison_of(:number_of_cars).
|
82
|
+
# is_less_than(:current_number_of_cars)
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# ##### is_less_than_or_equal_to
|
86
|
+
#
|
87
|
+
# Use `is_less_than_or_equal_to` to test usage of the
|
88
|
+
# `:less_than_or_equal_to` option. This asserts that the attribute can
|
89
|
+
# take a value which is less than or equal to the given value and cannot
|
90
|
+
# take a value which is greater than it. It can also accept methods or
|
91
|
+
# procs that returns a given value.
|
92
|
+
#
|
93
|
+
# class Person
|
94
|
+
# include ActiveModel::Model
|
95
|
+
# attr_accessor :birth_date
|
96
|
+
#
|
97
|
+
# validates_comparison_of :birth_date, less_than_or_equal_to: Date.new(1987, 12, 31)
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# # RSpec
|
101
|
+
# RSpec.describe Person, type: :model do
|
102
|
+
# it do
|
103
|
+
# should validate_comparison_of(:birth_date).
|
104
|
+
# is_less_than_or_equal_to(Date.new(1987, 12, 31))
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# # Minitest (Shoulda)
|
109
|
+
# class PersonTest < ActiveSupport::TestCase
|
110
|
+
# should validate_comparison_of(:birth_date).
|
111
|
+
# is_less_than_or_equal_to(Date.new(1987, 12, 31))
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# ##### is_greater_than_or_equal_to
|
115
|
+
#
|
116
|
+
# Use `is_greater_than_or_equal_to` to test usage of the
|
117
|
+
# `:greater_than_or_equal_to` option. This asserts that the attribute can
|
118
|
+
# take a value which is greater than or equal to the given value and
|
119
|
+
# cannot take a value which is less than it.
|
120
|
+
#
|
121
|
+
# class Person
|
122
|
+
# include ActiveModel::Model
|
123
|
+
# attribute :birth_date, :date
|
124
|
+
# attr_accessor :birth_date
|
125
|
+
#
|
126
|
+
# validates_comparison_of :birth_date,
|
127
|
+
# greater_than_or_equal_to: -> { 18.years.ago.to_date }
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# # RSpec
|
131
|
+
# RSpec.describe Person, type: :model do
|
132
|
+
# it do
|
133
|
+
# should validate_comparison_of(:birth_date).
|
134
|
+
# is_greater_than_or_equal_to(-> { 18.years.ago.to_date })
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# # Minitest (Shoulda)
|
139
|
+
# class PersonTest < ActiveSupport::TestCase
|
140
|
+
# should validate_comparison_of(:birth_date).
|
141
|
+
# is_greater_than_or_equal_to(-> { 18.years.ago.to_date })
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# ##### is_greater_than
|
145
|
+
#
|
146
|
+
# Use `is_greater_than` to test usage of the `:greater_than` option.
|
147
|
+
# This asserts that the attribute can take a value which is greater than
|
148
|
+
# the given value and cannot take a value less than or equal to it.
|
149
|
+
# It can also accept methods or procs that returns a given value.
|
150
|
+
#
|
151
|
+
# class Person
|
152
|
+
# include ActiveModel::Model
|
153
|
+
# attribute :legal_age, :integer
|
154
|
+
# attr_accessor :legal_age
|
155
|
+
#
|
156
|
+
# validates_comparison_of :legal_age, greater_than: 21
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
# # RSpec
|
160
|
+
# RSpec.describe Person, type: :model do
|
161
|
+
# it do
|
162
|
+
# should validate_comparison_of(:legal_age).
|
163
|
+
# is_greater_than(21)
|
164
|
+
# end
|
165
|
+
# end
|
166
|
+
#
|
167
|
+
# # Minitest (Shoulda)
|
168
|
+
# class PersonTest < ActiveSupport::TestCase
|
169
|
+
# should validate_comparison_of(:legal_age).
|
170
|
+
# is_greater_than(21)
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# ##### is_equal_to
|
174
|
+
#
|
175
|
+
# Use `is_equal_to` to test usage of the `:equal_to` option. This asserts
|
176
|
+
# that the attribute can take a value which is equal to the given value
|
177
|
+
# and cannot take a value which is not equal. It can also accept methods or
|
178
|
+
# procs that returns a given value.
|
179
|
+
#
|
180
|
+
# class Person
|
181
|
+
# include ActiveModel::Model
|
182
|
+
# attribute :favorite_color, :string
|
183
|
+
# attr_accessor :favorite_color
|
184
|
+
#
|
185
|
+
# validates_comparison_of :favorite_color, equal_to: "blue"
|
186
|
+
# end
|
187
|
+
#
|
188
|
+
# # RSpec
|
189
|
+
# RSpec.describe Person, type: :model do
|
190
|
+
# it { should validate_comparison_of(:favorite_color).is_equal_to("blue") }
|
191
|
+
# end
|
192
|
+
#
|
193
|
+
# # Minitest (Shoulda)
|
194
|
+
# class PersonTest < ActiveSupport::TestCase
|
195
|
+
# should validate_comparison_of(:favorite_color).is_equal_to("blue")
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
#
|
199
|
+
# ##### is_other_than
|
200
|
+
#
|
201
|
+
# Use `is_other_than` to test usage of the `:other_than` option.
|
202
|
+
# This asserts that the attribute can take a number which is not equal to
|
203
|
+
# the given value.
|
204
|
+
#
|
205
|
+
# class Person
|
206
|
+
# include ActiveModel::Model
|
207
|
+
# attr_accessor :legal_age
|
208
|
+
#
|
209
|
+
# validates_comparison_of :legal_age, other_than: 21
|
210
|
+
# end
|
211
|
+
#
|
212
|
+
# # RSpec
|
213
|
+
# RSpec.describe Person, type: :model do
|
214
|
+
# it do
|
215
|
+
# should validate_comparison_of(:legal_age).
|
216
|
+
# is_other_than(21)
|
217
|
+
# end
|
218
|
+
# end
|
219
|
+
#
|
220
|
+
# # Minitest (Shoulda)
|
221
|
+
# class PersonTest < ActiveSupport::TestCase
|
222
|
+
# should validate_comparison_of(:legal_age).
|
223
|
+
# is_other_than(21)
|
224
|
+
# end
|
225
|
+
#
|
226
|
+
# ##### with_message
|
227
|
+
#
|
228
|
+
# Use `with_message` if you are using a custom validation message.
|
229
|
+
#
|
230
|
+
# class Person
|
231
|
+
# include ActiveModel::Model
|
232
|
+
# attr_accessor :number_of_dependents
|
233
|
+
#
|
234
|
+
# validates_comparison_of :number_of_dependents, greater_than: 0
|
235
|
+
# message: 'Number of dependents must be a number'
|
236
|
+
# end
|
237
|
+
#
|
238
|
+
# # RSpec
|
239
|
+
# RSpec.describe Person, type: :model do
|
240
|
+
# it do
|
241
|
+
# should validate_comparison_of(:number_of_dependents).
|
242
|
+
# is_greater_than(0).
|
243
|
+
# with_message('Number of dependents must be a number')
|
244
|
+
# end
|
245
|
+
# end
|
246
|
+
#
|
247
|
+
# # Minitest (Shoulda)
|
248
|
+
# class PersonTest < ActiveSupport::TestCase
|
249
|
+
# should validate_comparison_of(:number_of_dependents).
|
250
|
+
# is_greater_than(0).
|
251
|
+
# with_message('Number of dependents must be a number')
|
252
|
+
# end
|
253
|
+
#
|
254
|
+
# ##### allow_nil
|
255
|
+
#
|
256
|
+
# Use `allow_nil` to assert that the attribute allows nil.
|
257
|
+
#
|
258
|
+
# class Post
|
259
|
+
# include ActiveModel::Model
|
260
|
+
# attr_accessor :age
|
261
|
+
#
|
262
|
+
# validates_comparison_of :age, greater_than: 0, allow_nil: true
|
263
|
+
# end
|
264
|
+
#
|
265
|
+
# # RSpec
|
266
|
+
# RSpec.describe Post, type: :model do
|
267
|
+
# it { should validate_comparison_of(:age).is_greater_than(0).allow_nil }
|
268
|
+
# end
|
269
|
+
#
|
270
|
+
# # Minitest (Shoulda)
|
271
|
+
# class PostTest < ActiveSupport::TestCase
|
272
|
+
# should validate_comparison_of(:age).is_greater_than(0).allow_nil
|
273
|
+
# end
|
274
|
+
#
|
275
|
+
# @return [ValidateComparisonOfMatcher]
|
276
|
+
#
|
277
|
+
def validate_comparison_of(attr)
|
278
|
+
ValidateComparisonOfMatcher.new(attr)
|
279
|
+
end
|
280
|
+
|
281
|
+
# @private
|
282
|
+
class ValidateComparisonOfMatcher < ValidationMatcher
|
283
|
+
NUMERIC_NAME = 'number'.freeze
|
284
|
+
DEFAULT_DIFF_TO_COMPARE = 1
|
285
|
+
|
286
|
+
attr_reader :diff_to_compare, :number_of_submatchers
|
287
|
+
|
288
|
+
def initialize(attribute)
|
289
|
+
super
|
290
|
+
@submatchers = []
|
291
|
+
@diff_to_compare = DEFAULT_DIFF_TO_COMPARE
|
292
|
+
@expects_to_allow_nil = false
|
293
|
+
@comparison_submatcher = false
|
294
|
+
end
|
295
|
+
|
296
|
+
def allow_nil
|
297
|
+
@expects_to_allow_nil = true
|
298
|
+
prepare_submatcher(allow_value_matcher(nil))
|
299
|
+
self
|
300
|
+
end
|
301
|
+
|
302
|
+
def expects_to_allow_nil?
|
303
|
+
@expects_to_allow_nil
|
304
|
+
end
|
305
|
+
|
306
|
+
def is_greater_than(value)
|
307
|
+
prepare_submatcher(comparison_matcher_for(value, :>).for(attribute))
|
308
|
+
self
|
309
|
+
end
|
310
|
+
|
311
|
+
def is_greater_than_or_equal_to(value)
|
312
|
+
prepare_submatcher(comparison_matcher_for(value, :>=).for(attribute))
|
313
|
+
self
|
314
|
+
end
|
315
|
+
|
316
|
+
def is_equal_to(value)
|
317
|
+
prepare_submatcher(comparison_matcher_for(value, :==).for(attribute))
|
318
|
+
self
|
319
|
+
end
|
320
|
+
|
321
|
+
def is_less_than(value)
|
322
|
+
prepare_submatcher(comparison_matcher_for(value, :<).for(attribute))
|
323
|
+
self
|
324
|
+
end
|
325
|
+
|
326
|
+
def is_less_than_or_equal_to(value)
|
327
|
+
prepare_submatcher(comparison_matcher_for(value, :<=).for(attribute))
|
328
|
+
self
|
329
|
+
end
|
330
|
+
|
331
|
+
def is_other_than(value)
|
332
|
+
prepare_submatcher(comparison_matcher_for(value, :!=).for(attribute))
|
333
|
+
self
|
334
|
+
end
|
335
|
+
|
336
|
+
def matches?(subject)
|
337
|
+
@subject = subject
|
338
|
+
@number_of_submatchers = @submatchers.size
|
339
|
+
unless @comparison_matcher
|
340
|
+
raise(ArgumentError, "matcher isn't qualified with any comparison matcher")
|
341
|
+
end
|
342
|
+
|
343
|
+
qualify_submatchers
|
344
|
+
first_submatcher_that_fails_to_match.nil?
|
345
|
+
end
|
346
|
+
|
347
|
+
def does_not_match?(subject)
|
348
|
+
@subject = subject
|
349
|
+
@number_of_submatchers = @submatchers.size
|
350
|
+
|
351
|
+
qualify_submatchers
|
352
|
+
first_submatcher_that_fails_to_not_match.nil?
|
353
|
+
end
|
354
|
+
|
355
|
+
def simple_description
|
356
|
+
String.new.tap do |description|
|
357
|
+
description << "validate that :#{attribute} looks like "
|
358
|
+
description << Shoulda::Matchers::Util.a_or_an(allowed_type_name)
|
359
|
+
|
360
|
+
if comparison_descriptions.present?
|
361
|
+
description << " #{comparison_descriptions}"
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def failure_message
|
367
|
+
overall_failure_message.dup.tap do |message|
|
368
|
+
message << "\n"
|
369
|
+
message << failure_message_for_first_submatcher_that_fails_to_match
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def failure_message_when_negated
|
374
|
+
overall_failure_message_when_negated.dup.tap do |message|
|
375
|
+
message << "\n"
|
376
|
+
message <<
|
377
|
+
failure_message_for_first_submatcher_that_fails_to_not_match
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def given_numeric_column?
|
382
|
+
attribute_is_active_record_column? &&
|
383
|
+
[:integer, :float, :decimal].include?(column_type)
|
384
|
+
end
|
385
|
+
|
386
|
+
private
|
387
|
+
|
388
|
+
def attribute_is_active_record_column?
|
389
|
+
columns_hash.key?(attribute.to_s)
|
390
|
+
end
|
391
|
+
|
392
|
+
def column_type
|
393
|
+
columns_hash[attribute.to_s].type
|
394
|
+
end
|
395
|
+
|
396
|
+
def columns_hash
|
397
|
+
if subject.class.respond_to?(:columns_hash)
|
398
|
+
subject.class.columns_hash
|
399
|
+
else
|
400
|
+
{}
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def prepare_submatcher(submatcher)
|
405
|
+
add_submatcher(submatcher)
|
406
|
+
submatcher
|
407
|
+
end
|
408
|
+
|
409
|
+
def comparison_matcher_for(value, operator)
|
410
|
+
@comparison_matcher = true
|
411
|
+
ComparisonMatcher.
|
412
|
+
new(self, value, operator).
|
413
|
+
for(attribute)
|
414
|
+
end
|
415
|
+
|
416
|
+
def add_submatcher(submatcher)
|
417
|
+
@submatchers << submatcher
|
418
|
+
end
|
419
|
+
|
420
|
+
def qualify_submatchers
|
421
|
+
@submatchers.each do |submatcher|
|
422
|
+
if @expects_strict
|
423
|
+
submatcher.strict
|
424
|
+
end
|
425
|
+
|
426
|
+
if @expected_message.present?
|
427
|
+
submatcher.with_message(@expected_message)
|
428
|
+
end
|
429
|
+
|
430
|
+
if @context
|
431
|
+
submatcher.on(@context)
|
432
|
+
end
|
433
|
+
|
434
|
+
submatcher.ignoring_interference_by_writer(
|
435
|
+
ignore_interference_by_writer,
|
436
|
+
)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def number_of_submatchers_for_failure_message
|
441
|
+
if has_been_qualified?
|
442
|
+
number_of_submatchers - 1
|
443
|
+
else
|
444
|
+
number_of_submatchers
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def has_been_qualified?
|
449
|
+
@submatchers.any? { |submatcher| submatcher_qualified?(submatcher) }
|
450
|
+
end
|
451
|
+
|
452
|
+
def submatcher_qualified?(submatcher)
|
453
|
+
submatcher.instance_of?(ComparisonMatcher)
|
454
|
+
end
|
455
|
+
|
456
|
+
def first_submatcher_that_fails_to_match
|
457
|
+
@_first_submatcher_that_fails_to_match ||=
|
458
|
+
@submatchers.detect do |submatcher|
|
459
|
+
!submatcher.matches?(subject)
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
def first_submatcher_that_fails_to_not_match
|
464
|
+
@_first_submatcher_that_fails_to_not_match ||=
|
465
|
+
@submatchers.detect do |submatcher|
|
466
|
+
submatcher.matches?(subject)
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
def failure_message_for_first_submatcher_that_fails_to_match
|
471
|
+
build_submatcher_failure_message_for(
|
472
|
+
first_submatcher_that_fails_to_match,
|
473
|
+
:failure_message,
|
474
|
+
)
|
475
|
+
end
|
476
|
+
|
477
|
+
def failure_message_for_first_submatcher_that_fails_to_not_match
|
478
|
+
build_submatcher_failure_message_for(
|
479
|
+
first_submatcher_that_fails_to_not_match,
|
480
|
+
:failure_message_when_negated,
|
481
|
+
)
|
482
|
+
end
|
483
|
+
|
484
|
+
def build_submatcher_failure_message_for(
|
485
|
+
submatcher,
|
486
|
+
failure_message_method
|
487
|
+
)
|
488
|
+
failure_message = submatcher.public_send(failure_message_method)
|
489
|
+
submatcher_description = submatcher.simple_description.
|
490
|
+
sub(/\bvalidate that\b/, 'validates').
|
491
|
+
sub(/\bdisallow\b/, 'disallows').
|
492
|
+
sub(/\ballow\b/, 'allows')
|
493
|
+
submatcher_message =
|
494
|
+
if number_of_submatchers_for_failure_message > 1
|
495
|
+
"In checking that #{model.name} #{submatcher_description}, " +
|
496
|
+
failure_message[0].downcase +
|
497
|
+
failure_message[1..]
|
498
|
+
else
|
499
|
+
failure_message
|
500
|
+
end
|
501
|
+
|
502
|
+
Shoulda::Matchers.word_wrap(submatcher_message, indent: 2)
|
503
|
+
end
|
504
|
+
|
505
|
+
def comparison_descriptions
|
506
|
+
description_array = submatcher_comparison_descriptions
|
507
|
+
if description_array.empty?
|
508
|
+
''
|
509
|
+
else
|
510
|
+
submatcher_comparison_descriptions.join(' and ')
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
def submatcher_comparison_descriptions
|
515
|
+
@submatchers.inject([]) do |arr, submatcher|
|
516
|
+
arr << if submatcher.respond_to? :comparison_description
|
517
|
+
submatcher.comparison_description
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
def allowed_type_name
|
523
|
+
'value'
|
524
|
+
end
|
525
|
+
|
526
|
+
def non_numeric_value
|
527
|
+
'abcd'
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
@@ -3,10 +3,10 @@ module Shoulda
|
|
3
3
|
module ActiveModel
|
4
4
|
# The `validate_exclusion_of` matcher tests usage of the
|
5
5
|
# `validates_exclusion_of` validation, asserting that an attribute cannot
|
6
|
-
# take a
|
6
|
+
# take a blocklist of values, and inversely, can take values outside of
|
7
7
|
# this list.
|
8
8
|
#
|
9
|
-
# If your
|
9
|
+
# If your blocklist an array of values, use `in_array`:
|
10
10
|
#
|
11
11
|
# class Game
|
12
12
|
# include ActiveModel::Model
|
@@ -29,7 +29,7 @@ module Shoulda
|
|
29
29
|
# in_array(['Mac', 'Linux'])
|
30
30
|
# end
|
31
31
|
#
|
32
|
-
# If your
|
32
|
+
# If your blocklist is a range of values, use `in_range`:
|
33
33
|
#
|
34
34
|
# class Game
|
35
35
|
# include ActiveModel::Model
|
@@ -3,14 +3,15 @@ require 'date'
|
|
3
3
|
|
4
4
|
module Shoulda
|
5
5
|
module Matchers
|
6
|
+
# @private
|
6
7
|
class ExampleClass; end
|
7
8
|
|
8
9
|
module ActiveModel
|
9
10
|
# The `validate_inclusion_of` matcher tests usage of the
|
10
11
|
# `validates_inclusion_of` validation, asserting that an attribute can
|
11
|
-
# take a
|
12
|
+
# take a allowlist of values and cannot take values outside of this list.
|
12
13
|
#
|
13
|
-
# If your
|
14
|
+
# If your allowlist is an array of values, use `in_array`:
|
14
15
|
#
|
15
16
|
# class Issue
|
16
17
|
# include ActiveModel::Model
|
@@ -34,7 +35,7 @@ module Shoulda
|
|
34
35
|
# in_array(['open', 'resolved', 'unresolved'])
|
35
36
|
# end
|
36
37
|
#
|
37
|
-
# If your
|
38
|
+
# If your allowlist is a range of values, use `in_range`:
|
38
39
|
#
|
39
40
|
# class Issue
|
40
41
|
# include ActiveModel::Model
|
@@ -3,7 +3,7 @@ module Shoulda
|
|
3
3
|
module ActiveModel
|
4
4
|
# The `validate_length_of` matcher tests usage of the
|
5
5
|
# `validates_length_of` matcher. Note that this matcher is intended to be
|
6
|
-
# used against string columns and not integer columns.
|
6
|
+
# used against string columns and associations and not integer columns.
|
7
7
|
#
|
8
8
|
# #### Qualifiers
|
9
9
|
#
|
@@ -36,7 +36,8 @@ module Shoulda
|
|
36
36
|
#
|
37
37
|
# Use `is_at_least` to test usage of the `:minimum` option. This asserts
|
38
38
|
# that the attribute can take a string which is equal to or longer than
|
39
|
-
# the given length and cannot take a string which is shorter.
|
39
|
+
# the given length and cannot take a string which is shorter. This qualifier
|
40
|
+
# also works for associations.
|
40
41
|
#
|
41
42
|
# class User
|
42
43
|
# include ActiveModel::Model
|
@@ -61,7 +62,8 @@ module Shoulda
|
|
61
62
|
#
|
62
63
|
# Use `is_at_most` to test usage of the `:maximum` option. This asserts
|
63
64
|
# that the attribute can take a string which is equal to or shorter than
|
64
|
-
# the given length and cannot take a string which is longer.
|
65
|
+
# the given length and cannot take a string which is longer. This qualifier
|
66
|
+
# also works for associations.
|
65
67
|
#
|
66
68
|
# class User
|
67
69
|
# include ActiveModel::Model
|
@@ -84,7 +86,8 @@ module Shoulda
|
|
84
86
|
#
|
85
87
|
# Use `is_equal_to` to test usage of the `:is` option. This asserts that
|
86
88
|
# the attribute can take a string which is exactly equal to the given
|
87
|
-
# length and cannot take a string which is shorter or longer.
|
89
|
+
# length and cannot take a string which is shorter or longer. This qualifier
|
90
|
+
# also works for associations.
|
88
91
|
#
|
89
92
|
# class User
|
90
93
|
# include ActiveModel::Model
|
@@ -106,7 +109,7 @@ module Shoulda
|
|
106
109
|
# ##### is_at_least + is_at_most
|
107
110
|
#
|
108
111
|
# Use `is_at_least` and `is_at_most` together to test usage of the `:in`
|
109
|
-
# option.
|
112
|
+
# option. This qualifies also works for associations.
|
110
113
|
#
|
111
114
|
# class User
|
112
115
|
# include ActiveModel::Model
|
@@ -260,6 +263,29 @@ module Shoulda
|
|
260
263
|
# should validate_length_of(:bio).is_at_least(15).allow_blank
|
261
264
|
# end
|
262
265
|
#
|
266
|
+
# ##### as_array
|
267
|
+
#
|
268
|
+
# Use `as_array` if you have an ActiveModel model and the attribute being validated
|
269
|
+
# is designed to store an array. (This is not necessary if you have an ActiveRecord
|
270
|
+
# model with an array column, as the matcher will detect this automatically.)
|
271
|
+
#
|
272
|
+
# class User
|
273
|
+
# include ActiveModel::Model
|
274
|
+
# attribute :arr, array: true
|
275
|
+
#
|
276
|
+
# validates_length_of :arr, minimum: 15
|
277
|
+
# end
|
278
|
+
#
|
279
|
+
# # RSpec
|
280
|
+
# describe User do
|
281
|
+
# it { should validate_length_of(:arr).as_array.is_at_least(15) }
|
282
|
+
# end
|
283
|
+
#
|
284
|
+
# # Minitest (Shoulda)
|
285
|
+
# class UserTest < ActiveSupport::TestCase
|
286
|
+
# should validate_length_of(:arr).as_array.is_at_least(15)
|
287
|
+
# end
|
288
|
+
#
|
263
289
|
def validate_length_of(attr)
|
264
290
|
ValidateLengthOfMatcher.new(attr)
|
265
291
|
end
|
@@ -275,6 +301,11 @@ module Shoulda
|
|
275
301
|
@long_message = nil
|
276
302
|
end
|
277
303
|
|
304
|
+
def as_array
|
305
|
+
@options[:array] = true
|
306
|
+
self
|
307
|
+
end
|
308
|
+
|
278
309
|
def is_at_least(length)
|
279
310
|
@options[:minimum] = length
|
280
311
|
@short_message ||= :too_short
|
@@ -451,15 +482,39 @@ module Shoulda
|
|
451
482
|
end
|
452
483
|
|
453
484
|
def allows_length_of?(length, message)
|
454
|
-
allows_value_of(
|
485
|
+
allows_value_of(value_of_length(length), message)
|
455
486
|
end
|
456
487
|
|
457
488
|
def disallows_length_of?(length, message)
|
458
|
-
disallows_value_of(
|
489
|
+
disallows_value_of(value_of_length(length), message)
|
490
|
+
end
|
491
|
+
|
492
|
+
def value_of_length(length)
|
493
|
+
if array_column?
|
494
|
+
['x'] * length
|
495
|
+
elsif collection_association?
|
496
|
+
Array.new(length) { association_reflection.klass.new }
|
497
|
+
else
|
498
|
+
'x' * length
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
def array_column?
|
503
|
+
@options[:array] || super
|
504
|
+
end
|
505
|
+
|
506
|
+
def collection_association?
|
507
|
+
association? && [:has_many, :has_and_belongs_to_many].include?(
|
508
|
+
association_reflection.macro,
|
509
|
+
)
|
510
|
+
end
|
511
|
+
|
512
|
+
def association?
|
513
|
+
association_reflection.present?
|
459
514
|
end
|
460
515
|
|
461
|
-
def
|
462
|
-
|
516
|
+
def association_reflection
|
517
|
+
model.try(:reflect_on_association, @attribute)
|
463
518
|
end
|
464
519
|
|
465
520
|
def translated_short_message
|