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
@@ -18,7 +18,6 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
18
18
  matches_or_not.reverse!
19
19
  to_or_not_to.reverse!
20
20
  end
21
-
22
21
  end
23
22
  end
24
23
 
@@ -49,6 +48,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
49
48
  def add_outside_value_to(values)
50
49
  values + [values.last + 1]
51
50
  end
51
+
52
+ def validation_matcher_scenario_args
53
+ super.deep_merge(column_type: :integer, default_value: 1)
54
+ end
52
55
  end
53
56
 
54
57
  context 'against an attribute with a specific column limit' do
@@ -95,6 +98,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
95
98
  def add_outside_value_to(values)
96
99
  values + [values.last + 1]
97
100
  end
101
+
102
+ def validation_matcher_scenario_args
103
+ super.deep_merge(column_type: :float, default_value: 1.0)
104
+ end
98
105
  end
99
106
 
100
107
  context 'against a decimal attribute' do
@@ -119,11 +126,20 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
119
126
  def add_outside_value_to(values)
120
127
  values + [values.last + 1]
121
128
  end
129
+
130
+ def validation_matcher_scenario_args
131
+ super.deep_merge(
132
+ column_type: :decimal,
133
+ default_value: BigDecimal.new('1.0')
134
+ )
135
+ end
122
136
  end
123
137
 
124
138
  context 'against a date attribute' do
125
139
  today = Date.today
126
140
 
141
+ define_method(:today) { today }
142
+
127
143
  it_behaves_like 'it supports in_array',
128
144
  possible_values: (1..5).map { |n| today + n },
129
145
  reserved_outside_value: described_class::ARBITRARY_OUTSIDE_DATE
@@ -141,11 +157,17 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
141
157
  def add_outside_value_to(values)
142
158
  values + [values.last + 1]
143
159
  end
160
+
161
+ def validation_matcher_scenario_args
162
+ super.deep_merge(column_type: :date, default_value: today)
163
+ end
144
164
  end
145
165
 
146
166
  context 'against a datetime attribute' do
147
167
  now = DateTime.now
148
168
 
169
+ define_method(:now) { now }
170
+
149
171
  it_behaves_like 'it supports in_array',
150
172
  possible_values: (1..5).map { |n| now + n },
151
173
  reserved_outside_value: described_class::ARBITRARY_OUTSIDE_DATETIME
@@ -163,11 +185,17 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
163
185
  def add_outside_value_to(values)
164
186
  values + [values.last + 1]
165
187
  end
188
+
189
+ def validation_matcher_scenario_args
190
+ super.deep_merge(column_type: :datetime, default_value: now)
191
+ end
166
192
  end
167
193
 
168
194
  context 'against a time attribute' do
169
195
  now = Time.now
170
196
 
197
+ define_method(:now) { now }
198
+
171
199
  it_behaves_like 'it supports in_array',
172
200
  possible_values: (1..5).map { |n| now + n },
173
201
  reserved_outside_value: described_class::ARBITRARY_OUTSIDE_TIME
@@ -185,6 +213,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
185
213
  def add_outside_value_to(values)
186
214
  values + [values.last + 1]
187
215
  end
216
+
217
+ def validation_matcher_scenario_args
218
+ super.deep_merge(column_type: :time, default_value: now)
219
+ end
188
220
  end
189
221
 
190
222
  context 'against a string attribute' do
@@ -202,6 +234,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
202
234
  def add_outside_value_to(values)
203
235
  values + %w(qux)
204
236
  end
237
+
238
+ def validation_matcher_scenario_args
239
+ super.deep_merge(column_type: :string)
240
+ end
205
241
  end
206
242
  end
207
243
 
@@ -210,7 +246,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
210
246
 
211
247
  testing_values_of_option 'allow_nil' do |option_args, matches_or_not, to_or_not_to|
212
248
  it "#{matches_or_not[0]} when the validation specifies allow_nil" do
213
- builder = build_object_allowing(valid_values, allow_nil: true)
249
+ builder = build_object_allowing(
250
+ valid_values,
251
+ validation_options: { allow_nil: true }
252
+ )
214
253
 
215
254
  __send__("expect_#{to_or_not_to[0]}_match_on_values", builder, valid_values) do |matcher|
216
255
  matcher.allow_nil(*option_args)
@@ -225,6 +264,36 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
225
264
  end
226
265
  end
227
266
  end
