shoulda-matchers 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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