shoulda-matchers 3.1.0 → 3.1.1

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