267
+
268
+ =begin
269
+ it_supports(
270
+ 'ignoring_interference_by_writer',
271
+ tests: {
272
+ accept_if_qualified_but_changing_value_does_not_interfere: {
273
+ changing_values_with: -> (value) { value || valid_values.first }
274
+ },
275
+ reject_if_qualified_but_changing_value_interferes: {
276
+ attribute_name: :attr,
277
+ changing_values_with: :never_falsy,
278
+ expected_message_includes: <<-MESSAGE.strip
279
+ As indicated in the message above, :attr seems to be changing certain
280
+ values as they are set, and this could have something to do with why
281
+ this test is failing. If you've overridden the writer method for this
282
+ attribute, then you may need to change it to make this test pass, or
283
+ do something else entirely.
284
+ MESSAGE
285
+ }
286
+ }
287
+ )
288
+ =end
289
+
290
+ def validation_matcher_scenario_args
291
+ super.deep_merge(validation_options: { allow_nil: true })
292
+ end
293
+
294
+ def configure_validation_matcher(matcher)
295
+ super(matcher).allow_nil
296
+ end
228
297
  end
229
298
 
230
299
  shared_examples_for 'it supports allow_blank' do |args|
@@ -232,7 +301,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
232
301
 
233
302
  testing_values_of_option 'allow_blank' do |option_args, matches_or_not, to_or_not_to|
234
303
  it "#{matches_or_not[0]} when the validation specifies allow_blank" do
235
- builder = build_object_allowing(valid_values, allow_blank: true)
304
+ builder = build_object_allowing(
305
+ valid_values,
306
+ validation_options: { allow_blank: true }
307
+ )
236
308
 
237
309
  __send__("expect_#{to_or_not_to[0]}_match_on_values", builder, valid_values) do |matcher|
238
310
  matcher.allow_blank(*option_args)
@@ -247,6 +319,38 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
247
319
  end
248
320
  end
249
321
  end
322
+
323
+ =begin
324
+ it_supports(
325
+ 'ignoring_interference_by_writer',
326
+ tests: {
327
+ accept_if_qualified_but_changing_value_does_not_interfere: {
328
+ changing_values_with: -> (value) {
329
+ value.presence || valid_values.first
330
+ }
331
+ },
332
+ reject_if_qualified_but_changing_value_interferes: {
333
+ attribute_name: :attr,
334
+ changing_values_with: :never_blank,
335
+ expected_message_includes: <<-MESSAGE.strip
336
+ As indicated in the message above, :attr seems to be changing certain
337
+ values as they are set, and this could have something to do with why
338
+ this test is failing. If you've overridden the writer method for this
339
+ attribute, then you may need to change it to make this test pass, or
340
+ do something else entirely.
341
+ MESSAGE
342
+ }
343
+ }
344
+ )
345
+ =end
346
+
347
+ def validation_matcher_scenario_args
348
+ super.deep_merge(validation_options: { allow_blank: true })
349
+ end
350
+
351
+ def configure_validation_matcher(matcher)
352
+ super(matcher).allow_blank
353
+ end
250
354
  end
251
355
 
252
356
  shared_examples_for 'it supports with_message' do |args|
@@ -254,7 +358,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
254
358
 
255
359
  context 'given a string' do
256
360
  it 'matches when validation uses given message' do
257
- builder = build_object_allowing(valid_values, message: 'a message')
361
+ builder = build_object_allowing(
362
+ valid_values,
363
+ validation_options: { message: 'a message' }
364
+ )
258
365
 
259
366
  expect_to_match_on_values(builder, valid_values) do |matcher|
260
367
  matcher.with_message('a message')
@@ -270,7 +377,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
270
377
  end
271
378
 
272
379
  it 'does not match when validation uses a message but it is not same as given' do
273
- builder = build_object_allowing(valid_values, message: 'a different message')
380
+ builder = build_object_allowing(
381
+ valid_values,
382
+ validation_options: { message: 'a different message' }
383
+ )
274
384
 
275
385
  expect_not_to_match_on_values(builder, valid_values) do |matcher|
276
386
  matcher.with_message('a message')
@@ -280,7 +390,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
280
390
 
281
391
  context 'given a regex' do
282
392
  it 'matches when validation uses a message that matches the regex' do
283
- builder = build_object_allowing(valid_values, message: 'this is a message')
393
+ builder = build_object_allowing(
394
+ valid_values,
395
+ validation_options: { message: 'this is a message' }
396
+ )
284
397
 
285
398
  expect_to_match_on_values(builder, valid_values) do |matcher|
286
399
  matcher.with_message(/a message/)
@@ -296,7 +409,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
296
409
  end
