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.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +3 -3
  4. data/CONTRIBUTING.md +60 -28
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +15 -12
  7. data/NEWS.md +111 -0
  8. data/README.md +94 -6
  9. data/Rakefile +10 -8
  10. data/custom_plan.rb +88 -0
  11. data/gemfiles/4.0.0.gemfile +1 -0
  12. data/gemfiles/4.0.0.gemfile.lock +21 -18
  13. data/gemfiles/4.0.1.gemfile +1 -0
  14. data/gemfiles/4.0.1.gemfile.lock +21 -18
  15. data/gemfiles/4.1.gemfile +1 -0
  16. data/gemfiles/4.1.gemfile.lock +21 -18
  17. data/gemfiles/4.2.gemfile +1 -0
  18. data/gemfiles/4.2.gemfile.lock +24 -21
  19. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +6 -11
  20. data/lib/shoulda/matchers/active_model.rb +10 -1
  21. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +258 -180
  22. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +45 -0
  23. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error.rb +23 -0
  24. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +236 -0
  25. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +62 -0
  26. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +40 -0
  27. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +48 -0
  28. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb +14 -0
  29. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb +14 -0
  30. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +34 -14
  31. data/lib/shoulda/matchers/active_model/helpers.rb +9 -17
  32. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +13 -6
  33. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +13 -2
  34. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +19 -35
  35. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +13 -2
  36. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +12 -2
  37. data/lib/shoulda/matchers/active_model/qualifiers.rb +12 -0
  38. data/lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb +101 -0
  39. data/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +21 -0
  40. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +30 -32
  41. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +5 -8
  42. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +22 -22
  43. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +27 -16
  44. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +58 -15
  45. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +22 -12
  46. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +165 -87
  47. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +7 -9
  48. data/lib/shoulda/matchers/active_model/validation_matcher.rb +111 -49
  49. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +60 -0
  50. data/lib/shoulda/matchers/active_model/validator.rb +71 -52
  51. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +19 -5
  52. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +450 -124
  53. data/lib/shoulda/matchers/util.rb +43 -0
  54. data/lib/shoulda/matchers/util/word_wrap.rb +59 -31
  55. data/lib/shoulda/matchers/version.rb +1 -1
  56. data/script/update_gem_in_all_appraisals +1 -1
  57. data/script/update_gems_in_all_appraisals +1 -1
  58. data/spec/acceptance/multiple_libraries_integration_spec.rb +5 -2
  59. data/spec/acceptance/rails_integration_spec.rb +6 -2
  60. data/spec/spec_helper.rb +1 -3
  61. data/spec/support/acceptance/helpers/step_helpers.rb +4 -1
  62. data/spec/support/tests/current_bundle.rb +21 -7
  63. data/spec/support/unit/active_record/create_table.rb +54 -0
  64. data/spec/support/unit/attribute.rb +47 -0
  65. data/spec/support/unit/capture.rb +6 -0
  66. data/spec/support/unit/change_value.rb +111 -0
  67. data/spec/support/unit/create_model_arguments/basic.rb +135 -0
  68. data/spec/support/unit/create_model_arguments/has_many.rb +15 -0
  69. data/spec/support/unit/create_model_arguments/uniqueness_matcher.rb +74 -0
  70. data/spec/support/unit/helpers/active_record_versions.rb +1 -1
  71. data/spec/support/unit/helpers/class_builder.rb +61 -47
  72. data/spec/support/unit/helpers/database_helpers.rb +5 -3
  73. data/spec/support/unit/helpers/model_builder.rb +77 -97
  74. data/spec/support/unit/helpers/validation_matcher_scenario_helpers.rb +44 -0
  75. data/spec/support/unit/load_environment.rb +12 -0
  76. data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +2 -2
  77. data/spec/support/unit/matchers/fail_with_message_matcher.rb +12 -1
  78. data/spec/support/unit/model_creation_strategies/active_model.rb +111 -0
  79. data/spec/support/unit/model_creation_strategies/active_record.rb +77 -0
  80. data/spec/support/unit/model_creators.rb +19 -0
  81. data/spec/support/unit/model_creators/active_model.rb +39 -0
  82. data/spec/support/unit/model_creators/active_record.rb +43 -0
  83. data/spec/support/unit/model_creators/active_record/has_and_belongs_to_many.rb +95 -0
  84. data/spec/support/unit/model_creators/active_record/has_many.rb +67 -0
  85. data/spec/support/unit/model_creators/active_record/uniqueness_matcher.rb +42 -0
  86. data/spec/support/unit/model_creators/basic.rb +97 -0
  87. data/spec/support/unit/rails_application.rb +1 -1
  88. data/spec/support/unit/record_validating_confirmation_builder.rb +3 -7
  89. data/spec/support/unit/shared_examples/ignoring_interference_by_writer.rb +79 -0
  90. data/spec/support/unit/validation_matcher_scenario.rb +62 -0
  91. data/spec/unit/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +4 -0
  92. data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +575 -140
  93. data/spec/unit/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +115 -15
  94. data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +42 -4
  95. data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +92 -6
  96. data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +122 -10
  97. data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +306 -58
  98. data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +122 -3
  99. data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +805 -131
  100. data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +196 -29
  101. data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +82 -40
  102. data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +600 -101
  103. data/spec/unit/shoulda/matchers/util/word_wrap_spec.rb +88 -33
  104. data/spec/unit_spec_helper.rb +10 -22
  105. data/zeus.json +11 -0
  106. metadata +64 -23
  107. data/lib/shoulda/matchers/active_model/strict_validator.rb +0 -51
  108. data/spec/support/unit/shared_examples/numerical_type_submatcher.rb +0 -15
  109. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +0 -288
  110. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +0 -100
  111. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +0 -100
  112. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +0 -100
