shoulda-matchers 3.0.1 → 3.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/.gitignore +1 -0
- data/.travis.yml +3 -3
- data/CONTRIBUTING.md +60 -28
- data/Gemfile +1 -0
- data/Gemfile.lock +15 -12
- data/NEWS.md +111 -0
- data/README.md +94 -6
- data/Rakefile +10 -8
- data/custom_plan.rb +88 -0
- data/gemfiles/4.0.0.gemfile +1 -0
- data/gemfiles/4.0.0.gemfile.lock +21 -18
- data/gemfiles/4.0.1.gemfile +1 -0
- data/gemfiles/4.0.1.gemfile.lock +21 -18
- data/gemfiles/4.1.gemfile +1 -0
- data/gemfiles/4.1.gemfile.lock +21 -18
- data/gemfiles/4.2.gemfile +1 -0
- data/gemfiles/4.2.gemfile.lock +24 -21
- data/lib/shoulda/matchers/action_controller/permit_matcher.rb +6 -11
- data/lib/shoulda/matchers/active_model.rb +10 -1
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +258 -180
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +45 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error.rb +23 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +236 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +62 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +40 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +48 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb +14 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb +14 -0
- data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +34 -14
- data/lib/shoulda/matchers/active_model/helpers.rb +9 -17
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +13 -6
- data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +13 -2
- data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +19 -35
- data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +13 -2
- data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +12 -2
- data/lib/shoulda/matchers/active_model/qualifiers.rb +12 -0
- data/lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb +101 -0
- data/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +21 -0
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +30 -32
- data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +5 -8
- data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +22 -22
- data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +27 -16
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +58 -15
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +22 -12
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +165 -87
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +7 -9
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +111 -49
- data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +60 -0
- data/lib/shoulda/matchers/active_model/validator.rb +71 -52
- data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +19 -5
- data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +450 -124
- data/lib/shoulda/matchers/util.rb +43 -0
- data/lib/shoulda/matchers/util/word_wrap.rb +59 -31
- data/lib/shoulda/matchers/version.rb +1 -1
- data/script/update_gem_in_all_appraisals +1 -1
- data/script/update_gems_in_all_appraisals +1 -1
- data/spec/acceptance/multiple_libraries_integration_spec.rb +5 -2
- data/spec/acceptance/rails_integration_spec.rb +6 -2
- data/spec/spec_helper.rb +1 -3
- data/spec/support/acceptance/helpers/step_helpers.rb +4 -1
- data/spec/support/tests/current_bundle.rb +21 -7
- data/spec/support/unit/active_record/create_table.rb +54 -0
- data/spec/support/unit/attribute.rb +47 -0
- data/spec/support/unit/capture.rb +6 -0
- data/spec/support/unit/change_value.rb +111 -0
- data/spec/support/unit/create_model_arguments/basic.rb +135 -0
- data/spec/support/unit/create_model_arguments/has_many.rb +15 -0
- data/spec/support/unit/create_model_arguments/uniqueness_matcher.rb +74 -0
- data/spec/support/unit/helpers/active_record_versions.rb +1 -1
- data/spec/support/unit/helpers/class_builder.rb +61 -47
- data/spec/support/unit/helpers/database_helpers.rb +5 -3
- data/spec/support/unit/helpers/model_builder.rb +77 -97
- data/spec/support/unit/helpers/validation_matcher_scenario_helpers.rb +44 -0
- data/spec/support/unit/load_environment.rb +12 -0
- data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +2 -2
- data/spec/support/unit/matchers/fail_with_message_matcher.rb +12 -1
- data/spec/support/unit/model_creation_strategies/active_model.rb +111 -0
- data/spec/support/unit/model_creation_strategies/active_record.rb +77 -0
- data/spec/support/unit/model_creators.rb +19 -0
- data/spec/support/unit/model_creators/active_model.rb +39 -0
- data/spec/support/unit/model_creators/active_record.rb +43 -0
- data/spec/support/unit/model_creators/active_record/has_and_belongs_to_many.rb +95 -0
- data/spec/support/unit/model_creators/active_record/has_many.rb +67 -0
- data/spec/support/unit/model_creators/active_record/uniqueness_matcher.rb +42 -0
- data/spec/support/unit/model_creators/basic.rb +97 -0
- data/spec/support/unit/rails_application.rb +1 -1
- data/spec/support/unit/record_validating_confirmation_builder.rb +3 -7
- data/spec/support/unit/shared_examples/ignoring_interference_by_writer.rb +79 -0
- data/spec/support/unit/validation_matcher_scenario.rb +62 -0
- data/spec/unit/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +4 -0
- data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +575 -140
- data/spec/unit/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +115 -15
- data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +42 -4
- data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +92 -6
- data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +122 -10
- data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +306 -58
- data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +122 -3
- data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +805 -131
- data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +196 -29
- data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +82 -40
- data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +600 -101
- data/spec/unit/shoulda/matchers/util/word_wrap_spec.rb +88 -33
- data/spec/unit_spec_helper.rb +10 -22
- data/zeus.json +11 -0
- metadata +64 -23
- data/lib/shoulda/matchers/active_model/strict_validator.rb +0 -51
- data/spec/support/unit/shared_examples/numerical_type_submatcher.rb +0 -15
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +0 -288
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +0 -100
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +0 -100
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +0 -100
@@ -57,7 +57,7 @@ module Shoulda
|
|
57
57
|
#
|
58
58
|
# Failures:
|
59
59
|
#
|
60
|
-
# 1) Post should
|
60
|
+
# 1) Post should validate :title to be case-sensitively unique
|
61
61
|
# Failure/Error: it { should validate_uniqueness_of(:title) }
|
62
62
|
# ActiveRecord::StatementInvalid:
|
63
63
|
# SQLite3::ConstraintException: posts.content may not be NULL: INSERT INTO "posts" ("title") VALUES (?)
|
@@ -133,7 +133,7 @@ module Shoulda
|
|
133
133
|
# unique, but the scoped attributes are not unique either.
|
134
134
|
#
|
135
135
|
# class Post < ActiveRecord::Base
|
136
|
-
# validates_uniqueness_of :slug, scope: :
|
136
|
+
# validates_uniqueness_of :slug, scope: :journal_id
|
137
137
|
# end
|
138
138
|
#
|
139
139
|
# # RSpec
|
@@ -167,6 +167,40 @@ module Shoulda
|
|
167
167
|
# should validate_uniqueness_of(:key).case_insensitive
|
168
168
|
# end
|
169
169
|
#
|
170
|
+
# ##### ignoring_case_sensitivity
|
171
|
+
#
|
172
|
+
# By default, `validate_uniqueness_of` will check that the
|
173
|
+
# validation is case sensitive: it asserts that uniquable attributes pass
|
174
|
+
# validation when their values are in a different case than corresponding
|
175
|
+
# attributes in the pre-existing record.
|
176
|
+
#
|
177
|
+
# Use `ignoring_case_sensitivity` to skip this check. This qualifier is
|
178
|
+
# particularly handy if your model has somehow changed the behavior of
|
179
|
+
# attribute you're testing so that it modifies the case of incoming values
|
180
|
+
# as they are set. For instance, perhaps you've overridden the writer
|
181
|
+
# method or added a `before_validation` callback to normalize the
|
182
|
+
# attribute.
|
183
|
+
#
|
184
|
+
# class User < ActiveRecord::Base
|
185
|
+
# validates_uniqueness_of :email
|
186
|
+
#
|
187
|
+
# def email=(value)
|
188
|
+
# super(value.downcase)
|
189
|
+
# end
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
# # RSpec
|
193
|
+
# describe Post do
|
194
|
+
# it do
|
195
|
+
# should validate_uniqueness_of(:email).ignoring_case_sensitivity
|
196
|
+
# end
|
197
|
+
# end
|
198
|
+
#
|
199
|
+
# # Minitest (Shoulda)
|
200
|
+
# class PostTest < ActiveSupport::TestCase
|
201
|
+
# should validate_uniqueness_of(:email).ignoring_case_sensitivity
|
202
|
+
# end
|
203
|
+
#
|
170
204
|
# ##### allow_nil
|
171
205
|
#
|
172
206
|
# Use `allow_nil` to assert that the attribute allows nil.
|
@@ -217,7 +251,17 @@ module Shoulda
|
|
217
251
|
|
218
252
|
def initialize(attribute)
|
219
253
|
super(attribute)
|
220
|
-
@
|
254
|
+
@expected_message = :taken
|
255
|
+
@options = {
|
256
|
+
case_sensitivity_strategy: :sensitive
|
257
|
+
}
|
258
|
+
@existing_record_created = false
|
259
|
+
@failure_reason = nil
|
260
|
+
@failure_reason_when_negated = nil
|
261
|
+
@attribute_setters = {
|
262
|
+
existing_record: [],
|
263
|
+
new_record: []
|
264
|
+
}
|
221
265
|
end
|
222
266
|
|
223
267
|
def scoped_to(*scopes)
|
@@ -225,13 +269,13 @@ module Shoulda
|
|
225
269
|
self
|
226
270
|
end
|
227
271
|
|
228
|
-
def
|
229
|
-
@
|
272
|
+
def case_insensitive
|
273
|
+
@options[:case_sensitivity_strategy] = :insensitive
|
230
274
|
self
|
231
275
|
end
|
232
276
|
|
233
|
-
def
|
234
|
-
@options[:
|
277
|
+
def ignoring_case_sensitivity
|
278
|
+
@options[:case_sensitivity_strategy] = :ignore
|
235
279
|
self
|
236
280
|
end
|
237
281
|
|
@@ -240,28 +284,40 @@ module Shoulda
|
|
240
284
|
self
|
241
285
|
end
|
242
286
|
|
287
|
+
def expects_to_allow_nil?
|
288
|
+
@options[:allow_nil]
|
289
|
+
end
|
290
|
+
|
243
291
|
def allow_blank
|
244
292
|
@options[:allow_blank] = true
|
245
293
|
self
|
246
294
|
end
|
247
295
|
|
248
|
-
def
|
249
|
-
|
250
|
-
result << "case sensitive " unless @options[:case_insensitive]
|
251
|
-
result << "unique value for #{@attribute}"
|
252
|
-
result << " scoped to #{@options[:scopes].join(', ')}" if @options[:scopes].present?
|
253
|
-
result
|
296
|
+
def expects_to_allow_blank?
|
297
|
+
@options[:allow_blank]
|
254
298
|
end
|
255
299
|
|
256
|
-
def
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
@all_records = @subject.class.all
|
300
|
+
def simple_description
|
301
|
+
description = "validate that :#{@attribute} is"
|
302
|
+
description << description_for_case_sensitive_qualifier
|
303
|
+
description << ' unique'
|
261
304
|
|
262
|
-
|
263
|
-
|
264
|
-
|
305
|
+
if @options[:scopes].present?
|
306
|
+
description << " within the scope of #{inspected_expected_scopes}"
|
307
|
+
end
|
308
|
+
|
309
|
+
description
|
310
|
+
end
|
311
|
+
|
312
|
+
def matches?(given_record)
|
313
|
+
@given_record = given_record
|
314
|
+
@all_records = model.all
|
315
|
+
|
316
|
+
existing_record_valid? &&
|
317
|
+
validate_attribute_present? &&
|
318
|
+
validate_scopes_present? &&
|
319
|
+
scopes_match? &&
|
320
|
+
validate_two_records_with_same_non_blank_value_cannot_coexist? &&
|
265
321
|
validate_case_sensitivity? &&
|
266
322
|
validate_after_scope_change? &&
|
267
323
|
allows_nil? &&
|
@@ -270,42 +326,102 @@ module Shoulda
|
|
270
326
|
Uniqueness::TestModels.remove_all
|
271
327
|
end
|
272
328
|
|
273
|
-
|
329
|
+
protected
|
274
330
|
|
275
|
-
def
|
276
|
-
@
|
277
|
-
|
331
|
+
def failure_reason
|
332
|
+
@failure_reason || super
|
333
|
+
end
|
334
|
+
|
335
|
+
def failure_reason_when_negated
|
336
|
+
@failure_reason_when_negated || super
|
337
|
+
end
|
338
|
+
|
339
|
+
def build_allow_or_disallow_value_matcher(args)
|
340
|
+
super.tap do |matcher|
|
341
|
+
matcher.failure_message_preface = method(:failure_message_preface)
|
342
|
+
matcher.attribute_changed_value_message =
|
343
|
+
method(:attribute_changed_value_message)
|
278
344
|
end
|
279
345
|
end
|
280
346
|
|
281
|
-
|
282
|
-
expected_scopes = Array.wrap(@options[:scopes])
|
347
|
+
private
|
283
348
|
|
284
|
-
|
285
|
-
|
349
|
+
def case_sensitivity_strategy
|
350
|
+
@options[:case_sensitivity_strategy]
|
351
|
+
end
|
352
|
+
|
353
|
+
def new_record
|
354
|
+
unless defined?(@new_record)
|
355
|
+
build_new_record
|
356
|
+
end
|
357
|
+
|
358
|
+
@new_record
|
359
|
+
end
|
360
|
+
alias_method :subject, :new_record
|
361
|
+
|
362
|
+
def description_for_case_sensitive_qualifier
|
363
|
+
case case_sensitivity_strategy
|
364
|
+
when :sensitive
|
365
|
+
' case-sensitively'
|
366
|
+
when :insensitive
|
367
|
+
' case-insensitively'
|
286
368
|
else
|
287
|
-
|
369
|
+
''
|
288
370
|
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def validation
|
374
|
+
model._validators[@attribute].detect do |validator|
|
375
|
+
validator.is_a?(::ActiveRecord::Validations::UniquenessValidator)
|
376
|
+
end
|
377
|
+
end
|
289
378
|
|
379
|
+
def scopes_match?
|
290
380
|
if expected_scopes == actual_scopes
|
291
381
|
true
|
292
382
|
else
|
293
|
-
@
|
294
|
-
"#{expected_scopes}"
|
383
|
+
@failure_reason = 'Expected the validation'
|
295
384
|
|
296
|
-
if
|
297
|
-
@
|
385
|
+
if expected_scopes.empty?
|
386
|
+
@failure_reason << ' not to be scoped to anything'
|
298
387
|
else
|
299
|
-
@
|
388
|
+
@failure_reason << " to be scoped to #{inspected_expected_scopes}"
|
389
|
+
end
|
390
|
+
|
391
|
+
if actual_scopes.empty?
|
392
|
+
@failure_reason << ', but it was not scoped to anything.'
|
393
|
+
else
|
394
|
+
@failure_reason << ', but it was scoped to '
|
395
|
+
@failure_reason << "#{inspected_actual_scopes} instead."
|
300
396
|
end
|
301
397
|
|
302
398
|
false
|
303
399
|
end
|
304
400
|
end
|
305
401
|
|
402
|
+
def expected_scopes
|
403
|
+
Array.wrap(@options[:scopes])
|
404
|
+
end
|
405
|
+
|
406
|
+
def inspected_expected_scopes
|
407
|
+
expected_scopes.map(&:inspect).to_sentence
|
408
|
+
end
|
409
|
+
|
410
|
+
def actual_scopes
|
411
|
+
if validation
|
412
|
+
Array.wrap(validation.options[:scope])
|
413
|
+
else
|
414
|
+
[]
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
def inspected_actual_scopes
|
419
|
+
actual_scopes.map(&:inspect).to_sentence
|
420
|
+
end
|
421
|
+
|
306
422
|
def allows_nil?
|
307
|
-
if
|
308
|
-
|
423
|
+
if expects_to_allow_nil?
|
424
|
+
update_existing_record!(nil)
|
309
425
|
allows_value_of(nil, @expected_message)
|
310
426
|
else
|
311
427
|
true
|
@@ -313,48 +429,69 @@ module Shoulda
|
|
313
429
|
end
|
314
430
|
|
315
431
|
def allows_blank?
|
316
|
-
if
|
317
|
-
|
432
|
+
if expects_to_allow_blank?
|
433
|
+
update_existing_record!('')
|
318
434
|
allows_value_of('', @expected_message)
|
319
435
|
else
|
320
436
|
true
|
321
437
|
end
|
322
438
|
end
|
323
439
|
|
324
|
-
def
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
440
|
+
def existing_record_valid?
|
441
|
+
if existing_record.valid?
|
442
|
+
true
|
443
|
+
else
|
444
|
+
@failure_reason =
|
445
|
+
"The record you provided could not be created, " +
|
446
|
+
"as it failed with the following validation errors:\n\n" +
|
447
|
+
format_validation_errors(existing_record.errors)
|
448
|
+
false
|
449
|
+
end
|
330
450
|
end
|
331
451
|
|
332
|
-
def
|
333
|
-
unless
|
334
|
-
|
452
|
+
def existing_record
|
453
|
+
unless defined?(@existing_record)
|
454
|
+
find_or_create_existing_record
|
335
455
|
end
|
456
|
+
|
457
|
+
@existing_record
|
336
458
|
end
|
337
459
|
|
338
|
-
def
|
339
|
-
|
340
|
-
|
460
|
+
def find_or_create_existing_record
|
461
|
+
@existing_record = find_existing_record
|
462
|
+
|
463
|
+
unless @existing_record
|
464
|
+
@existing_record = create_existing_record
|
465
|
+
@existing_record_created = true
|
341
466
|
end
|
342
467
|
end
|
343
468
|
|
344
|
-
def
|
345
|
-
|
469
|
+
def find_existing_record
|
470
|
+
record = model.first
|
471
|
+
|
472
|
+
if record.present?
|
473
|
+
record
|
474
|
+
else
|
475
|
+
nil
|
476
|
+
end
|
346
477
|
end
|
347
478
|
|
348
|
-
def
|
349
|
-
@
|
479
|
+
def create_existing_record
|
480
|
+
@given_record.tap do |existing_record|
|
481
|
+
ensure_secure_password_set(existing_record)
|
482
|
+
existing_record.save
|
483
|
+
end
|
350
484
|
end
|
351
485
|
|
352
|
-
def
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
486
|
+
def update_existing_record!(value)
|
487
|
+
if existing_value_read != value
|
488
|
+
set_attribute_on!(
|
489
|
+
:existing_record,
|
490
|
+
existing_record,
|
491
|
+
@attribute,
|
492
|
+
value
|
493
|
+
)
|
494
|
+
existing_record.save!
|
358
495
|
end
|
359
496
|
end
|
360
497
|
|
@@ -365,70 +502,117 @@ module Shoulda
|
|
365
502
|
end
|
366
503
|
end
|
367
504
|
|
368
|
-
def
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
505
|
+
def arbitrary_non_blank_value
|
506
|
+
limit = column_limit_for(@attribute)
|
507
|
+
non_blank_value = 'an arbitrary value'
|
508
|
+
|
509
|
+
if limit && limit < non_blank_value.length
|
510
|
+
'x' * limit
|
511
|
+
else
|
512
|
+
non_blank_value
|
373
513
|
end
|
374
514
|
end
|
375
515
|
|
376
516
|
def has_secure_password?
|
377
|
-
|
517
|
+
model.ancestors.map(&:to_s).include?(
|
518
|
+
'ActiveModel::SecurePassword::InstanceMethodsOnActivation'
|
519
|
+
)
|
378
520
|
end
|
379
521
|
|
380
|
-
def
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
522
|
+
def build_new_record
|
523
|
+
@new_record = existing_record.dup
|
524
|
+
|
525
|
+
attribute_names_under_test.each do |attribute_name|
|
526
|
+
set_attribute_on_new_record!(
|
527
|
+
attribute_name,
|
528
|
+
existing_record.public_send(attribute_name)
|
529
|
+
)
|
530
|
+
end
|
531
|
+
|
532
|
+
@new_record
|
533
|
+
end
|
534
|
+
|
535
|
+
def validate_attribute_present?
|
536
|
+
if model.method_defined?("#{attribute}=")
|
537
|
+
true
|
392
538
|
else
|
539
|
+
@failure_reason =
|
540
|
+
":#{attribute} does not seem to be an attribute on #{model.name}."
|
541
|
+
false
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def validate_scopes_present?
|
546
|
+
if all_scopes_present_on_model?
|
393
547
|
true
|
548
|
+
else
|
549
|
+
reason = ''
|
550
|
+
|
551
|
+
reason << inspected_scopes_missing_on_model.to_sentence
|
552
|
+
|
553
|
+
if inspected_scopes_missing_on_model.many?
|
554
|
+
reason << " do not seem to be attributes"
|
555
|
+
else
|
556
|
+
reason << " does not seem to be an attribute"
|
557
|
+
end
|
558
|
+
|
559
|
+
reason << " on #{model.name}."
|
560
|
+
|
561
|
+
@failure_reason = reason
|
562
|
+
|
563
|
+
false
|
394
564
|
end
|
395
565
|
end
|
396
566
|
|
397
|
-
def
|
398
|
-
|
399
|
-
|
400
|
-
|
567
|
+
def all_scopes_present_on_model?
|
568
|
+
scopes_missing_on_model.none?
|
569
|
+
end
|
570
|
+
|
571
|
+
def scopes_missing_on_model
|
572
|
+
@_missing_scopes ||= expected_scopes.select do |scope|
|
573
|
+
!model.method_defined?("#{scope}=")
|
401
574
|
end
|
575
|
+
end
|
402
576
|
|
403
|
-
|
577
|
+
def inspected_scopes_missing_on_model
|
578
|
+
scopes_missing_on_model.map(&:inspect)
|
404
579
|
end
|
405
580
|
|
406
|
-
def
|
407
|
-
|
581
|
+
def validate_two_records_with_same_non_blank_value_cannot_coexist?
|
582
|
+
if existing_value_read.blank?
|
583
|
+
update_existing_record!(arbitrary_non_blank_value)
|
584
|
+
end
|
585
|
+
|
586
|
+
disallows_value_of(existing_value_read, @expected_message)
|
587
|
+
end
|
408
588
|
|
409
|
-
|
589
|
+
def validate_case_sensitivity?
|
590
|
+
if should_validate_case_sensitivity?
|
591
|
+
value = existing_value_read
|
410
592
|
swapcased_value = value.swapcase
|
411
593
|
|
412
|
-
if
|
413
|
-
disallows_value_of(swapcased_value, @expected_message)
|
414
|
-
else
|
594
|
+
if case_sensitivity_strategy == :sensitive
|
415
595
|
if value == swapcased_value
|
416
596
|
raise NonCaseSwappableValueError.create(
|
417
|
-
model:
|
597
|
+
model: model,
|
418
598
|
attribute: @attribute,
|
419
599
|
value: value
|
420
600
|
)
|
421
601
|
end
|
422
602
|
|
423
603
|
allows_value_of(swapcased_value, @expected_message)
|
604
|
+
else
|
605
|
+
disallows_value_of(swapcased_value, @expected_message)
|
424
606
|
end
|
425
607
|
else
|
426
608
|
true
|
427
609
|
end
|
428
610
|
end
|
429
611
|
|
430
|
-
def
|
431
|
-
|
612
|
+
def should_validate_case_sensitivity?
|
613
|
+
case_sensitivity_strategy != :ignore &&
|
614
|
+
existing_value_read.respond_to?(:swapcase) &&
|
615
|
+
!existing_value_read.empty?
|
432
616
|
end
|
433
617
|
|
434
618
|
def model_class?(model_name)
|
@@ -438,10 +622,10 @@ module Shoulda
|
|
438
622
|
end
|
439
623
|
|
440
624
|
def validate_after_scope_change?
|
441
|
-
if
|
625
|
+
if expected_scopes.empty? || all_scopes_are_booleans?
|
442
626
|
true
|
443
627
|
else
|
444
|
-
|
628
|
+
expected_scopes.all? do |scope|
|
445
629
|
previous_value = @all_records.map(&scope).compact.max
|
446
630
|
|
447
631
|
next_value =
|
@@ -451,16 +635,12 @@ module Shoulda
|
|
451
635
|
next_value_for(scope, previous_value)
|
452
636
|
end
|
453
637
|
|
454
|
-
|
455
|
-
|
456
|
-
if allows_value_of(existing_value, @expected_message)
|
457
|
-
@subject.__send__("#{scope}=", previous_value)
|
638
|
+
set_attribute_on_new_record!(scope, next_value)
|
458
639
|
|
459
|
-
|
460
|
-
|
640
|
+
if allows_value_of(existing_value_read, @expected_message)
|
641
|
+
set_attribute_on_new_record!(scope, previous_value)
|
461
642
|
true
|
462
643
|
else
|
463
|
-
@failure_message << " (with different value of #{scope})"
|
464
644
|
false
|
465
645
|
end
|
466
646
|
end
|
@@ -478,20 +658,7 @@ module Shoulda
|
|
478
658
|
end
|
479
659
|
|
480
660
|
def dummy_scalar_value_for(column)
|
481
|
-
|
482
|
-
when :integer
|
483
|
-
0
|
484
|
-
when :date
|
485
|
-
Date.today
|
486
|
-
when :datetime
|
487
|
-
DateTime.now
|
488
|
-
when :uuid
|
489
|
-
SecureRandom.uuid
|
490
|
-
when :boolean
|
491
|
-
true
|
492
|
-
else
|
493
|
-
'dummy value'
|
494
|
-
end
|
661
|
+
Shoulda::Matchers::Util.dummy_value_for(column.type)
|
495
662
|
end
|
496
663
|
|
497
664
|
def next_value_for(scope, previous_value)
|
@@ -534,8 +701,8 @@ module Shoulda
|
|
534
701
|
end
|
535
702
|
|
536
703
|
def defined_as_enum?(scope)
|
537
|
-
|
538
|
-
|
704
|
+
model.respond_to?(:defined_enums) &&
|
705
|
+
new_record.defined_enums[scope.to_s]
|
539
706
|
end
|
540
707
|
|
541
708
|
def polymorphic_type_attribute?(scope, previous_value)
|
@@ -543,21 +710,180 @@ module Shoulda
|
|
543
710
|
end
|
544
711
|
|
545
712
|
def available_enum_values_for(scope, previous_value)
|
546
|
-
|
713
|
+
new_record.defined_enums[scope.to_s].reject do |key, _|
|
547
714
|
key == previous_value
|
548
715
|
end
|
549
716
|
end
|
550
717
|
|
551
|
-
def
|
552
|
-
|
718
|
+
def set_attribute_on!(record_type, record, attribute_name, value)
|
719
|
+
attribute_setter = build_attribute_setter(
|
720
|
+
record,
|
721
|
+
attribute_name,
|
722
|
+
value
|
723
|
+
)
|
724
|
+
attribute_setter.set!
|
725
|
+
|
726
|
+
@attribute_setters[record_type] << attribute_setter
|
727
|
+
end
|
728
|
+
|
729
|
+
def set_attribute_on_existing_record!(attribute_name, value)
|
730
|
+
set_attribute_on!(
|
731
|
+
:existing_record,
|
732
|
+
existing_record,
|
733
|
+
attribute_name,
|
734
|
+
value
|
735
|
+
)
|
736
|
+
end
|
737
|
+
|
738
|
+
def set_attribute_on_new_record!(attribute_name, value)
|
739
|
+
set_attribute_on!(
|
740
|
+
:new_record,
|
741
|
+
new_record,
|
742
|
+
attribute_name,
|
743
|
+
value
|
744
|
+
)
|
745
|
+
end
|
746
|
+
|
747
|
+
def attribute_setter_for_existing_record
|
748
|
+
@attribute_setters[:existing_record].last
|
749
|
+
end
|
750
|
+
|
751
|
+
def attribute_names_under_test
|
752
|
+
[@attribute] + expected_scopes
|
553
753
|
end
|
554
754
|
|
555
|
-
def
|
556
|
-
|
755
|
+
def build_attribute_setter(record, attribute_name, value)
|
756
|
+
Shoulda::Matchers::ActiveModel::AllowValueMatcher::AttributeSetter.new(
|
757
|
+
matcher_name: :validate_uniqueness_of,
|
758
|
+
object: record,
|
759
|
+
attribute_name: attribute_name,
|
760
|
+
value: value,
|
761
|
+
ignore_interference_by_writer: ignore_interference_by_writer
|
762
|
+
)
|
763
|
+
end
|
764
|
+
|
765
|
+
def existing_value_read
|
766
|
+
existing_record.public_send(@attribute)
|
767
|
+
end
|
768
|
+
|
769
|
+
def existing_value_written
|
770
|
+
if attribute_setter_for_existing_record
|
771
|
+
attribute_setter_for_existing_record.value_written
|
772
|
+
else
|
773
|
+
existing_value_read
|
774
|
+
end
|
557
775
|
end
|
558
776
|
|
559
777
|
def column_for(scope)
|
560
|
-
|
778
|
+
model.columns_hash[scope.to_s]
|
779
|
+
end
|
780
|
+
|
781
|
+
def column_limit_for(attribute)
|
782
|
+
column_for(attribute).try(:limit)
|
783
|
+
end
|
784
|
+
|
785
|
+
def model
|
786
|
+
@given_record.class
|
787
|
+
end
|
788
|
+
|
789
|
+
def failure_message_preface
|
790
|
+
prefix = ''
|
791
|
+
|
792
|
+
if @existing_record_created
|
793
|
+
prefix << "After taking the given #{model.name}"
|
794
|
+
|
795
|
+
if attribute_setter_for_existing_record
|
796
|
+
prefix << ', setting its '
|
797
|
+
prefix << description_for_attribute_setter(
|
798
|
+
attribute_setter_for_existing_record
|
799
|
+
)
|
800
|
+
else
|
801
|
+
prefix << ", whose :#{attribute} is "
|
802
|
+
prefix << "‹#{existing_value_read.inspect}›"
|
803
|
+
end
|
804
|
+
|
805
|
+
prefix << ", and saving it as the existing record, then"
|
806
|
+
else
|
807
|
+
if attribute_setter_for_existing_record
|
808
|
+
prefix << "Given an existing #{model.name},"
|
809
|
+
prefix << ' after setting its '
|
810
|
+
prefix << description_for_attribute_setter(
|
811
|
+
attribute_setter_for_existing_record
|
812
|
+
)
|
813
|
+
prefix << ', then'
|
814
|
+
else
|
815
|
+
prefix << "Given an existing #{model.name} whose :#{attribute}"
|
816
|
+
prefix << ' is '
|
817
|
+
prefix << Shoulda::Matchers::Util.inspect_value(
|
818
|
+
existing_value_read
|
819
|
+
)
|
820
|
+
prefix << ', after'
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
824
|
+
prefix << " making a new #{model.name} and setting its "
|
825
|
+
|
826
|
+
prefix << description_for_attribute_setter(
|
827
|
+
last_attribute_setter_used_on_new_record,
|
828
|
+
same_as_existing: existing_and_new_values_are_same?
|
829
|
+
)
|
830
|
+
|
831
|
+
prefix << ", the matcher expected the new #{model.name} to be"
|
832
|
+
|
833
|
+
prefix
|
834
|
+
end
|
835
|
+
|
836
|
+
def attribute_changed_value_message
|
837
|
+
<<-MESSAGE.strip
|
838
|
+
As indicated in the message above,
|
839
|
+
:#{last_attribute_setter_used_on_new_record.attribute_name} seems to be
|
840
|
+
changing certain values as they are set, and this could have something
|
841
|
+
to do with why this test is failing. If you or something else has
|
842
|
+
overridden the writer method for this attribute to normalize values by
|
843
|
+
changing their case in any way (for instance, ensuring that the
|
844
|
+
attribute is always downcased), then try adding
|
845
|
+
`ignoring_case_sensitivity` onto the end of the uniqueness matcher.
|
846
|
+
Otherwise, you may need to write the test yourself, or do something
|
847
|
+
different altogether.
|
848
|
+
MESSAGE
|
849
|
+
end
|
850
|
+
|
851
|
+
def description_for_attribute_setter(attribute_setter, same_as_existing: nil)
|
852
|
+
description = ":#{attribute_setter.attribute_name} to "
|
853
|
+
|
854
|
+
if same_as_existing == false
|
855
|
+
description << 'a different value, '
|
856
|
+
end
|
857
|
+
|
858
|
+
description << Shoulda::Matchers::Util.inspect_value(
|
859
|
+
attribute_setter.value_written
|
860
|
+
)
|
861
|
+
|
862
|
+
if attribute_setter.attribute_changed_value?
|
863
|
+
description << ' (read back as '
|
864
|
+
description << Shoulda::Matchers::Util.inspect_value(
|
865
|
+
attribute_setter.value_read
|
866
|
+
)
|
867
|
+
description << ')'
|
868
|
+
end
|
869
|
+
|
870
|
+
if same_as_existing == true
|
871
|
+
description << ' as well'
|
872
|
+
end
|
873
|
+
|
874
|
+
description
|
875
|
+
end
|
876
|
+
|
877
|
+
def existing_and_new_values_are_same?
|
878
|
+
last_value_set_on_new_record == existing_value_written
|
879
|
+
end
|
880
|
+
|
881
|
+
def last_attribute_setter_used_on_new_record
|
882
|
+
last_submatcher_run.last_attribute_setter_used
|
883
|
+
end
|
884
|
+
|
885
|
+
def last_value_set_on_new_record
|
886
|
+
last_submatcher_run.last_value_set
|
561
887
|
end
|
562
888
|
|
563
889
|
# @private
|