297
410
 
298
411
  it 'does not match when validation uses a message but it does not match regex' do
299
- builder = build_object_allowing(valid_values, message: 'a different message')
412
+ builder = build_object_allowing(
413
+ valid_values,
414
+ validation_options: { message: 'a different message' }
415
+ )
300
416
 
301
417
  expect_not_to_match_on_values(builder, valid_values) do |matcher|
302
418
  matcher.with_message(/a message/)
@@ -320,6 +436,8 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
320
436
  zero = args[:zero]
321
437
  reserved_outside_value = args.fetch(:reserved_outside_value)
322
438
 
439
+ define_method(:valid_values) { args.fetch(:possible_values) }
440
+
323
441
  it 'does not match a record with no validations' do
324
442
  builder = build_object
325
443
  expect_not_to_match_on_values(builder, possible_values)
@@ -364,7 +482,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
364
482
  context '+ strict' do
365
483
  context 'when the validation specifies strict' do
366
484
  it 'matches when the given values match the valid values' do
367
- builder = build_object_allowing(possible_values, strict: true)
485
+ builder = build_object_allowing(
486
+ possible_values,
487
+ validation_options: { strict: true }
488
+ )
368
489
 
369
490
  expect_to_match_on_values(builder, possible_values) do |matcher|
370
491
  matcher.strict
@@ -372,7 +493,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
372
493
  end
373
494
 
374
495
  it 'does not match when the given values do not match the valid values' do
375
- builder = build_object_allowing(possible_values, strict: true)
496
+ builder = build_object_allowing(
497
+ possible_values,
498
+ validation_options: { strict: true }
499
+ )
376
500
 
377
501
  values = add_outside_value_to(possible_values)
378
502
  expect_not_to_match_on_values(builder, values) do |matcher|
@@ -393,6 +517,23 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
393
517
  end
394
518
  end
395
519
 
520
+ it_supports(
521
+ 'ignoring_interference_by_writer',
522
+ tests: {
523
+ reject_if_qualified_but_changing_value_interferes: {
524
+ attribute_name: :attr,
525
+ changing_values_with: :next_value,
526
+ expected_message_includes: <<-MESSAGE.strip
527
+ As indicated in the message above, :attr seems to be changing certain
528
+ values as they are set, and this could have something to do with why
529
+ this test is failing. If you've overridden the writer method for this
530
+ attribute, then you may need to change it to make this test pass, or
531
+ do something else entirely.
532
+ MESSAGE
533
+ }
534
+ }
535
+ )
536
+
396
537
  def expect_to_match_on_values(builder, values, &block)
397
538
  expect_to_match_in_array(builder, values, &block)
398
539
  end
@@ -400,11 +541,21 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
400
541
  def expect_not_to_match_on_values(builder, values, &block)
401
542
  expect_not_to_match_in_array(builder, values, &block)
402
543
  end
544
+
545
+ def validation_matcher_scenario_args
546
+ super.deep_merge(validation_options: { in: valid_values })
547
+ end
548
+
549
+ def configure_validation_matcher(matcher)
550
+ super(matcher).in_array(valid_values)
551
+ end
403
552
  end
404
553
 
405
554
  shared_examples_for 'it supports in_range' do |args|
406
555
  possible_values = args[:possible_values]
407
556
 
557
+ define_method(:valid_values) { args.fetch(:possible_values) }
558
+
408
559
  it 'does not match a record with no validations' do
409
560
  builder = build_object
410
561
  expect_not_to_match_on_values(builder, possible_values)
@@ -451,7 +602,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
451
602
  context '+ strict' do
452
603
  context 'when the validation specifies strict' do
453
604
  it 'matches when the given range matches the range in the validation' do
454
- builder = build_object_allowing(possible_values, strict: true)
605
+ builder = build_object_allowing(
606
+ possible_values,
607
+ validation_options: { strict: true }
608
+ )
455
609
 
456
610
  expect_to_match_on_values(builder, possible_values) do |matcher|
457
611
  matcher.strict
@@ -459,7 +613,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
459
613
  end
460
614
 
461
615
  it 'matches when the given range does not match the range in the validation' do
462
- builder = build_object_allowing(possible_values, strict: true)
616
+ builder = build_object_allowing(
617
+ possible_values,
618
+ validation_options: { strict: true }
619
+ )
463
620
 
464
621
  range = Range.new(possible_values.first, possible_values.last + 1)
465
622
  expect_not_to_match_on_values(builder, range) do |matcher|
@@ -480,6 +637,23 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
480
637
  end