@@ -66,88 +66,193 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
66
66
  end
67
67
 
68
68
  context 'when too narrow of a scope is specified' do
69
- it 'rejects' do
69
+ it 'rejects with an appropriate failure message' do
70
70
  record = build_record_validating_uniqueness(
71
71
  scopes: [
72
72
  build_attribute(name: :scope1),
73
- { name: :scope2 }
73
+ build_attribute(name: :scope2)
74
74
  ],
75
+ additional_attributes: [:other]
75
76
  )
76
- expect(record).
77
- not_to validate_uniqueness.
78
- scoped_to(:scope1, :scope2, :other)
77
+
78
+ assertion = lambda do
79
+ expect(record).
80
+ to validate_uniqueness.
81
+ scoped_to(:scope1, :scope2, :other)
82
+ end
83
+
84
+ message = <<-MESSAGE
85
+ Example did not properly validate that :attr is case-sensitively unique
86
+ within the scope of :scope1, :scope2, and :other.
87
+ Expected the validation to be scoped to :scope1, :scope2, and :other,
88
+ but it was scoped to :scope1 and :scope2 instead.
89
+ MESSAGE
90
+
91
+ expect(&assertion).to fail_with_message(message)
79
92
  end
80
93
  end
81
94
 
82
95
  context 'when too broad of a scope is specified' do
83
- it 'rejects' do
96
+ it 'rejects with an appropriate failure message' do
84
97
  record = build_record_validating_uniqueness(
85
98
  scopes: [
86
99
  build_attribute(name: :scope1),
87
- { name: :scope2 }
100
+ build_attribute(name: :scope2)
88
101
  ],
89
102
  )
90
- expect(record).
91
- not_to validate_uniqueness.
92
- scoped_to(:scope1)
103
+
104
+ assertion = lambda do
105
+ expect(record).
106
+ to validate_uniqueness.
107
+ scoped_to(:scope1)
108
+ end
109
+
110
+ message = <<-MESSAGE
111
+ Example did not properly validate that :attr is case-sensitively unique
112
+ within the scope of :scope1.
113
+ Expected the validation to be scoped to :scope1, but it was scoped to
114
+ :scope1 and :scope2 instead.
115
+ MESSAGE
116
+
117
+ expect(&assertion).to fail_with_message(message)
93
118
  end
94
119
  end
95
120
 
96
121
  context 'when a different scope is specified' do
97
- it 'rejects' do
122
+ it 'rejects with an appropriate failure message' do
98
123
  record = build_record_validating_uniqueness(
99
- scopes: [ build_attribute(name: :scope) ],
100
- additional_attributes: [:other]
124
+ scopes: [ build_attribute(name: :other) ],
125
+ additional_attributes: [:scope]
101
126
  )
102
- expect(record).
103
- not_to validate_uniqueness.
104
- scoped_to(:other)
127
+ assertion = lambda do
128
+ expect(record).
129
+ to validate_uniqueness.
130
+ scoped_to(:scope)
131
+ end
132
+
133
+ message = <<-MESSAGE
134
+ Example did not properly validate that :attr is case-sensitively unique
135
+ within the scope of :scope.
136
+ Expected the validation to be scoped to :scope, but it was scoped to
137
+ :other instead.
138
+ MESSAGE
139
+
140
+ expect(&assertion).to fail_with_message(message)
105
141
  end
106
142
  end
107
143
 
108
144
  context 'when no scope is specified' do
109
- it 'rejects' do
145
+ it 'rejects with an appropriate failure message' do
110
146
  record = build_record_validating_uniqueness(
111
147
  scopes: [ build_attribute(name: :scope) ]
112
148
  )
113
- expect(record).not_to validate_uniqueness
149
+
150
+ assertion = lambda do
151
+ expect(record).to validate_uniqueness
152
+ end
153
+
154
+ message = <<-MESSAGE
155
+ Example did not properly validate that :attr is case-sensitively unique.
156
+ Expected the validation not to be scoped to anything, but it was
157
+ scoped to :scope instead.
158
+ MESSAGE
159
+
160
+ expect(&assertion).to fail_with_message(message)
114
161
  end
115
162
 
116
- it 'rejects if the scope is unset beforehand' do
117
- record = build_record_validating_uniqueness(
118
- scopes: [ build_attribute(name: :scope, value: nil) ]
119
- )
120
- expect(record).not_to validate_uniqueness
163
+ context 'if the scope attribute is unset in the record given to the matcher' do
164
+ it 'rejects with an appropriate failure message' do
165
+ record = build_record_validating_uniqueness(
166
+ scopes: [ build_attribute(name: :scope, value: nil) ]
167
+ )
168
+
169
+ assertion = lambda do
170
+ expect(record).to validate_uniqueness
171
+ end
172
+
173
+ message = <<-MESSAGE
174
+ Example did not properly validate that :attr is case-sensitively unique.
175
+ Expected the validation not to be scoped to anything, but it was
176
+ scoped to :scope instead.
177
+ MESSAGE
178
+
179
+ expect(&assertion).to fail_with_message(message)
180
+ end
121
181
  end
