shoulda-matchers 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 958afbf8d323fd9b923340fdffc6156b7a3249cc
4
- data.tar.gz: d20c30587592682f62e4b061619d32254625de23
3
+ metadata.gz: f5c8e2ffc80ab293ff70c3b4635ba8bcbcb1f9ac
4
+ data.tar.gz: f9c362c8e26ec64f5275eb7e0f61dbe9e472de8d
5
5
  SHA512:
6
- metadata.gz: b2141dd710f58fa62201e4fa3de70c324bbb9e6d225f41d88ecef413c401a10f1426f59025849e466c58d7c58bf6f94b0330b6e82c7577e3797f6bd0f91d97a2
7
- data.tar.gz: d22c37a1994c433c9213b1a3090f6cd06201a49d19fbc7cdbeca7bc826fec4b0853a7a216e8977df578b1627011eb23786fe276dc12e2a906116822dd88f0f1b
6
+ metadata.gz: 32dd5209bade6bc80dd860bd7de9f4ba7b772c37633ff555110663abf2dd250d7807b6e5d19dd6160ba834a3dff41d9f90e2100d25e65143feab0a29117527b8
7
+ data.tar.gz: 38aa6e350fce49d688d6874e0afc50489310821ef27bb8f54a5c2787f43967d6f1359f0989f8fbb5201819eaa8b1a794f832218749bcc8d0c7ea77d0d19cf534
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ gem 'appraisal', '~> 2.0'
4
4
  gem 'bundler', '~> 1.1'
5
5
  gem 'pry', github: 'pry/pry'
6
6
  gem 'pry-byebug'
7
- gem 'rake', '~> 10.0'
7
+ gem 'rake', '>= 10.5.0', '< 11'
8
8
  gem 'rspec', '~> 3.2'
9
9
  gem 'zeus'
10
10
 
@@ -28,7 +28,7 @@ GEM
28
28
  pygments.rb (0.3.7)
29
29
  posix-spawn (~> 0.3.6)
30
30
  yajl-ruby (~> 1.1.0)
31
- rake (10.4.2)
31
+ rake (10.5.0)
32
32
  redcarpet (3.0.0)
33
33
  rspec (3.4.0)
34
34
  rspec-core (~> 3.4.0)
@@ -60,11 +60,11 @@ DEPENDENCIES
60
60
  pry!
61
61
  pry-byebug
62
62
  pygments.rb
63
- rake (~> 10.0)
63
+ rake (>= 10.5.0, < 11)
64
64
  redcarpet
65
65
  rspec (~> 3.2)
66
66
  yard
67
67
  zeus
68
68
 
69
69
  BUNDLED WITH