481
638
  end
482
639
 
640
+ it_supports(
641
+ 'ignoring_interference_by_writer',
642
+ tests: {
643
+ reject_if_qualified_but_changing_value_interferes: {
644
+ attribute_name: :attr,
645
+ changing_values_with: :next_value,
646
+ expected_message_includes: <<-MESSAGE.strip
647
+ As indicated in the message above, :attr seems to be changing certain
648
+ values as they are set, and this could have something to do with why
649
+ this test is failing. If you've overridden the writer method for this
650
+ attribute, then you may need to change it to make this test pass, or
651
+ do something else entirely.
652
+ MESSAGE
653
+ }
654
+ }
655
+ )
656
+
483
657
  def expect_to_match_on_values(builder, range, &block)
484
658
  expect_to_match_in_range(builder, range, &block)
485
659
  end
@@ -487,6 +661,14 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
487
661
  def expect_not_to_match_on_values(builder, range, &block)
488
662
  expect_not_to_match_in_range(builder, range, &block)
489
663
  end
664
+
665
+ def validation_matcher_scenario_args
666
+ super.deep_merge(validation_options: { in: valid_values })
667
+ end
668
+
669
+ def configure_validation_matcher(matcher)
670
+ super(matcher).in_range(valid_values)
671
+ end
490
672
  end
491
673
 
492
674
  shared_context 'against a boolean attribute for true and false' do
@@ -537,6 +719,8 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
537
719
  context 'against a timestamp column' do
538
720
  now = DateTime.now
539
721
 
722
+ define_method(:now) { now }
723
+
540
724
  it_behaves_like 'it supports in_array',
541
725
  possible_values: (1..5).map { |n| now + n },
542
726
  reserved_outside_value: described_class::ARBITRARY_OUTSIDE_DATETIME
@@ -554,6 +738,10 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
554
738
  def add_outside_value_to(values)
555
739
  values + [values.last + 1]
556
740
  end
741
+
742
+ def validation_matcher_scenario_args
743
+ super.deep_merge(column_type: :timestamp, default_value: now)
744
+ end
557
745
  end
558
746
 
559
747
  context 'against a boolean attribute' do
@@ -615,27 +803,12 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
615
803
  end
616
804
  end
617
805
 
618
- def build_object_with_generic_attribute(options = {}, &block)
619
- attribute_name = :attr
620
- column_type = options.fetch(:column_type)
621
- column_options = {
622
- type: column_type,
623
- options: options.fetch(:column_options, {})
624
- }
625
- validation_options = options[:validation_options]
626
- custom_validation = options[:custom_validation]
627
-
628
- model = define_model :example, attribute_name => column_options
629
- customize_model_class(
630
- model,
631
- attribute_name,
632
- validation_options,
633
- custom_validation
634
- )
635
-
636
- object = model.new
806
+ def define_simple_model(attribute_name: :attr, column_options: {}, &block)
807
+ define_model('Example', attribute_name => column_options, &block)
808
+ end
637
809
 
638
- object_builder_class.new(attribute_name, object, validation_options)
810
+ def validation_matcher_scenario_args
811
+ super.deep_merge(model_creator: :active_record)
639
812
  end
640
813
  end
641
814
 
@@ -658,24 +831,56 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
658
831
  end
659
832
  end
660
833
 
661
- def build_object_with_generic_attribute(options = {}, &block)
662
- attribute_name = :attr
663
- validation_options = options[:validation_options]
664
- custom_validation = options[:custom_validation]
665
- value = options[:value]
834
+ def define_simple_model(attribute_name: :attr, column_options: {}, &block)
835
+ define_active_model_class('Example', accessors: [attribute_name], &block)
836
+ end
666
837
 
667
- model = define_active_model_class :example, accessors: [attribute_name]
668
- customize_model_class(
669
- model,
670
- attribute_name,
671
- validation_options,
672
- custom_validation
673
- )
838
+ def validation_matcher_scenario_args
839
+ super.deep_merge(model_creator: :active_model)
840
+ end
841
+ end
674
842
 
675
- object = model.new
676
- object.__send__("#{attribute_name}=", value)
843
+ describe '#description' do
844
+ context 'given an array of values' do
845
+ context 'when there is one value' do
846
+ it 'returns the correct string' do
847
+ matcher = validate_inclusion_of(:attr).in_array([true])
677
848
 