122
182
  end
123
183
 
124
184
  context 'when a non-existent attribute is specified as a scope' do
125
- it 'rejects' do
126
- record = build_record_validating_uniqueness(
127
- scopes: [ build_attribute(name: :scope) ]
128
- )
129
- expect(record).not_to validate_uniqueness.scoped_to(:non_existent)
185
+ context 'when there is more than one scope' do
186
+ it 'rejects with an appropriate failure message (and does not raise an error)' do
187
+ record = build_record_validating_uniqueness(
188
+ scopes: [ build_attribute(name: :scope) ]
189
+ )
190
+
191
+ assertion = lambda do
192
+ expect(record).to validate_uniqueness.scoped_to(:non_existent)
193
+ end
194
+
195
+ message = <<-MESSAGE.strip
196
+ Example did not properly validate that :attr is case-sensitively unique
197
+ within the scope of :non_existent.
198
+ :non_existent does not seem to be an attribute on Example.
199
+ MESSAGE
200
+
201
+ expect(&assertion).to fail_with_message(message)
202
+ end
203
+ end
204
+
205
+ context 'when there is more than one scope' do
206
+ it 'rejects with an appropriate failure message (and does not raise an error)' do
207
+ record = build_record_validating_uniqueness(
208
+ scopes: [ build_attribute(name: :scope) ]
209
+ )
210
+
211
+ assertion = lambda do
212
+ expect(record).to validate_uniqueness.scoped_to(
213
+ :non_existent1,
214
+ :non_existent2
215
+ )
216
+ end
217
+
218
+ message = <<-MESSAGE.strip
219
+ Example did not properly validate that :attr is case-sensitively unique
220
+ within the scope of :non_existent1 and :non_existent2.
221
+ :non_existent1 and :non_existent2 do not seem to be attributes on
222
+ Example.
223
+ MESSAGE
224
+
225
+ expect(&assertion).to fail_with_message(message)
226
+ end
130
227
  end
131
228
  end
132
229
 
133
230
  define_method(:build_attribute) do |attribute_options|