70
- 1.11.1
70
+ 1.11.2
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2014, Tammer Saleh, thoughtbot, inc.
1
+ Copyright (c) 2006-2016, Tammer Saleh, thoughtbot, inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
data/NEWS.md CHANGED
@@ -1,3 +1,55 @@
1
+ # 3.1.1
2
+
3
+ ### Bug fixes
4
+
5
+ * Some matchers make use of ActiveSupport's `in?` method, but do not include the
6
+ file where this is defined in ActiveSupport. This causes problems with
7
+ projects using shoulda-matchers that do not include all of ActiveSupport by
8
+ default. To fix this, replace `in?` with Ruby's builtin `include?`.
9
+
10
+ * *Pull request: [#879]*
11
+
12
+ * `validate_uniqueness_of` works by creating a record if it doesn't exist, and
13
+ then testing against a new record with various attributes set that are equal
14
+ to (or different than) corresponding attributes in the existing record. In
15
+ 3.1.0 a change was made whereby when the uniqueness matcher is given a new
16
+ record and creates an existing record out of it, it ensures that the record is
17
+ valid before continuing on. This created a problem because if the subject,
18
+ before it was saved, was empty and therefore in an invalid state, it could not
19
+ effectively be saved. While ideally this should be enforced, doing so would be
20
+ a backward-incompatible change, so this behavior has been rolled back.
21
+ ([#880], [#884], [#885])
22
+
23
+ * *Commit: [45de869]*
24
+ * *Issues: [#880], [#884], [#885]*
25
+
26
+ * Fix an issue with `validate_uniqueness_of` + `scoped_to` when used against a
27
+ model where the attribute has multiple uniqueness validations and each
28
+ validation has a different set of scopes. In this case, a test written for the
29
+ first validation (and its scopes) would pass, but tests for the other
30
+ validations (and their scopes) would not, as the matcher only considered the
31
+ first set of scopes as the *actual* set of scopes.
32
+
33
+ * *Commit: [28bd9a1]*
34
+ * *Issues: [#830]*
35
+
36
+ ### Improvements
37
+
38
+ * Update `validate_uniqueness_of` so that if an existing record fails to be
39
+ created because a column is non-nullable and was not filled in, raise an
40
+ ExistingRecordInvalid exception with details on how to fix the test.
41
+
42
+ * *Commit: [78ccfc5]*
43
+
44
+ [#879]: https://github.com/thoughtbot/shoulda-matchers/issues/879
45
+ [45de869]: https://github.com/thoughtbot/shoulda-matchers/commit/45de8698487d57f559c5bf35818d1c1ee82b0e77
46
+ [#880]: https://github.com/thoughtbot/shoulda-matchers/issues/880
47
+ [#884]: https://github.com/thoughtbot/shoulda-matchers/issues/884
48
+ [#885]: https://github.com/thoughtbot/shoulda-matchers/issues/885
49
+ [78ccfc5]: https://github.com/thoughtbot/shoulda-matchers/commit/78ccfc50b52fa686c109d614df66744b0da65380
50
+ [28bd9a1]: https://github.com/thoughtbot/shoulda-matchers/commit/28bd9a10c71af4d541b692d6204163c394ebd33c
51
+ [#830]: https://github.com/thoughtbot/shoulda-matchers/issues/830
52
+
1
53
  # 3.1.0
2
54
 
3
55
  ### Bug fixes
data/README.md CHANGED
@@ -109,7 +109,32 @@ group :test do
109
109
  end
110
110
  ```
111
111
 
112
- [Then, configure the gem to integrate with RSpec](#configuration).
112
+ Now you need to tell the gem a couple of things:
113
+
114
+ * Which test framework you're using
115
+ * Which portion of the matchers you want to use
116
+
117
+ You can supply this information by using a configuration block. Place the
118
+ following in `rails_helper.rb`:
119
+
120
+ ``` ruby
121
+ Shoulda::Matchers.configure do |config|
122
+ config.integrate do |with|
123
+ # Choose a test framework:
124
+ with.test_framework :rspec
125
+ with.test_framework :minitest
126
+ with.test_framework :minitest_4
127
+ with.test_framework :test_unit
128
+
129
+ # Choose one or more libraries:
130
+ with.library :active_record
131
+ with.library :active_model
132
+ with.library :action_controller
133
+ # Or, choose the following (which implies all of the above):
134
+ with.library :rails
135
+ end
136
+ end
137
+ ```
113
138
 
114
139
  Now you can use matchers in your tests. For instance a model test might look
115
140
  like this:
@@ -149,7 +174,7 @@ end
149
174
  Then you can say:
150
175
 
151
176
  ``` ruby
152
- describe MyModel, type: :model do
177
+ describe MySpecialModel, type: :model do
153
178
  # ...
154
179
  end
155
180
  ```
@@ -184,8 +209,6 @@ group :test do
184
209
  end
185
210
  ```
186
211
 
187
- [Then, configure the gem to integrate with Minitest](#configuration).
188
-
189
212
  Now you can use matchers in your tests. For instance a model test might look
190
213
  like this:
191
214
 
@@ -195,36 +218,6 @@ class PersonTest < ActiveSupport::TestCase
195
218
  end
196
219
  ```
197
220
 
198
- ### Configuration
199
-
200
- Before you can use Shoulda Matchers, you'll need to tell it a couple of things:
201
-
202
- * Which test framework you're using
203
- * Which portion of the matchers you want to use
204
-
205
- You can supply this information by using a configuration block. Place the
206
- following in `rails_helper.rb` (if you're using RSpec) or `test_helper.rb` (if
207
- you're using Minitest):
208
-
209
- ``` ruby
210
- Shoulda::Matchers.configure do |config|
211
- config.integrate do |with|
212
- # Choose a test framework:
213
- with.test_framework :rspec
214
- with.test_framework :minitest
215
- with.test_framework :minitest_4
216
- with.test_framework :test_unit
217
-
218
- # Choose one or more libraries:
219
- with.library :active_record
220
- with.library :active_model
221
- with.library :action_controller
222
- # Or, choose the following (which implies all of the above):
223
- with.library :rails
224
- end
225
- end
226
- ```
227
-
228
221
  ## Running tests
229
222
 
230
223
  ### Unit tests
@@ -1,12 +1,27 @@
1
- require 'zeus/rails'
1
+ require 'zeus'
2
+ require 'zeus/plan'
2
3
  require_relative 'spec/support/tests/current_bundle'
3
4
 
4
- class CustomPlan < Zeus::Plan
5
- def initialize
6
- super
7
- @rails_plan = Zeus::Rails.new
5
+ class CouldNotBootZeusError < StandardError
6
+ def self.create(underlying_error:)
7
+ new(<<-MESSAGE)
8
+ Couldn't boot Zeus.
9
+
10
+ Bundler tried to load a gem that has already been loaded (but the
11
+ versions are different).
12
+
13
+ Note that Appraisal requires Rake, and so you'll want to make sure that
14
+ the Gemfile is pointing to the same version of Rake that you have
15
+ installed locally.
16
+
17
+ The original message is as follows:
18
+
19
+ #{underlying_error.message}
20
+ MESSAGE
8
21
  end
22
+ end
9
23
 
24
+ class CustomPlan < Zeus::Plan
10
25
  def boot
11
26
  ENV['BUNDLE_GEMFILE'] = File.expand_path(
12
27
  "../gemfiles/#{latest_appraisal}.gemfile",
@@ -19,10 +34,11 @@ class CustomPlan < Zeus::Plan
19
34
  $LOAD_PATH << File.expand_path('../spec', __FILE__)
20
35
 
21
36
  require_relative 'spec/support/unit/load_environment'
37
+ rescue Gem::LoadError => error
38
+ raise CouldNotBootZeusError.create(underlying_error: error)
22
39
  end
23
40
 
24
41
  def after_fork
25
- # @rails_plan.reconnect_activerecord
26
42
  end
27
43
 
28
44
  def test_environment
@@ -6,7 +6,7 @@ gem "appraisal", "~> 2.0"
6
6
  gem "bundler", "~> 1.1"
7
7
  gem "pry", :github => "pry/pry"
8
8
  gem "pry-byebug"
9
- gem "rake", "~> 10.0"
9
+ gem "rake", ">= 10.5.0", "< 11"
10
10
  gem "rspec", "~> 3.2"
11
11
  gem "zeus"
12
12
  gem "yard"
@@ -115,7 +115,7 @@ GEM
115
115
  activesupport (= 4.0.0)
116
116
  rake (>= 0.8.7)
117
117
  thor (>= 0.18.1, < 2.0)
118
- rake (10.4.2)
118
+ rake (10.5.0)
119
119
  rdoc (4.2.0)
120
120
  redcarpet (3.3.2)
121
121
  rspec (3.4.0)
@@ -203,7 +203,7 @@ DEPENDENCIES
203
203
  pry-byebug
204
204
  pygments.rb
205
205
  rails (= 4.0.0)
206
- rake (~> 10.0)
206
+ rake (>= 10.5.0, < 11)
207
207
  redcarpet
208
208
  rspec (~> 3.2)
209
209
  rspec-rails (>= 3.2.0, < 4)
@@ -220,4 +220,4 @@ DEPENDENCIES
220
220
  zeus
221
221
 
222
222
  BUNDLED WITH
223
- 1.11.1
223
+ 1.11.2
@@ -6,7 +6,7 @@ gem "appraisal", "~> 2.0"
6
6
  gem "bundler", "~> 1.1"
7
7
  gem "pry", :github => "pry/pry"
8
8
  gem "pry-byebug"
9
- gem "rake", "~> 10.0"
9
+ gem "rake", ">= 10.5.0", "< 11"
10
10
  gem "rspec", "~> 3.2"
11
11
  gem "zeus"
12
12
  gem "yard"
@@ -117,7 +117,7 @@ GEM
117
117
  activesupport (= 4.0.1)
118
118
  rake (>= 0.8.7)
119
119
  thor (>= 0.18.1, < 2.0)
120
- rake (10.4.2)
120
+ rake (10.5.0)
121
121
  rdoc (4.2.0)
122
122
  redcarpet (3.3.2)
123
123
  rspec (3.4.0)
@@ -205,7 +205,7 @@ DEPENDENCIES
205
205
  pry-byebug
206
206
  pygments.rb
207
207
  rails (= 4.0.1)
208
- rake (~> 10.0)
208
+ rake (>= 10.5.0, < 11)
209
209
  redcarpet
210
210
  rspec (~> 3.2)
211
211
  rspec-rails (>= 3.2.0, < 4)
@@ -222,4 +222,4 @@ DEPENDENCIES
222
222
  zeus
223
223
 
224
224
  BUNDLED WITH
225
- 1.11.1
225
+ 1.11.2
@@ -6,7 +6,7 @@ gem "appraisal", "~> 2.0"
6
6
  gem "bundler", "~> 1.1"
7
7
  gem "pry", :github => "pry/pry"
8
8
  gem "pry-byebug"
9
- gem "rake", "~> 10.0"
9
+ gem "rake", ">= 10.5.0", "< 11"
10
10
  gem "rspec", "~> 3.2"
11
11
  gem "zeus"
12
12
  gem "yard"
@@ -113,7 +113,7 @@ GEM
113
113
  activesupport (= 4.1.13)
114
114
  rake (>= 0.8.7)
115
115
  thor (>= 0.18.1, < 2.0)
116
- rake (10.4.2)
116
+ rake (10.5.0)
117
117
  rdoc (4.2.0)
118
118
  redcarpet (3.3.2)
119
119
  rspec (3.4.0)
@@ -200,7 +200,7 @@ DEPENDENCIES
200
200
  pry-byebug
201
201
  pygments.rb
202
202
  rails (~> 4.1.0)
203
- rake (~> 10.0)
203
+ rake (>= 10.5.0, < 11)
204
204
  redcarpet
205
205
  rspec (~> 3.2)
206
206
  rspec-rails (>= 3.2.0, < 4)
@@ -217,4 +217,4 @@ DEPENDENCIES
217
217
  zeus
218
218
 
219
219
  BUNDLED WITH
220
- 1.11.1
220
+ 1.11.2
@@ -6,7 +6,7 @@ gem "appraisal", "~> 2.0"
6
6
  gem "bundler", "~> 1.1"
7
7
  gem "pry", :github => "pry/pry"
8
8
  gem "pry-byebug"
9
- gem "rake", "~> 10.0"
9
+ gem "rake", ">= 10.5.0", "< 11"
10
10
  gem "rspec", "~> 3.2"
11
11
  gem "zeus"
12
12
  gem "yard"
@@ -138,7 +138,7 @@ GEM
138
138
  activesupport (= 4.2.4)
139
139
  rake (>= 0.8.7)
140
140
  thor (>= 0.18.1, < 2.0)
141
- rake (10.4.2)
141
+ rake (10.5.0)
142
142
  rdoc (4.2.0)
143
143
  redcarpet (3.3.2)
144
144
  rspec (3.4.0)
@@ -223,7 +223,7 @@ DEPENDENCIES
223
223
  pry-byebug
224
224
  pygments.rb
225
225
  rails (~> 4.2.0)
226
- rake (~> 10.0)
226
+ rake (>= 10.5.0, < 11)
227
227
  redcarpet
228
228
  rspec (~> 3.2)
229
229
  rspec-rails (>= 3.2.0, < 4)
@@ -240,4 +240,4 @@ DEPENDENCIES
240
240
  zeus
241
241
 
242
242
  BUNDLED WITH
243
- 1.11.1
243
+ 1.11.2
@@ -42,7 +42,7 @@ module Shoulda
42
42
  end
43
43
 
44
44
  def symbolize_or_stringify(key, value)
45
- if key.in?(PARAMS_TO_SYMBOLIZE)
45
+ if PARAMS_TO_SYMBOLIZE.include?(key)
46
46
  value.to_sym
47
47
  else
48
48
  stringify(value)
@@ -603,7 +603,7 @@ pass, or do something else entirely.
603
603
  def inspected_values_to_set
604
604
  Shoulda::Matchers::Util.inspect_values(values_to_set).to_sentence(
605
605
  two_words_connector: " or ",
606
- last_word_connector: ", or"
606
+ last_word_connector: ", or "
607
607
  )
608
608
  end
609
609
 
@@ -1,4 +1,5 @@
1
1
  require 'bigdecimal'
2
+ require 'date'
2
3
 
3
4
  module Shoulda
4
5
  module Matchers
@@ -53,23 +53,34 @@ module Shoulda
53
53
  # it { should validate_uniqueness_of(:title) }
54
54
  # end
55
55
  #
56
- # However, running this test will fail with something like:
56
+ # However, running this test will fail with an exception such as:
57
57
  #
58
- # Failures:
58
+ # Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher::ExistingRecordInvalid:
59
+ # validate_uniqueness_of works by matching a new record against an
60
+ # existing record. If there is no existing record, it will create one
61
+ # using the record you provide.
59
62
  #
60
- # 1) Post should validate :title to be case-sensitively unique
61
- # Failure/Error: it { should validate_uniqueness_of(:title) }
62
- # ActiveRecord::StatementInvalid:
63
- # SQLite3::ConstraintException: posts.content may not be NULL: INSERT INTO "posts" ("title") VALUES (?)
63
+ # While doing this, the following error was raised:
64
+ #
65
+ # PG::NotNullViolation: ERROR: null value in column "content" violates not-null constraint
66
+ # DETAIL: Failing row contains (1, null, null).
67
+ # : INSERT INTO "posts" DEFAULT VALUES RETURNING "id"
68
+ #
69
+ # The best way to fix this is to provide the matcher with a record where
70
+ # any required attributes are filled in with valid values beforehand.
71
+ #
72
+ # (The exact error message will differ depending on which database you're
73
+ # using, but you get the idea.)
64
74
  #
65
75
  # This happens because `validate_uniqueness_of` tries to create a new post
66
76
  # but cannot do so because of the `content` attribute: though unrelated to
67
- # this test, it nevertheless needs to be filled in. The solution is to
68
- # build a custom Post object ahead of time with `content` filled in:
77
+ # this test, it nevertheless needs to be filled in. As indicated at the
78
+ # end of the error message, the solution is to build a custom Post object
79
+ # ahead of time with `content` filled in:
69
80
  #
70
81
  # describe Post do
71
82
  # describe "validations" do
72
- # subject { Post.new(content: 'Here is the content') }
83
+ # subject { Post.new(content: "Here is the content") }
73
84
  # it { should validate_uniqueness_of(:title) }
74
85
  # end
75
86
  # end
@@ -259,8 +270,8 @@ module Shoulda
259
270
  @failure_reason = nil
260
271
  @failure_reason_when_negated = nil
261
272
  @attribute_setters = {
262
- existing_record: [],
263
- new_record: []
273
+ existing_record: AttributeSetters.new,
274
+ new_record: AttributeSetters.new
264
275
  }
265
276
  end
266
277
 
@@ -313,10 +324,9 @@ module Shoulda
313
324
  @given_record = given_record
314
325
  @all_records = model.all
315
326
 
316
- existing_record_valid? &&
317
- validate_attribute_present? &&
318
- validate_scopes_present? &&
319
- scopes_match? &&
327
+ validate_attribute_present_on_model? &&
328
+ validate_scopes_present_on_model? &&
329
+ validate_scopes_match? &&
320
330
  validate_two_records_with_same_non_blank_value_cannot_coexist? &&
321
331
  validate_case_sensitivity? &&
322
332
  validate_after_scope_change? &&
@@ -370,14 +380,14 @@ module Shoulda
370
380
  end
371
381
  end
372
382
 
373
- def validation
374
- model._validators[@attribute].detect do |validator|
383
+ def validations
384
+ model._validators[@attribute].select do |validator|
375
385
  validator.is_a?(::ActiveRecord::Validations::UniquenessValidator)
376
386
  end
377
387
  end
378
388
 
379
- def scopes_match?
380
- if expected_scopes == actual_scopes
389
+ def validate_scopes_match?
390
+ if scopes_match?
381
391
  true
382
392
  else
383
393
  @failure_reason = 'Expected the validation'
@@ -388,7 +398,7 @@ module Shoulda
388
398
  @failure_reason << " to be scoped to #{inspected_expected_scopes}"
389
399
  end
390
400
 
391
- if actual_scopes.empty?
401
+ if actual_sets_of_scopes.empty?
392
402
  @failure_reason << ', but it was not scoped to anything.'
393
403
  else
394
404
  @failure_reason << ', but it was scoped to '
@@ -399,24 +409,42 @@ module Shoulda
399
409
  end
400
410
  end
401
411
 
402
- def expected_scopes
403
- Array.wrap(@options[:scopes])
412
+ def scopes_match?
413
+ actual_sets_of_scopes.empty? && expected_scopes.empty? ||
414
+ actual_sets_of_scopes.any? { |scopes| scopes == expected_scopes }
404
415
  end
405
416
 
406
417
  def inspected_expected_scopes
407
418
  expected_scopes.map(&:inspect).to_sentence
408
419
  end
409
420
 
410
- def actual_scopes
411
- if validation
412
- Array.wrap(validation.options[:scope])
421
+ def inspected_actual_scopes
422
+ inspected_actual_sets_of_scopes.to_sentence(
423
+ words_connector: " or ",
424
+ last_word_connector: ", or"
425
+ )
426
+ end
427
+
428
+ def inspected_actual_sets_of_scopes
429
+ inspected_sets_of_scopes = actual_sets_of_scopes.map do |scopes|
430
+ scopes.map(&:inspect)
431
+ end
432
+
433
+ if inspected_sets_of_scopes.many?
434
+ inspected_sets_of_scopes.map { |x| "(#{x.to_sentence})" }
413
435
  else
414
- []
436
+ inspected_sets_of_scopes.map(&:to_sentence)
415
437
  end
416
438
  end
417
439
 
418
- def inspected_actual_scopes
419
- actual_scopes.map(&:inspect).to_sentence
440
+ def expected_scopes
441
+ Array.wrap(@options[:scopes])
442
+ end
443
+
444
+ def actual_sets_of_scopes
445
+ validations.map do |validation|
446
+ Array.wrap(validation.options[:scope])
447
+ end.reject(&:empty?)
420
448
  end
421
449
 
422
450
  def allows_nil?
@@ -437,18 +465,6 @@ module Shoulda
437
465
  end
438
466
  end
439
467
 
440
- def existing_record_valid?
441
- if existing_record.valid?
442
- true
443
- else
444
- @failure_reason =
445
- "The record you provided could not be created, " +
446
- "as it failed with the following validation errors:\n\n" +
447
- format_validation_errors(existing_record.errors)
448
- false
449
- end
450
- end
451
-
452
468
  def existing_record
453
469
  unless defined?(@existing_record)
454
470
  find_or_create_existing_record
@@ -479,20 +495,10 @@ module Shoulda
479
495
  def create_existing_record
480
496
  @given_record.tap do |existing_record|
481
497
  ensure_secure_password_set(existing_record)
482
- existing_record.save
483
- end
484
- end
485
-
486
- def update_existing_record!(value)
487
- if existing_value_read != value
488
- set_attribute_on!(
489
- :existing_record,
490
- existing_record,
491
- @attribute,
492
- value
493
- )
494
- existing_record.save!
498
+ existing_record.save(validate: false)
495
499
  end
500
+ rescue ::ActiveRecord::StatementInvalid => error
501
+ raise ExistingRecordInvalid.create(underlying_exception: error)
496
502
  end
497
503
 
498
504
  def ensure_secure_password_set(instance)
@@ -502,6 +508,15 @@ module Shoulda
502
508
  end
503
509
  end
504
510
 
511
+ def update_existing_record!(value)
512
+ if existing_value_read != value
513
+ set_attribute_on_existing_record!(@attribute, value)
514
+ # It would be nice if we could ensure that the record was valid,
515
+ # but that would break users' existing tests
516
+ existing_record.save(validate: false)
517
+ end
518
+ end
519
+
505
520
  def arbitrary_non_blank_value
506
521
  limit = column_limit_for(@attribute)
507
522
  non_blank_value = 'an arbitrary value'
@@ -532,8 +547,8 @@ module Shoulda
532
547
  @new_record
533
548
  end
534
549
 
535
- def validate_attribute_present?
536
- if model.method_defined?("#{attribute}=")
550
+ def validate_attribute_present_on_model?
551
+ if attribute_present_on_model?
537
552
  true
538
553
  else
539
554
  @failure_reason =
@@ -542,7 +557,12 @@ module Shoulda
542
557
  end
543
558
  end
544
559
 
545
- def validate_scopes_present?
560
+ def attribute_present_on_model?
561
+ model.method_defined?("#{attribute}=") ||
562
+ model.columns_hash.key?(attribute.to_s)
563
+ end
564
+
565
+ def validate_scopes_present_on_model?
546
566
  if all_scopes_present_on_model?
547
567
  true
548
568
  else
@@ -697,7 +717,7 @@ module Shoulda
697
717
  end
698
718
 
699
719
  def boolean_value?(value)
700
- value.in?([true, false])
720
+ [true, false].include?(value)
701
721
  end
702
722
 
703
723
  def defined_as_enum?(scope)
@@ -748,6 +768,11 @@ module Shoulda
748
768
  @attribute_setters[:existing_record].last
749
769
  end
750
770
 
771
+ def attribute_setters_for_new_record
772
+ @attribute_setters[:new_record] +
773
+ [last_attribute_setter_used_on_new_record]
774
+ end
775
+
751
776
  def attribute_names_under_test
752
777
  [@attribute] + expected_scopes
753
778
  end
@@ -793,7 +818,7 @@ module Shoulda
793
818
  prefix << "After taking the given #{model.name}"
794
819
 
795
820
  if attribute_setter_for_existing_record
796
- prefix << ', setting its '
821
+ prefix << ', setting '
797
822
  prefix << description_for_attribute_setter(
798
823
  attribute_setter_for_existing_record
799
824
  )
@@ -806,7 +831,7 @@ module Shoulda
806
831
  else
807
832
  if attribute_setter_for_existing_record
808
833
  prefix << "Given an existing #{model.name},"
809
- prefix << ' after setting its '
834
+ prefix << ' after setting '
810
835
  prefix << description_for_attribute_setter(
811
836
  attribute_setter_for_existing_record
812
837
  )
@@ -821,12 +846,9 @@ module Shoulda
821
846
  end
822
847
  end
823
848
 
824
- prefix << " making a new #{model.name} and setting its "
849
+ prefix << " making a new #{model.name} and setting "
825
850
 
826
- prefix << description_for_attribute_setter(
827
- last_attribute_setter_used_on_new_record,
828
- same_as_existing: existing_and_new_values_are_same?
829
- )
851
+ prefix << descriptions_for_attribute_setters_for_new_record
830
852
 
831
853
  prefix << ", the matcher expected the new #{model.name} to be"
832
854
 
@@ -849,7 +871,7 @@ different altogether.
849
871
  end
850
872
 
851
873
  def description_for_attribute_setter(attribute_setter, same_as_existing: nil)
852
- description = ":#{attribute_setter.attribute_name} to "
874
+ description = "its :#{attribute_setter.attribute_name} to "
853
875
 
854
876
  if same_as_existing == false
855
877
  description << 'a different value, '
@@ -874,6 +896,23 @@ different altogether.
874
896
  description
875
897
  end
876
898
 
899
+ def descriptions_for_attribute_setters_for_new_record
900
+ attribute_setter_descriptions_for_new_record.to_sentence
901
+ end
902
+
903
+ def attribute_setter_descriptions_for_new_record
904
+ attribute_setters_for_new_record.map do |attribute_setter|
905
+ same_as_existing = (
906
+ attribute_setter.value_written ==
907
+ existing_value_written
908
+ )
909
+ description_for_attribute_setter(
910
+ attribute_setter,
911
+ same_as_existing: same_as_existing
912
+ )
913
+ end
914
+ end
915
+
877
916
  def existing_and_new_values_are_same?
878
917
  last_value_set_on_new_record == existing_value_written
879
918
  end
@@ -886,6 +925,50 @@ different altogether.
886
925
  last_submatcher_run.last_value_set
887
926
  end
888
927
 
928
+ # @private
929
+ class AttributeSetters
930
+ include Enumerable
931
+
932
+ def initialize
933
+ @attribute_setters = []
934
+ end
935
+
936
+ def <<(given_attribute_setter)
937
+ index = find_index_of(given_attribute_setter)
938
+
939
+ if index
940
+ @attribute_setters[index] = given_attribute_setter
941
+ else
942
+ @attribute_setters << given_attribute_setter
943
+ end
944
+ end
945
+
946
+ def +(other_attribute_setters)
947
+ dup.tap do |attribute_setters|
948
+ other_attribute_setters.each do |attribute_setter|
949
+ attribute_setters << attribute_setter
950
+ end
951
+ end
952
+ end
953
+
954
+ def each(&block)
955
+ @attribute_setters.each(&block)
956
+ end
957
+
958
+ def last
959
+ @attribute_setters.last
960
+ end
961
+
962
+ private
963
+
964
+ def find_index_of(given_attribute_setter)
965
+ @attribute_setters.find_index do |attribute_setter|
966
+ attribute_setter.attribute_name ==
967
+ given_attribute_setter.attribute_name
968
+ end
969
+ end
970
+ end
971
+
889
972
  # @private
890
973
  class NonCaseSwappableValueError < Shoulda::Matchers::Error
891
974
  attr_accessor :model, :attribute, :value
@@ -895,7 +978,7 @@ different altogether.
895
978
  Your #{model.name} model has a uniqueness validation on :#{attribute} which is
896
979
  declared to be case-sensitive, but the value the uniqueness matcher used,
897
980
  #{value.inspect}, doesn't contain any alpha characters, so using it to
898
- to test the case-sensitivity part of the validation is ineffective. There are
981
+ test the case-sensitivity part of the validation is ineffective. There are
899
982
  two possible solutions for this depending on what you're trying to do here:
900
983
 
901
984
  a) If you meant for the validation to be case-sensitive, then you need to give
@@ -912,6 +995,28 @@ http://matchers.shoulda.io/docs/v#{Shoulda::Matchers::VERSION}/file.NonCaseSwapp
912
995
  MESSAGE
913
996
  end
914
997
  end
998
+
999
+ # @private
1000
+ class ExistingRecordInvalid < Shoulda::Matchers::Error
1001
+ include Shoulda::Matchers::ActiveModel::Helpers
1002
+
1003
+ attr_accessor :underlying_exception
1004
+
1005
+ def message
1006
+ <<-MESSAGE.strip
1007
+ validate_uniqueness_of works by matching a new record against an
1008
+ existing record. If there is no existing record, it will create one
1009
+ using the record you provide.
1010
+
1011
+ While doing this, the following error was raised:
1012
+
1013
+ #{Shoulda::Matchers::Util.indent(underlying_exception.message, 2)}
1014
+
1015
+ The best way to fix this is to provide the matcher with a record where
1016
+ any required attributes are filled in with valid values beforehand.
1017
+ MESSAGE
1018
+ end
1019
+ end
915
1020
  end
916
1021
  end
917
1022
  end
@@ -1,6 +1,6 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- VERSION = '3.1.0'.freeze
4
+ VERSION = '3.1.1'.freeze
5
5
  end
6
6
  end
@@ -10,7 +10,7 @@ end
10
10
 
11
11
  describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
12
12
  context "#description" do
13
- it 'describes itself with multiple values' do
13
+ it 'describes itself with two values' do
14
14
  matcher = allow_value('foo', 'bar').for(:baz)
15
15
 
16
16
  expect(matcher.description).to eq(
@@ -18,6 +18,14 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
18
18
  )
19
19
  end
20
20
 
21
+ it 'describes itself with more than two values' do
22
+ matcher = allow_value('foo', 'bar', 'qux').for(:baz)
23
+
24
+ expect(matcher.description).to eq(
25
+ 'allow :baz to be ‹"foo"›, ‹"bar"›, or ‹"qux"›'
26
+ )
27
+ end
28
+
21
29
  it 'describes itself with a single value' do
22
30
  matcher = allow_value('foo').for(:baz)
23
31
 
@@ -227,6 +227,94 @@ within the scope of :non_existent1 and :non_existent2.
227
227
  end
228
228
  end
229
229
 
230
+ context 'when there is more than one validation on the same attribute with different scopes' do
231
+ context 'when a record exists beforehand, where all scopes are set' do
232
+ if column_type != :boolean
233
+ context 'when each validation has the same (default) message' do
234
+ it 'accepts' do
235
+ pending 'this needs another qualifier to properly fix'
236
+
237
+ model = define_model(
238
+ 'Example',
239
+ attribute_name => :string,
240
+ scope1: column_type,
241
+ scope2: column_type
242
+ ) do |m|
243
+ m.validates_uniqueness_of(attribute_name, scope: [:scope1])
244
+ m.validates_uniqueness_of(attribute_name, scope: [:scope2])
245
+ end
246
+
247
+ model.create!(
248
+ attribute_name => dummy_value_for(:string),
249
+ scope1: dummy_value_for(column_type),
250
+ scope2: dummy_value_for(column_type)
251
+ )
252
+
253
+ expect(model.new).to validate_uniqueness.scoped_to(:scope1)
254
+ expect(model.new).to validate_uniqueness.scoped_to(:scope2)
255
+ end
256
+ end
257
+ end
258
+
259
+ context 'when each validation has a different message' do
260
+ it 'accepts' do
261
+ model = define_model(
262
+ 'Example',
263
+ attribute_name => :string,
264
+ scope1: column_type,
265
+ scope2: column_type
266
+ ) do |m|
267
+ m.validates_uniqueness_of(
268
+ attribute_name,
269
+ scope: [:scope1],
270
+ message: 'first message'
271
+ )
272
+ m.validates_uniqueness_of(
273
+ attribute_name,
274
+ scope: [:scope2],
275
+ message: 'second message'
276
+ )
277
+ end
278
+
279
+ model.create!(
280
+ attribute_name => dummy_value_for(:string),
281
+ scope1: dummy_value_for(column_type),
282
+ scope2: dummy_value_for(column_type)
283
+ )
284
+
285
+ expect(model.new).
286
+ to validate_uniqueness.
287
+ scoped_to(:scope1).
288
+ with_message('first message')
289
+
290
+ expect(model.new).
291
+ to validate_uniqueness.
292
+ scoped_to(:scope2).
293
+ with_message('second message')
294
+ end
295
+ end
296
+ end
297
+
298
+ context 'when no record exists beforehand' do
299
+ it 'accepts' do
300
+ pending 'this needs another qualifier to properly fix'
301
+
302
+ model = define_model(
303
+ 'Example',
304
+ attribute_name => :string,
305
+ scope1: column_type,
306
+ scope2: column_type
307
+ ) do |m|
308
+ m.validates_uniqueness_of(attribute_name, scope: [:scope1])
309
+ m.validates_uniqueness_of(attribute_name, scope: [:scope2])
310
+ end
311
+
312
+ expect(model.new).to validate_uniqueness.scoped_to(:scope1)
313
+ expect(model.new).to validate_uniqueness.scoped_to(:scope2)
314
+ end
315
+ end
316
+ end
317
+
230
318
  define_method(:build_attribute) do |attribute_options|
231
319
  attribute_options.deep_merge(
232
320
  column_type: column_type,
@@ -268,7 +356,7 @@ Example did not properly validate that :attr is case-sensitively unique.
268
356
  end
269
357
  end
270
358
 
271
- context 'when the record is created beforehand' do
359
+ context 'when the existing record was created beforehand' do
272
360
  context 'when the subject is a new record' do
273
361
  it 'accepts' do
274
362
  create_record_validating_uniqueness
@@ -277,63 +365,169 @@ Example did not properly validate that :attr is case-sensitively unique.
277
365
  end
278
366
  end
279
367
 
280
- context 'when the subject is an existing record' do
368
+ context 'when the subject is itself the existing record' do
281
369
  it 'accepts' do
282
370
  expect(existing_record_validating_uniqueness).to validate_uniqueness
283
371
  end
284
372
  end
373
+ end
285
374
 
286
- context 'when the validation has no scope and a scope is specified' do
287
- it 'rejects with an appropriate failure message' do
288
- model = define_model_validating_uniqueness(
289
- additional_attributes: [:other]
290
- )
291
- create_record_from(model)
292
- record = build_record_from(model)
375
+ context 'when the existing record was not created beforehand' do
376
+ context 'and the subject is empty' do
377
+ context 'and the attribute being tested is required' do
378
+ it 'can save the subject without the attribute being set' do
379
+ options = { attribute_name: :attr }
380
+ model = define_model_validating_uniqueness(options) do |m|
381
+ m.validates_presence_of :attr
382
+ end
293
383
 
294
- assertion = lambda do
295
- expect(record).to validate_uniqueness.scoped_to(:other)
384
+ record = model.new
385
+
386
+ expect(record).to validate_uniqueness
296
387
  end
388
+ end
297
389
 
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
390
+ context 'and the attribute being tested are required along with other attributes' do
391
+ it 'can save the subject without the attributes being set' do
392
+ options = {
393
+ attribute_name: :attr,
394
+ additional_attributes: [:required_attribute]
395
+ }
396
+ model = define_model_validating_uniqueness(options) do |m|
397
+ m.validates_presence_of :attr
398
+ m.validates_presence_of :required_attribute
399
+ end
304
400
 
305
- expect(&assertion).to fail_with_message(message)
401
+ expect(model.new).to validate_uniqueness
402
+ end
306
403
  end
307
- end
308
- end
309
404
 
310
- context 'when the record is not created beforehand' do
311
- it 'creates the record automatically' do
312
- model = define_model_validating_uniqueness
313
- assertion = -> {
314
- record = build_record_from(model)
315
- expect(record).to validate_uniqueness
316
- }
317
- expect(&assertion).to change(model, :count).from(0).to(1)
405
+ context 'and the attribute being tested has other validations on it' do
406
+ it 'can save the subject without it being completely valid' do
407
+ options = { attribute_name: :attr }
408
+
409
+ model = define_model_validating_uniqueness(options) do |m|
410
+ m.validates_presence_of :attr
411
+ m.validates_numericality_of :attr
412
+ end
413
+
414
+ expect(model.new).to validate_uniqueness
415
+ end
416
+ end
417
+
418
+ context 'and the table has non-nullable columns other than the attribute being validated' do
419
+ context 'which are set beforehand' do
420
+ it 'can save the subject' do
421
+ options = {
422
+ additional_attributes: [
423
+ { name: :required_attribute, options: { null: false } }
424
+ ]
425
+ }
426
+ model = define_model_validating_uniqueness(options)
427
+ record = model.new
428
+ record.required_attribute = 'something'
429
+
430
+ expect(record).to validate_uniqueness
431
+ end
432
+ end
433
+
434
+ context 'which are not set beforehand' do
435
+ it 'raises a useful exception' do
436
+ options = {
437
+ additional_attributes: [
438
+ { name: :required_attribute, options: { null: false } }
439
+ ]
440
+ }
441
+ model = define_model_validating_uniqueness(options)
442
+
443
+ assertion = lambda do
444
+ expect(model.new).to validate_uniqueness
445
+ end
446
+
447
+ expect(&assertion).to raise_error(
448
+ described_class::ExistingRecordInvalid
449
+ )
450
+ end
451
+ end
452
+ end
453
+
454
+ context 'and the model has required attributes other than the attribute being validated' do
455
+ it 'can save the subject without the attributes being set' do
456
+ options = {
457
+ additional_attributes: [:required_attribute]
458
+ }
459
+ model = define_model_validating_uniqueness(options) do |m|
460
+ m.validates_presence_of :required_attribute
461
+ end
462
+
463
+ expect(model.new).to validate_uniqueness
464
+ end
465
+ end
318
466
  end
319
467
 
320
- context 'and the table has required attributes other than the attribute being validated, set beforehand' do
321
- it 'does not require the record to be persisted' do
322
- options = {
323
- additional_attributes: [
324
- { name: :required_attribute, options: { null: false } }
325
- ]
468
+ context 'and the subject is not empty' do
469
+ it 'creates the record automatically from the subject' do
470
+ model = define_model_validating_uniqueness
471
+ assertion = -> {
472
+ record = build_record_from(model)
473
+ expect(record).to validate_uniqueness
326
474
  }
327
- model = define_model_validating_uniqueness(options) do |m|
328
- m.validates_presence_of :required_attribute
475
+ expect(&assertion).to change(model, :count).from(0).to(1)
476
+ end
477
+
478
+ context 'and the table has required attributes other than the attribute being validated, set beforehand' do
479
+ it 'can save the subject' do
480
+ options = {
481
+ additional_attributes: [
482
+ { name: :required_attribute, options: { null: false } }
483
+ ]
484
+ }
485
+ model = define_model_validating_uniqueness(options)
486
+
487
+ record = build_record_from(model, required_attribute: 'something')
488
+ expect(record).to validate_uniqueness
329
489
  end
490
+ end
330
491
 
331
- record = build_record_from(model, required_attribute: 'something')
332
- expect(record).to validate_uniqueness
492
+ context 'and the model has required attributes other than the attribute being validated, set beforehand' do
493
+ it 'can save the subject' do
494
+ options = {
495
+ additional_attributes: [:required_attribute]
496
+ }
497
+ model = define_model_validating_uniqueness(options) do |m|
498
+ m.validates_presence_of :required_attribute
499
+ end
500
+
501
+ record = build_record_from(model, required_attribute: 'something')
502
+ expect(record).to validate_uniqueness
503
+ end
333
504
  end
334
505
  end
335
506
  end
336
507
 
508
+ context 'when the validation has no scope and a scope is specified' do
509
+ it 'rejects with an appropriate failure message' do
510
+ model = define_model_validating_uniqueness(
511
+ additional_attributes: [:other]
512
+ )
513
+ create_record_from(model)
514
+ record = build_record_from(model)
515
+
516
+ assertion = lambda do
517
+ expect(record).to validate_uniqueness.scoped_to(:other)
518
+ end
519
+
520
+ message = <<-MESSAGE
521
+ Example did not properly validate that :attr is case-sensitively unique
522
+ within the scope of :other.
523
+ Expected the validation to be scoped to :other, but it was not scoped
524
+ to anything.
525
+ MESSAGE
526
+
527
+ expect(&assertion).to fail_with_message(message)
528
+ end
529
+ end
530
+
337
531
  context 'and the validation has a custom message' do
338
532
  context 'when no message is specified' do
339
533
  it 'rejects with an appropriate failure message' do
@@ -1307,7 +1501,7 @@ Example did not properly validate that :name is case-sensitively unique.
1307
1501
  SecureRandom.uuid
1308
1502
  elsif value.is_a?(Time)
1309
1503
  value + 1
1310
- elsif value.in?([true, false])
1504
+ elsif [true, false].include?(value)
1311
1505
  !value
1312
1506
  elsif value.respond_to?(:next)
1313
1507
  value.next
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoulda-matchers
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tammer Saleh
@@ -14,20 +14,20 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2016-01-10 00:00:00.000000000 Z
17
+ date: 2016-01-28 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: activesupport
21
21
  requirement: !ruby/object:Gem::Requirement
22
22
  requirements:
23
- - - '>='
23
+ - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: 4.0.0
26
26
  type: :runtime
27
27
  prerelease: false
28
28
  version_requirements: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - '>='
30
+ - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 4.0.0
33
33
  description: Making tests easy on the fingers and eyes
@@ -36,11 +36,11 @@ executables: []
36
36
  extensions: []
37
37
  extra_rdoc_files: []
38
38
  files:
39
- - .gitignore
40
- - .hound.yml
41
- - .hound_config/ruby.yml
42
- - .travis.yml
43
- - .yardopts
39
+ - ".gitignore"
40
+ - ".hound.yml"
41
+ - ".hound_config/ruby.yml"
42
+ - ".travis.yml"
43
+ - ".yardopts"
44
44
  - Appraisals
45
45
  - CONTRIBUTING.md
46
46
  - Gemfile
@@ -355,17 +355,17 @@ require_paths:
355
355
  - lib
356
356
  required_ruby_version: !ruby/object:Gem::Requirement
357
357
  requirements:
358
- - - '>='
358
+ - - ">="
359
359
  - !ruby/object:Gem::Version
360
360
  version: 2.0.0
361
361
  required_rubygems_version: !ruby/object:Gem::Requirement
362
362
  requirements:
363
- - - '>='
363
+ - - ">="
364
364
  - !ruby/object:Gem::Version
365
365
  version: '0'
366
366
  requirements: []
367
367
  rubyforge_project:
368
- rubygems_version: 2.2.2
368
+ rubygems_version: 2.5.1
369
369
  signing_key:
370
370
  specification_version: 4
371
371
  summary: Making tests easy on the fingers and eyes