678
- object_builder_class.new(attribute_name, object, validation_options)
849
+ expect(matcher.description).to eq(
850
+ 'validate that :attr is ‹true›'
851
+ )
852
+ end
853
+ end
854
+
855
+ context 'when there are two values' do
856
+ it 'returns the correct string' do
857
+ matcher = validate_inclusion_of(:attr).in_array([true, 'dog'])
858
+
859
+ expect(matcher.description).to eq(
860
+ 'validate that :attr is either ‹true› or ‹"dog"›'
861
+ )
862
+ end
863
+ end
864
+
865
+ context 'when there are three or more values' do
866
+ it 'returns the correct string' do
867
+ matcher = validate_inclusion_of(:attr).in_array([true, 'dog', 'cat'])
868
+
869
+ expect(matcher.description).to eq(
870
+ 'validate that :attr is either ‹true›, ‹"dog"›, or ‹"cat"›'
871
+ )
872
+ end
873
+ end
874
+ end
875
+
876
+ context 'given a range of values' do
877
+ it 'returns the correct string' do
878
+ matcher = validate_inclusion_of(:attr).in_range(1..10)
879
+
880
+ expect(matcher.description).to eq(
881
+ 'validate that :attr lies inside the range ‹1› to ‹10›'
882
+ )
883
+ end
679
884
  end
680
885
  end
681
886
 
@@ -683,24 +888,63 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
683
888
  @_object_builder_class ||= Struct.new(:attribute, :object, :validation_options)
684
889
  end
685
890
 
686
- def customize_model_class(klass, attribute_name, validation_options, custom_validation)
687
- klass.class_eval do
891
+ def build_object_with_generic_attribute(
892
+ attribute_name: :attr,
893
+ validation_options: nil,
894
+ value: nil,
895
+ **other_options
896
+ )
897
+ model = define_model_validating_inclusion(
898
+ attribute_name: attribute_name,
899
+ validation_options: validation_options,
900
+ **other_options
901
+ )
902
+
903
+ object = model.new
904
+ object.__send__("#{attribute_name}=", value)
905
+
906
+ object_builder_class.new(attribute_name, object, validation_options)
907
+ end
908
+
909
+ def define_model_validating_inclusion(
910
+ attribute_name: :attr,
911
+ column_type: :string,
912
+ column_options: {},
913
+ validation_options: nil,
914
+ custom_validation: nil,
915
+ customize_model_class: -> (object) { }
916
+ )
917
+ column_options = { type: column_type, options: column_options }
918
+
919
+ define_simple_model(
920
+ attribute_name: attribute_name,
921
+ column_options: column_options
922
+ ) do |model|
688
923
  if validation_options
689
- validates_inclusion_of attribute_name, validation_options
924
+ model.validates_inclusion_of(attribute_name, validation_options)
690
925
  end
691
926
 
692
927
  if custom_validation
693
- define_method :custom_validation do
694
- instance_exec(attribute_name, &custom_validation)
928
+ model.class_eval do
929
+ define_method :custom_validation do
930
+ custom_validation.call(self, attribute_name)
931
+ end
932
+
933
+ validate :custom_validation
695
934
  end
935
+ end
696
936
 
697
- validate :custom_validation
937
+ if customize_model_class
938
+ model.instance_eval(&customize_model_class)
698
939
  end
699
940
  end
700
941
  end
701
942
 
702
- def build_object_allowing(values, options = {})
703
- build_object(validation_options: options.merge(in: values))
943
+ def build_object_allowing(values, validation_options: {}, **other_options)
944
+ build_object(
945
+ validation_options: validation_options.merge(in: values),
946
+ **other_options
947
+ )
704
948
  end
705
949
 
706
950
  def expect_to_match(builder)
@@ -747,13 +991,13 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
747
991
  low_message = 'too low'
748
992
  high_message = 'too high'
749
993
 
750
- builder = build_object custom_validation: ->(attribute) {
751
- value = __send__(attribute)
994
+ builder = build_object custom_validation: -> (object, attribute) {
995
+ value = object.public_send(attribute)
752
996
 
753
997
  if value < low_value
754
- errors.add(attribute, low_message)
998
+ object.errors.add(attribute, low_message)
755
999
  elsif value > high_value
756
- errors.add(attribute, high_message)
1000
+ object.errors.add(attribute, high_message)
757
1001
  end
758
1002
  }
759
1003
 
@@ -764,4 +1008,8 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
764
1008
  with_high_message(high_message)
765
1009
  end
766
1010
  end
1011
+
1012
+ def validation_matcher_scenario_args
1013
+ super.deep_merge(matcher_name: :validate_inclusion_of)
1014
+ end
767
1015
  end