134
- attribute_options.merge(
231
+ attribute_options.deep_merge(
135
232
  column_type: column_type,
136
233
  value_type: value_type,
137
- array: array
234
+ options: { array: array }
138
235
  )
139
236
  end
140
237
  end
141
238
 
142
239
  context 'when the model does not have a uniqueness validation' do
143
- it 'rejects' do
144
- model = define_model(:example, attribute_name => :string) do |m|
145
- m.attr_accessible attribute_name
240
+ it 'rejects with an appropriate failure message' do
241
+ model = define_model_without_validation
242
+ model.create!(attribute_name => 'value')
243
+
244
+ assertion = lambda do
245
+ expect(model.new).to validate_uniqueness_of(attribute_name)
146
246
  end
147
247
 
148
- model.create!(attr: 'value')
248
+ message = <<-MESSAGE
249
+ Example did not properly validate that :attr is case-sensitively unique.
250
+ Given an existing Example whose :attr is ‹"value"›, after making a new
251
+ Example and setting its :attr to ‹"value"› as well, the matcher
252
+ expected the new Example to be invalid, but it was valid instead.
253
+ MESSAGE
149
254
 
150
- expect(model.new).not_to validate_uniqueness_of(attribute_name)
255
+ expect(&assertion).to fail_with_message(message)
151
256
  end
152
257
  end
153
258
 
@@ -179,13 +284,25 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
179
284
  end
180
285
 
181
286
  context 'when the validation has no scope and a scope is specified' do
182
- it 'rejects' do
287
+ it 'rejects with an appropriate failure message' do
183
288
  model = define_model_validating_uniqueness(
184
289
  additional_attributes: [:other]
185
290
  )
186
291
  create_record_from(model)
187
292
  record = build_record_from(model)
188
- expect(record).not_to validate_uniqueness.scoped_to(:other)
293
+
294
+ assertion = lambda do
295
+ expect(record).to validate_uniqueness.scoped_to(:other)
296
+ end
297
+
298
+ message = <<-MESSAGE
299
+ Example did not properly validate that :attr is case-sensitively unique
300
+ within the scope of :other.
301
+ Expected the validation to be scoped to :other, but it was not scoped
302
+ to anything.
303
+ MESSAGE
304
+
305
+ expect(&assertion).to fail_with_message(message)
189
306
  end
190
307
  end
191
308
  end
@@ -219,23 +336,60 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
219
336
 
220
337
  context 'and the validation has a custom message' do
221
338
  context 'when no message is specified' do
222
- it 'rejects' do
339
+ it 'rejects with an appropriate failure message' do
223
340
  record = build_record_validating_uniqueness(
341
+ attribute_value: 'some value',
224
342
  validation_options: { message: 'bad value' }
225
343
  )
226
- expect(record).not_to validate_uniqueness
344
+
345
+ assertion = lambda do
346
+ expect(record).to validate_uniqueness
347
+ end
348
+
349
+ message = <<-MESSAGE
350
+ Example did not properly validate that :attr is case-sensitively unique.
351
+ After taking the given Example, whose :attr is ‹"some value"›, and
352
+ saving it as the existing record, then making a new Example and
353
+ setting its :attr to ‹"some value"› as well, the matcher expected the
354
+ new Example to be invalid and to produce the validation error "has
355
+ already been taken" on :attr. The record was indeed invalid, but it
356
+ produced these validation errors instead:
357
+
358
+ * attr: ["bad value"]
359
+ MESSAGE
360
+
361
+ expect(&assertion).to fail_with_message(message)
227
362
  end
228
363
  end
229
364
 
230
365
  context 'given a string' do
231
366
  context 'when the given and actual messages do not match' do
232
- it 'rejects' do
367
+ it 'rejects with an appropriate failure message' do
233
368
  record = build_record_validating_uniqueness(
234
- validation_options: { message: 'bad value' }
369
+ attribute_value: 'some value',
370
+ validation_options: { message: 'something else entirely' }
235
371
  )
236
- expect(record).
237
- not_to validate_uniqueness.
238
- with_message('something else entirely')
372
+
373
+ assertion = lambda do
374
+ expect(record).
375
+ to validate_uniqueness.
376
+ with_message('some message')
377
+ end
378
+
379
+ message = <<-MESSAGE
380
+ Example did not properly validate that :attr is case-sensitively unique,
381
+ producing a custom validation error on failure.
382
+ After taking the given Example, whose :attr is ‹"some value"›, and
383
+ saving it as the existing record, then making a new Example and
384
+ setting its :attr to ‹"some value"› as well, the matcher expected the
385
+ new Example to be invalid and to produce the validation error "some
386
+ message" on :attr. The record was indeed invalid, but it produced
387
+ these validation errors instead:
388
+
389
+ * attr: ["something else entirely"]
390
+ MESSAGE
391
+
392
+ expect(&assertion).to fail_with_message(message)
239
393
  end
240
394
  end
241
395
 
@@ -253,13 +407,32 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
253
407
 
254
408
  context 'given a regex' do
255
409
  context 'when the given and actual messages do not match' do
256
- it 'rejects' do
410
+ it 'rejects with an appropriate failure message' do
257
411
  record = build_record_validating_uniqueness(
258
- validation_options: { message: 'Bad value' }
412
+ attribute_value: 'some value',
413
+ validation_options: { message: 'something else entirely' }
259
414
  )
260
- expect(record).
261
- not_to validate_uniqueness.
262
- with_message(/something else entirely/)
415
+
416
+ assertion = lambda do
417
+ expect(record).
418
+ to validate_uniqueness.
419
+ with_message(/some message/)
420
+ end
421
+
422
+ message = <<-MESSAGE
423
+ Example did not properly validate that :attr is case-sensitively unique,
424
+ producing a custom validation error on failure.
425
+ After taking the given Example, whose :attr is ‹"some value"›, and
426
+ saving it as the existing record, then making a new Example and
427
+ setting its :attr to ‹"some value"› as well, the matcher expected the
428
+ new Example to be invalid and to produce a validation error matching
429
+ ‹/some message/› on :attr. The record was indeed invalid, but it
430
+ produced these validation errors instead:
431
+
432
+ * attr: ["something else entirely"]
433
+ MESSAGE
434
+
435
+ expect(&assertion).to fail_with_message(message)
263
436
  end
264
437
  end
265
438
 
@@ -275,6 +448,36 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
275
448
  end
276
449
  end
277
450
  end
451
+
452
+ it_supports(
453
+ 'ignoring_interference_by_writer',
454
+ tests: {
455
+ reject_if_qualified_but_changing_value_interferes: {
456
+ model_name: 'Example',
457
+ attribute_name: :attr,
458
+ default_value: 'some value',
459
+ changing_values_with: :next_value,
460
+ expected_message: <<-MESSAGE.strip
461
+ Example did not properly validate that :attr is case-sensitively unique.
462
+ After taking the given Example, whose :attr is ‹"some valuf"›, and
463
+ saving it as the existing record, then making a new Example and
464
+ setting its :attr to ‹"some valuf"› (read back as ‹"some valug"›) as
465
+ well, the matcher expected the new Example to be invalid, but it was
466
+ valid instead.
467
+
468
+ As indicated in the message above, :attr seems to be changing certain
469
+ values as they are set, and this could have something to do with why
470
+ this test is failing. If you or something else has overridden the
471
+ writer method for this attribute to normalize values by changing their
472
+ case in any way (for instance, ensuring that the attribute is always
473
+ downcased), then try adding `ignoring_case_sensitivity` onto the end
474
+ of the uniqueness matcher. Otherwise, you may need to write the test
475
+ yourself, or do something different altogether.
476
+
477
+ MESSAGE
478
+ }
479
+ }
480
+ )
278
481
  end
279
482
 
280
483
  context 'when the model has a scoped uniqueness validation' do
@@ -334,27 +537,49 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
334
537
  end
335
538
 
336
539
  context 'when too narrow of a scope is specified' do
337
- it 'rejects' do
540
+ it 'rejects with an appropriate failure message' do
338
541
  record = build_record_validating_scoped_uniqueness_with_enum(
339
542
  enum_scope: :scope1,
340
543
  additional_scopes: [:scope2],
341
544
  additional_attributes: [:other]
342
545
  )
343
- expect(record).
344
- not_to validate_uniqueness.
345
- scoped_to(:scope1, :scope2, :other)
546
+
547
+ assertion = lambda do
548
+ expect(record).
549
+ to validate_uniqueness.
550
+ scoped_to(:scope1, :scope2, :other)
551
+ end
552
+
553
+ message = <<-MESSAGE
554
+ Example did not properly validate that :attr is case-sensitively unique
555
+ within the scope of :scope1, :scope2, and :other.
556
+ Expected the validation to be scoped to :scope1, :scope2, and :other,
557
+ but it was scoped to :scope1 and :scope2 instead.
558
+ MESSAGE
559
+
560
+ expect(&assertion).to fail_with_message(message)
346
561
  end
347
562
  end
348
563
 
349
564
  context 'when too broad of a scope is specified' do
350
- it 'rejects' do
565
+ it 'rejects with an appropriate failure message' do
351
566
  record = build_record_validating_scoped_uniqueness_with_enum(
352
567
  enum_scope: :scope1,
353
568
  additional_scopes: [:scope2]
354
569
  )
355
- expect(record).
356
- not_to validate_uniqueness.
357
- scoped_to(:scope1)
570
+
571
+ assertion = lambda do
572
+ expect(record).to validate_uniqueness.scoped_to(:scope1)
573
+ end
574
+
575
+ message = <<-MESSAGE
576
+ Example did not properly validate that :attr is case-sensitively unique
577
+ within the scope of :scope1.
578
+ Expected the validation to be scoped to :scope1, but it was scoped to
579
+ :scope1 and :scope2 instead.
580
+ MESSAGE
581
+
582
+ expect(&assertion).to fail_with_message(message)
358
583
  end
359
584
  end
360
585
  end
@@ -467,26 +692,55 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
467
692
  end
468
693
 
469
694
  context 'when the matcher is qualified with case_insensitive' do
470
- it 'rejects' do
695
+ it 'rejects with an appropriate failure message' do
471
696
  record = build_record_validating_uniqueness(
472
697
  attribute_type: :string,
698
+ attribute_value: 'some value',
473
699
  validation_options: { case_sensitive: true }
474
700
  )
475
701
 
476
- expect(record).not_to validate_uniqueness.case_insensitive
702
+ assertion = lambda do
703
+ expect(record).to validate_uniqueness.case_insensitive
704
+ end
705
+
706
+ message = <<-MESSAGE
707
+ Example did not properly validate that :attr is case-insensitively
708
+ unique.
709
+ After taking the given Example, whose :attr is ‹"some value"›, and
710
+ saving it as the existing record, then making a new Example and
711
+ setting its :attr to a different value, ‹"SOME VALUE"›, the matcher
712
+ expected the new Example to be invalid, but it was valid instead.
713
+ MESSAGE
714
+
715
+ expect(&assertion).to fail_with_message(message)
477
716
  end
478
717
  end
479
718
  end
480
719
 
481
720
  context 'when the model has a case-insensitive validation' do
482
721
  context 'when case_insensitive is not specified' do
483
- it 'rejects' do
722
+ it 'rejects with an appropriate failure message' do
484
723
  record = build_record_validating_uniqueness(
485
724
  attribute_type: :string,
486
725
  validation_options: { case_sensitive: false }
487
726
  )
488
727
 
489
- expect(record).not_to validate_uniqueness
728
+ assertion = lambda do
729
+ expect(record).to validate_uniqueness
730
+ end
731
+
732
+ message = <<-MESSAGE
733
+ Example did not properly validate that :attr is case-sensitively unique.
734
+ After taking the given Example, setting its :attr to ‹"an arbitrary
735
+ value"›, and saving it as the existing record, then making a new
736
+ Example and setting its :attr to a different value, ‹"AN ARBITRARY
737
+ VALUE"›, the matcher expected the new Example to be valid, but it was
738
+ invalid instead, producing these validation errors:
739
+
740
+ * attr: ["has already been taken"]
741
+ MESSAGE
742
+
743
+ expect(&assertion).to fail_with_message(message)
490
744
  end
491
745
  end
492
746
 
@@ -499,6 +753,44 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
499
753
 
500
754
  expect(record).to validate_uniqueness.case_insensitive
501
755
  end
756
+
757
+ it_supports(
758
+ 'ignoring_interference_by_writer',
759
+ tests: {
760
+ reject_if_qualified_but_changing_value_interferes: {
761
+ model_name: 'Example',
762
+ attribute_name: :attr,
763
+ default_value: 'some value',
764
+ changing_values_with: :next_value,
765
+ expected_message: <<-MESSAGE.strip
766
+ Example did not properly validate that :attr is case-insensitively
767
+ unique.
768
+ After taking the given Example, whose :attr is ‹"some valuf"›, and
769
+ saving it as the existing record, then making a new Example and
770
+ setting its :attr to ‹"some valuf"› (read back as ‹"some valug"›) as
771
+ well, the matcher expected the new Example to be invalid, but it was
772
+ valid instead.
773
+
774
+ As indicated in the message above, :attr seems to be changing certain
775
+ values as they are set, and this could have something to do with why
776
+ this test is failing. If you or something else has overridden the
777
+ writer method for this attribute to normalize values by changing their
778
+ case in any way (for instance, ensuring that the attribute is always
779
+ downcased), then try adding `ignoring_case_sensitivity` onto the end
780
+ of the uniqueness matcher. Otherwise, you may need to write the test
781
+ yourself, or do something different altogether.
782
+ MESSAGE
783
+ }
784
+ }
785
+ )
786
+
787
+ def validation_matcher_scenario_args
788
+ super.deep_merge(validation_options: { case_sensitive: false })
789
+ end
790
+
791
+ def configure_validation_matcher(matcher)
792
+ super(matcher).case_insensitive
793
+ end
502
794
  end
503
795
  end
504
796
 
@@ -543,18 +835,50 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
543
835
 
544
836
  context 'when the validation is not declared with allow_nil' do
545
837
  context 'given a new record whose attribute is nil' do
546
- it 'rejects' do
838
+ it 'rejects with an appropriate failure message' do
547
839
  model = define_model_validating_uniqueness
548
840
  record = build_record_from(model, attribute_name => nil)
549
- expect(record).not_to validate_uniqueness.allow_nil
841
+
842
+ assertion = lambda do
843
+ expect(record).to validate_uniqueness.allow_nil
844
+ end
845
+
846
+ message = <<-MESSAGE
847
+ Example did not properly validate that :attr is case-sensitively unique,
848
+ but only if it is not nil.
849
+ After taking the given Example, setting its :attr to ‹nil›, and saving
850
+ it as the existing record, then making a new Example and setting its
851
+ :attr to ‹nil› as well, the matcher expected the new Example to be
852
+ valid, but it was invalid instead, producing these validation errors:
853
+
854
+ * attr: ["has already been taken"]
855
+ MESSAGE
856
+
857
+ expect(&assertion).to fail_with_message(message)
550
858
  end
551
859
  end
552
860
 
553
861
  context 'given an existing record whose attribute is nil' do
554
- it 'rejects' do
862
+ it 'rejects with an appropriate failure message' do
555
863
  model = define_model_validating_uniqueness
556
864
  record = create_record_from(model, attribute_name => nil)
557
- expect(record).not_to validate_uniqueness.allow_nil
865
+
866
+ assertion = lambda do
867
+ expect(record).to validate_uniqueness.allow_nil
868
+ end
869
+
870
+ message = <<-MESSAGE
871
+ Example did not properly validate that :attr is case-sensitively unique,
872
+ but only if it is not nil.
873
+ Given an existing Example, after setting its :attr to ‹nil›, then
874
+ making a new Example and setting its :attr to ‹nil› as well, the
875
+ matcher expected the new Example to be valid, but it was invalid
876
+ instead, producing these validation errors:
877
+
878
+ * attr: ["has already been taken"]
879
+ MESSAGE
880
+
881
+ expect(&assertion).to fail_with_message(message)
558
882
  end
559
883
  end
560
884
  end
@@ -640,38 +964,102 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
640
964
 
641
965
  context 'when the validation is not declared with allow_blank' do
642
966
  context 'given a new record whose attribute is nil' do
643
- it 'rejects' do
967
+ it 'rejects with an appropriate failure message' do
644
968
  model = define_model_validating_uniqueness
645
969
  record = build_record_from(model, attribute_name => nil)
646
- expect(record).not_to validate_uniqueness.allow_blank
970
+
971
+ assertion = lambda do
972
+ expect(record).to validate_uniqueness.allow_blank
973
+ end
974
+
975
+ message = <<-MESSAGE
976
+ Example did not properly validate that :attr is case-sensitively unique,
977
+ but only if it is not blank.
978
+ After taking the given Example, setting its :attr to ‹""›, and saving
979
+ it as the existing record, then making a new Example and setting its
980
+ :attr to ‹""› as well, the matcher expected the new Example to be
981
+ valid, but it was invalid instead, producing these validation errors:
982
+
983
+ * attr: ["has already been taken"]
984
+ MESSAGE
985
+
986
+ expect(&assertion).to fail_with_message(message)
647
987
  end
648
988
  end
649
989
 
650
990
  context 'given an existing record whose attribute is nil' do
651
- it 'rejects' do
991
+ it 'rejects with an appropriate failure message' do
652
992
  model = define_model_validating_uniqueness
653
993
  record = create_record_from(model, attribute_name => nil)
654
- expect(record).not_to validate_uniqueness.allow_blank
994
+
995
+ assertion = lambda do
996
+ expect(record).to validate_uniqueness.allow_blank
997
+ end
998
+
999
+ message = <<-MESSAGE
1000
+ Example did not properly validate that :attr is case-sensitively unique,
1001
+ but only if it is not blank.
1002
+ Given an existing Example, after setting its :attr to ‹""›, then
1003
+ making a new Example and setting its :attr to ‹""› as well, the
1004
+ matcher expected the new Example to be valid, but it was invalid
1005
+ instead, producing these validation errors:
1006
+
1007
+ * attr: ["has already been taken"]
1008
+ MESSAGE
1009
+
1010
+ expect(&assertion).to fail_with_message(message)
655
1011
  end
656
1012
  end
657
1013
 
658
1014
  context 'given a new record whose attribute is empty' do
659
- it 'rejects' do
1015
+ it 'rejects with an appropriate failure message' do
660
1016
  model = define_model_validating_uniqueness(
661
1017
  attribute_type: :string
662
1018
  )
663
1019
  record = build_record_from(model, attribute_name => '')
664
- expect(record).not_to validate_uniqueness.allow_blank
1020
+
1021
+ assertion = lambda do
1022
+ expect(record).to validate_uniqueness.allow_blank
1023
+ end
1024
+
1025
+ message = <<-MESSAGE
1026
+ Example did not properly validate that :attr is case-sensitively unique,
1027
+ but only if it is not blank.
1028
+ After taking the given Example, setting its :attr to ‹""›, and saving
1029
+ it as the existing record, then making a new Example and setting its
1030
+ :attr to ‹""› as well, the matcher expected the new Example to be
1031
+ valid, but it was invalid instead, producing these validation errors:
1032
+
1033
+ * attr: ["has already been taken"]
1034
+ MESSAGE
1035
+
1036
+ expect(&assertion).to fail_with_message(message)
665
1037
  end
666
1038
  end
667
1039
 
668
1040
  context 'given an existing record whose attribute is empty' do
669
- it 'rejects' do
1041
+ it 'rejects with an appropriate failure message' do
670
1042
  model = define_model_validating_uniqueness(
671
1043
  attribute_type: :string
672
1044
  )
673
1045
  record = create_record_from(model, attribute_name => '')
674
- expect(record).not_to validate_uniqueness.allow_blank
1046
+
1047
+ assertion = lambda do
1048
+ expect(record).to validate_uniqueness.allow_blank
1049
+ end
1050
+
1051
+ message = <<-MESSAGE
1052
+ Example did not properly validate that :attr is case-sensitively unique,
1053
+ but only if it is not blank.
1054
+ Given an existing Example, after setting its :attr to ‹""›, then
1055
+ making a new Example and setting its :attr to ‹""› as well, the
1056
+ matcher expected the new Example to be valid, but it was invalid
1057
+ instead, producing these validation errors:
1058
+
1059
+ * attr: ["has already been taken"]
1060
+ MESSAGE
1061
+
1062
+ expect(&assertion).to fail_with_message(message)
675
1063
  end
676
1064
  end
677
1065
  end
@@ -726,26 +1114,129 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
726
1114
  end
727
1115
  end
728
1116
 
1117
+ context 'when the model does not have the attribute being tested' do
1118
+ it 'fails with an appropriate failure message' do
1119
+ model = define_model(:example)
1120
+
1121
+ assertion = lambda do
1122
+ expect(model.new).to validate_uniqueness_of(:attr)
1123
+ end
1124
+
1125
+ message = <<-MESSAGE.strip
1126
+ Example did not properly validate that :attr is case-sensitively unique.
1127
+ :attr does not seem to be an attribute on Example.
1128
+ MESSAGE
1129
+
1130
+ expect(&assertion).to fail_with_message(message)
1131
+ end
1132
+ end
1133
+
1134
+ context 'when the writer method for the attribute changes the case of incoming values' do
1135
+ context 'when the validation is case-sensitive' do
1136
+ context 'and the matcher is ensuring that the validation is case-sensitive' do
1137
+ it 'rejects with an appropriate failure message' do
1138
+ model = define_model_validating_uniqueness(
1139
+ attribute_name: :name
1140
+ )
1141
+
1142
+ model.class_eval do
1143
+ def name=(name)
1144
+ super(name.upcase)
1145
+ end
1146
+ end
1147
+
1148
+ assertion = lambda do
1149
+ expect(model.new).to validate_uniqueness_of(:name)
1150
+ end
1151
+
1152
+ message = <<-MESSAGE.strip
1153
+ Example did not properly validate that :name is case-sensitively unique.
1154
+ After taking the given Example, setting its :name to ‹"an arbitrary
1155
+ value"› (read back as ‹"AN ARBITRARY VALUE"›), and saving it as the
1156
+ existing record, then making a new Example and setting its :name to
1157
+ ‹"an arbitrary value"› (read back as ‹"AN ARBITRARY VALUE"›) as well,
1158
+ the matcher expected the new Example to be valid, but it was invalid
1159
+ instead, producing these validation errors:
1160
+
1161
+ * name: ["has already been taken"]
1162
+
1163
+ As indicated in the message above, :name seems to be changing certain
1164
+ values as they are set, and this could have something to do with why
1165
+ this test is failing. If you or something else has overridden the
1166
+ writer method for this attribute to normalize values by changing their
1167
+ case in any way (for instance, ensuring that the attribute is always
1168
+ downcased), then try adding `ignoring_case_sensitivity` onto the end
1169
+ of the uniqueness matcher. Otherwise, you may need to write the test
1170
+ yourself, or do something different altogether.
1171
+ MESSAGE
1172
+
1173
+ expect(&assertion).to fail_with_message(message)
1174
+ end
1175
+ end
1176
+
1177
+ context 'and the matcher is ignoring case sensitivity' do
1178
+ it 'accepts (and not raise an error)' do
1179
+ model = define_model_validating_uniqueness(
1180
+ attribute_name: :name
1181
+ )
1182
+
1183
+ model.class_eval do
1184
+ def name=(name)
1185
+ super(name.upcase)
1186
+ end
1187
+ end
1188
+
1189
+ expect(model.new).
1190
+ to validate_uniqueness_of(:name).
1191
+ ignoring_case_sensitivity
1192
+ end
1193
+ end
1194
+ end
1195
+
1196
+ context 'when the validation is case-insensitive' do
1197
+ context 'and the matcher is ensuring that the validation is case-insensitive' do
1198
+ it 'accepts (and does not raise an error)' do
1199
+ model = define_model_validating_uniqueness(
1200
+ attribute_name: :name,
1201
+ validation_options: { case_sensitive: false },
1202
+ )
1203
+
1204
+ model.class_eval do
1205
+ def name=(name)
1206
+ super(name.downcase)
1207
+ end
1208
+ end
1209
+
1210
+ expect(model.new).
1211
+ to validate_uniqueness_of(:name).
1212
+ case_insensitive
1213
+ end
1214
+ end
1215
+ end
1216
+ end
1217
+
729
1218
  let(:model_attributes) { {} }
730
1219
 
731
1220
  def default_attribute
732
1221
  {
733
1222
  value_type: :string,
734
1223
  column_type: :string,
735
- array: false
1224
+ options: { array: false, null: true }
736
1225
  }
737
1226
  end
738
1227
 
739
1228
  def normalize_attribute(attribute)
740
1229
  if attribute.is_a?(Hash)
741
- if attribute.key?(:type)
742
- attribute[:value_type] = attribute[:type]
743
- attribute[:column_type] = attribute[:type]
1230
+ attribute_copy = attribute.dup
1231
+
1232
+ if attribute_copy.key?(:type)
1233
+ attribute_copy[:value_type] = attribute_copy[:type]
1234
+ attribute_copy[:column_type] = attribute_copy[:type]
744
1235
  end
745
1236
 
746
- default_attribute.merge(attribute)
1237
+ default_attribute.deep_merge(attribute_copy)
747
1238
  else
748
- default_attribute.merge(name: attribute)
1239
+ default_attribute.deep_merge(name: attribute)
749
1240
  end
750
1241
  end
751
1242
 
@@ -757,15 +1248,10 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
757
1248
 
758
1249
  def column_options_from(attributes)
759
1250
  attributes.inject({}) do |options, attribute|
760
- options_for_attribute = options[attribute[:name]] = {
1251
+ options[attribute[:name]] = {
761
1252
  type: attribute[:column_type],
762
1253
  options: attribute.fetch(:options, {})
763
1254
  }
764
-
765
- if attribute[:array]
766
- options_for_attribute[:options][:array] = attribute[:array]
767
- end
768
-
769
1255
  options
770
1256
  end
771
1257
  end
@@ -773,10 +1259,14 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
773
1259
  def attributes_with_values_for(model)
774
1260
  model_attributes[model].each_with_object({}) do |attribute, attrs|
775
1261
  attrs[attribute[:name]] = attribute.fetch(:value) do
776
- dummy_value_for(
777
- attribute[:value_type],
778
- array: attribute[:array]
779
- )
1262
+ if attribute[:options][:null]
1263
+ nil
1264
+ else
1265
+ dummy_value_for(
1266
+ attribute[:value_type],
1267
+ array: attribute[:options][:array]
1268
+ )
1269
+ end
780
1270
  end
781
1271
  end
782
1272
  end
@@ -835,25 +1325,21 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
835
1325
  end
836
1326
  end
837
1327
 
838
- def determine_scope_attribute_names_from(scope_attributes)
839
- scope_attributes.map do |attribute|
840
- if attribute.is_a?(Hash)
841
- attribute[:name]
842
- else
843
- attribute
844
- end
845
- end
846
- end
847
-
848
1328
  def define_model_validating_uniqueness(options = {}, &block)
1329
+ attribute_name = options.fetch(:attribute_name) { self.attribute_name }
849
1330
  attribute_type = options.fetch(:attribute_type, :string)
850
1331
  attribute_options = options.fetch(:attribute_options, {})
851
- attribute = {
1332
+ attribute = normalize_attribute(
852
1333
  name: attribute_name,
853
1334
  value_type: attribute_type,
854
1335
  column_type: attribute_type,
855
1336
  options: attribute_options
856
- }
1337
+ )
1338
+
1339
+ if options.key?(:attribute_value)
1340
+ attribute[:value] = options[:attribute_value]
1341
+ end
1342
+
857
1343
  scope_attributes = normalize_attributes(options.fetch(:scopes, []))
858
1344
  scope_attribute_names = scope_attributes.map { |attr| attr[:name] }
859
1345
  additional_attributes = normalize_attributes(
@@ -909,6 +1395,12 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
909
1395
  build_record_from(model)
910
1396
  end
911
1397
 
1398
+ def define_model_without_validation
1399
+ define_model(:example, attribute_name => :string) do |model|
1400
+ model.attr_accessible(attribute_name)
1401
+ end
1402
+ end
1403
+
912
1404
  def validate_uniqueness
913
1405
  validate_uniqueness_of(attribute_name)
914
1406
  end
@@ -916,4 +1408,11 @@ describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :mo
916
1408
  def attribute_name
917
1409
  :attr
918
1410
  end
1411
+
1412
+ def validation_matcher_scenario_args
1413
+ super.deep_merge(
1414
+ matcher_name: :validate_uniqueness_of,
1415
+ model_creator: :"active_record/uniqueness_matcher"
1416
+ )
1417
+ end
919
1418
  end