shoulda-matchers 2.6.2 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +6 -1
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +26 -22
  5. data/NEWS.md +52 -1
  6. data/README.md +8 -6
  7. data/Rakefile +1 -1
  8. data/doc_config/gh-pages/index.html.erb +1 -1
  9. data/doc_config/yard/templates/default/fulldoc/html/css/global.css +1 -1
  10. data/doc_config/yard/templates/default/fulldoc/html/css/style.css +26 -0
  11. data/features/rails_integration.feature +18 -3
  12. data/features/step_definitions/rails_steps.rb +21 -3
  13. data/gemfiles/3.0.gemfile +1 -1
  14. data/gemfiles/3.0.gemfile.lock +30 -24
  15. data/gemfiles/3.1.gemfile +1 -1
  16. data/gemfiles/3.1.gemfile.lock +32 -26
  17. data/gemfiles/3.1_1.9.2.gemfile +1 -1
  18. data/gemfiles/3.1_1.9.2.gemfile.lock +22 -19
  19. data/gemfiles/3.2.gemfile +1 -1
  20. data/gemfiles/3.2.gemfile.lock +32 -26
  21. data/gemfiles/3.2_1.9.2.gemfile +1 -1
  22. data/gemfiles/3.2_1.9.2.gemfile.lock +8 -8
  23. data/gemfiles/4.0.0.gemfile +2 -2
  24. data/gemfiles/4.0.0.gemfile.lock +35 -31
  25. data/gemfiles/4.0.1.gemfile +2 -2
  26. data/gemfiles/4.0.1.gemfile.lock +24 -22
  27. data/gemfiles/4.1.gemfile +1 -1
  28. data/gemfiles/4.1.gemfile.lock +26 -22
  29. data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +10 -3
  30. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +24 -6
  31. data/lib/shoulda/matchers/active_model.rb +2 -2
  32. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +1 -1
  33. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +1 -1
  34. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +5 -3
  35. data/lib/shoulda/matchers/active_model/{ensure_exclusion_of_matcher.rb → validate_exclusion_of_matcher.rb} +20 -10
  36. data/lib/shoulda/matchers/active_model/{ensure_inclusion_of_matcher.rb → validate_inclusion_of_matcher.rb} +52 -28
  37. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +28 -19
  38. data/lib/shoulda/matchers/active_record.rb +18 -16
  39. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +4 -4
  40. data/lib/shoulda/matchers/active_record/association_matcher.rb +17 -12
  41. data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +86 -0
  42. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +19 -0
  43. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +2 -1
  44. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +138 -0
  45. data/lib/shoulda/matchers/independent.rb +3 -2
  46. data/lib/shoulda/matchers/independent/{delegate_matcher.rb → delegate_method_matcher.rb} +69 -49
  47. data/lib/shoulda/matchers/independent/delegate_method_matcher/stubbed_target.rb +37 -0
  48. data/lib/shoulda/matchers/independent/delegate_method_matcher/target_not_defined_error.rb +15 -0
  49. data/lib/shoulda/matchers/version.rb +1 -1
  50. data/lib/shoulda/matchers/warn.rb +30 -2
  51. data/spec/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +6 -0
  52. data/spec/shoulda/matchers/action_controller/set_session_matcher_spec.rb +67 -5
  53. data/spec/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb +9 -9
  54. data/spec/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +30 -3
  55. data/spec/shoulda/matchers/active_model/{ensure_exclusion_of_matcher_spec.rb → validate_exclusion_of_matcher_spec.rb} +29 -13
  56. data/spec/shoulda/matchers/active_model/{ensure_inclusion_of_matcher_spec.rb → validate_inclusion_of_matcher_spec.rb} +34 -16
  57. data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +35 -0
  58. data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +56 -1
  59. data/spec/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +101 -0
  60. data/spec/shoulda/matchers/doublespeak/object_double_spec.rb +6 -6
  61. data/spec/shoulda/matchers/independent/{delegate_matcher → delegate_method_matcher}/stubbed_target_spec.rb +1 -1
  62. data/spec/shoulda/matchers/independent/{delegate_matcher_spec.rb → delegate_method_matcher_spec.rb} +88 -29
  63. data/spec/spec_helper.rb +2 -3
  64. data/spec/support/fail_with_message_including_matcher.rb +14 -3
  65. data/spec/support/fail_with_message_matcher.rb +14 -2
  66. data/spec/support/rails_versions.rb +4 -0
  67. metadata +19 -14
  68. data/lib/shoulda/matchers/independent/delegate_matcher/stubbed_target.rb +0 -35
@@ -28,7 +28,7 @@ module Shoulda
28
28
  # @private
29
29
  class FilterParamMatcher
30
30
  def initialize(key)
31
- @key = key.to_s
31
+ @key = key
32
32
  end
33
33
 
34
34
  def matches?(controller)
@@ -52,11 +52,18 @@ module Shoulda
52
52
  private
53
53
 
54
54
  def filters_key?
55
- filtered_keys.include?(@key)
55
+ filtered_keys.any? do |filter|
56
+ case filter
57
+ when Regexp
58
+ filter =~ @key
59
+ else
60
+ filter == @key
61
+ end
62
+ end
56
63
  end
57
64
 
58
65
  def filtered_keys
59
- Rails.application.config.filter_parameters.map(&:to_s)
66
+ Rails.application.config.filter_parameters
60
67
  end
61
68
  end
62
69
  end
@@ -38,28 +38,46 @@ module Shoulda
38
38
  # particular value.
39
39
  #
40
40
  # class PostsController < ApplicationController
41
- # def show
41
+ # def index
42
42
  # session[:foo] = 'bar'
43
43
  # end
44
+ #
45
+ # def show
46
+ # session[:foo] = nil
47
+ # end
44
48
  # end
45
49
  #
46
50
  # # RSpec
47
51
  # describe PostsController do
48
- # describe 'GET #show' do
49
- # before { get :show }
52
+ # describe 'GET #index' do
53
+ # before { get :index }
50
54
  #
51
55
  # it { should set_session(:foo).to('bar') }
52
56
  # it { should_not set_session(:foo).to('something else') }
57
+ # it { should_not set_session(:foo).to(nil) }
58
+ # end
59
+ #
60
+ # describe 'GET #show' do
61
+ # before { get :show }
62
+ #
63
+ # it { should set_session(:foo).to(nil) }
53
64
  # end
54
65
  # end
55
66
  #
56
67
  # # Test::Unit
57
68
  # class PostsControllerTest < ActionController::TestCase
58
- # context 'GET #show' do
59
- # setup { get :show }
69
+ # context 'GET #index' do
70
+ # setup { get :index }
60
71
  #
61
72
  # should set_session(:foo).to('bar')
62
73
  # should_not set_session(:foo).to('something else')
74
+ # should_not set_session(:foo).to(nil)
75
+ # end
76
+ #
77
+ # context 'GET #show' do
78
+ # setup { get :show }
79
+ #
80
+ # should set_session(:foo).to(nil)
63
81
  # end
64
82
  # end
65
83
  #
@@ -129,7 +147,7 @@ module Shoulda
129
147
 
130
148
  def assigned_correct_value?
131
149
  if assigned_value?
132
- if value_or_default_value.nil?
150
+ if !defined?(@value)
133
151
  true
134
152
  else
135
153
  assigned_value == value_or_default_value
@@ -5,8 +5,8 @@ require 'shoulda/matchers/active_model/exception_message_finder'
5
5
  require 'shoulda/matchers/active_model/allow_value_matcher'
6
6
  require 'shoulda/matchers/active_model/disallow_value_matcher'
7
7
  require 'shoulda/matchers/active_model/ensure_length_of_matcher'
8
- require 'shoulda/matchers/active_model/ensure_inclusion_of_matcher'
9
- require 'shoulda/matchers/active_model/ensure_exclusion_of_matcher'
8
+ require 'shoulda/matchers/active_model/validate_inclusion_of_matcher'
9
+ require 'shoulda/matchers/active_model/validate_exclusion_of_matcher'
10
10
  require 'shoulda/matchers/active_model/validate_absence_of_matcher'
11
11
  require 'shoulda/matchers/active_model/validate_presence_of_matcher'
12
12
  require 'shoulda/matchers/active_model/validate_uniqueness_of_matcher'
@@ -209,7 +209,7 @@ module Shoulda
209
209
  self
210
210
  end
211
211
 
212
- def _after_setting_value(&callback) # :nodoc:
212
+ def _after_setting_value(&callback)
213
213
  self.after_setting_value_callback = callback
214
214
  end
215
215
 
@@ -57,7 +57,7 @@ module Shoulda
57
57
  #
58
58
  # ##### is_equal_to
59
59
  #
60
- # Use `is_at_equal` to test usage of the `:is` option. This asserts that
60
+ # Use `is_equal_to` to test usage of the `:is` option. This asserts that
61
61
  # the attribute can take a string which is exactly equal to the given
62
62
  # length and cannot take a string which is shorter or longer.
63
63
  #
@@ -82,8 +82,10 @@ module Shoulda
82
82
  else
83
83
  obj
84
84
  end
85
- elsif attribute_class == Fixnum
85
+ elsif [Fixnum, Float].include?(attribute_class)
86
86
  1
87
+ elsif attribute_class == BigDecimal
88
+ BigDecimal.new(1, 0)
87
89
  elsif !attribute_class || attribute_class == String
88
90
  'an arbitrary value'
89
91
  else
@@ -93,8 +95,8 @@ module Shoulda
93
95
 
94
96
  def attribute_class
95
97
  @subject.class.respond_to?(:columns_hash) &&
96
- @subject.class.columns_hash[@attribute].respond_to?(:klass) &&
97
- @subject.class.columns_hash[@attribute].klass
98
+ @subject.class.columns_hash[@attribute.to_s].respond_to?(:klass) &&
99
+ @subject.class.columns_hash[@attribute.to_s].klass
98
100
  end
99
101
 
100
102
  def collection?
@@ -1,7 +1,7 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  module ActiveModel
4
- # The `ensure_exclusion_of` matcher tests usage of the
4
+ # The `validate_exclusion_of` matcher tests usage of the
5
5
  # `validates_exclusion_of` validation, asserting that an attribute cannot
6
6
  # take a blacklist of values, and inversely, can take values outside of
7
7
  # this list.
@@ -18,14 +18,14 @@ module Shoulda
18
18
  # # RSpec
19
19
  # describe Game do
20
20
  # it do
21
- # should ensure_exclusion_of(:supported_os).
21
+ # should validate_exclusion_of(:supported_os).
22
22
  # in_array(['Mac', 'Linux'])
23
23
  # end
24
24
  # end
25
25
  #
26
26
  # # Test::Unit
27
27
  # class GameTest < ActiveSupport::TestCase
28
- # should ensure_exclusion_of(:supported_os).
28
+ # should validate_exclusion_of(:supported_os).
29
29
  # in_array(['Mac', 'Linux'])
30
30
  # end
31
31
  #
@@ -41,14 +41,14 @@ module Shoulda
41
41
  # # RSpec
42
42
  # describe Game do
43
43
  # it do
44
- # should ensure_exclusion_of(:floors_with_enemies).
44
+ # should validate_exclusion_of(:floors_with_enemies).
45
45
  # in_range(5..8)
46
46
  # end
47
47
  # end
48
48
  #
49
49
  # # Test::Unit
50
50
  # class GameTest < ActiveSupport::TestCase
51
- # should ensure_exclusion_of(:floors_with_enemies).
51
+ # should validate_exclusion_of(:floors_with_enemies).
52
52
  # in_range(5..8)
53
53
  # end
54
54
  #
@@ -70,7 +70,7 @@ module Shoulda
70
70
  # # RSpec
71
71
  # describe Game do
72
72
  # it do
73
- # should ensure_exclusion_of(:weapon).
73
+ # should validate_exclusion_of(:weapon).
74
74
  # in_array(['pistol', 'paintball gun', 'stick']).
75
75
  # with_message('You chose a puny weapon')
76
76
  # end
@@ -78,19 +78,29 @@ module Shoulda
78
78
  #
79
79
  # # Test::Unit
80
80
  # class GameTest < ActiveSupport::TestCase
81
- # should ensure_exclusion_of(:weapon).
81
+ # should validate_exclusion_of(:weapon).
82
82
  # in_array(['pistol', 'paintball gun', 'stick']).
83
83
  # with_message('You chose a puny weapon')
84
84
  # end
85
85
  #
86
- # @return [EnsureExclusionOfMatcher]
86
+ # @return [ValidateExclusionOfMatcher]
87
87
  #
88
+ def validate_exclusion_of(attr)
89
+ ValidateExclusionOfMatcher.new(attr)
90
+ end
91
+
92
+ # @deprecated Use {#validate_exclusion_of} instead.
93
+ # @return [ValidateExclusionOfMatcher]
88
94
  def ensure_exclusion_of(attr)
89
- EnsureExclusionOfMatcher.new(attr)
95
+ Shoulda::Matchers.warn_about_deprecated_method(
96
+ :ensure_exclusion_of,
97
+ :validate_exclusion_of
98
+ )
99
+ validate_exclusion_of(attr)
90
100
  end
91
101
 
92
102
  # @private
93
- class EnsureExclusionOfMatcher < ValidationMatcher
103
+ class ValidateExclusionOfMatcher < ValidationMatcher
94
104
  def initialize(attribute)
95
105
  super(attribute)
96
106
  @array = nil
@@ -3,7 +3,7 @@ require 'bigdecimal'
3
3
  module Shoulda
4
4
  module Matchers
5
5
  module ActiveModel
6
- # The `ensure_inclusion_of` matcher tests usage of the
6
+ # The `validate_inclusion_of` matcher tests usage of the
7
7
  # `validates_inclusion_of` validation, asserting that an attribute can
8
8
  # take a whitelist of values and cannot take values outside of this list.
9
9
  #
@@ -19,14 +19,14 @@ module Shoulda
19
19
  # # RSpec
20
20
  # describe Issue do
21
21
  # it do
22
- # should ensure_inclusion_of(:state).
22
+ # should validate_inclusion_of(:state).
23
23
  # in_array(%w(open resolved unresolved))
24
24
  # end
25
25
  # end
26
26
  #
27
27
  # # Test::Unit
28
28
  # class IssueTest < ActiveSupport::TestCase
29
- # should ensure_inclusion_of(:state).
29
+ # should validate_inclusion_of(:state).
30
30
  # in_array(%w(open resolved unresolved))
31
31
  # end
32
32
  #
@@ -41,15 +41,31 @@ module Shoulda
41
41
  #
42
42
  # # RSpec
43
43
  # describe Issue do
44
- # it { should ensure_inclusion_of(:state).in_range(1..5) }
44
+ # it { should validate_inclusion_of(:state).in_range(1..5) }
45
45
  # end
46
46
  #
47
47
  # # Test::Unit
48
48
  # class IssueTest < ActiveSupport::TestCase
49
- # should ensure_inclusion_of(:state).in_range(1..5)
49
+ # should validate_inclusion_of(:state).in_range(1..5)
50
50
  # end
51
51
  #
52
- # #### Optional qualifiers
52
+ # #### Caveats
53
+ #
54
+ # We discourage using `validate_inclusion_of` with boolean columns. In
55
+ # fact, there is never a case where a boolean column will be anything but
56
+ # true, false, or nil, as ActiveRecord will type-cast an incoming value to
57
+ # one of these three values. That means there isn't any way we can refute
58
+ # this logic in a test. Hence, this will produce a warning:
59
+ #
60
+ # it { should validate_inclusion_of(:imported).in_array([true, false]) }
61
+ #
62
+ # The only case where `validate_inclusion_of` *could* be appropriate is
63
+ # for ensuring that a boolean column accepts nil, but we recommend
64
+ # using `allow_value` instead, like this:
65
+ #
66
+ # it { should allow_value(nil).for(:imported) }
67
+ #
68
+ # #### Qualifiers
53
69
  #
54
70
  # ##### with_message
55
71
  #
@@ -67,7 +83,7 @@ module Shoulda
67
83
  # # RSpec
68
84
  # describe Issue do
69
85
  # it do
70
- # should ensure_inclusion_of(:severity).
86
+ # should validate_inclusion_of(:severity).
71
87
  # in_array(%w(low medium high)).
72
88
  # with_message('Severity must be low, medium, or high')
73
89
  # end
@@ -75,7 +91,7 @@ module Shoulda
75
91
  #
76
92
  # # Test::Unit
77
93
  # class IssueTest < ActiveSupport::TestCase
78
- # should ensure_inclusion_of(:severity).
94
+ # should validate_inclusion_of(:severity).
79
95
  # in_array(%w(low medium high)).
80
96
  # with_message('Severity must be low, medium, or high')
81
97
  # end
@@ -103,7 +119,7 @@ module Shoulda
103
119
  # # RSpec
104
120
  # describe Person do
105
121
  # it do
106
- # should ensure_inclusion_of(:age).
122
+ # should validate_inclusion_of(:age).
107
123
  # in_range(0..65).
108
124
  # with_low_message('You do not receive any benefits')
109
125
  # end
@@ -111,7 +127,7 @@ module Shoulda
111
127
  #
112
128
  # # Test::Unit
113
129
  # class PersonTest < ActiveSupport::TestCase
114
- # should ensure_inclusion_of(:age).
130
+ # should validate_inclusion_of(:age).
115
131
  # in_range(0..65).
116
132
  # with_low_message('You do not receive any benefits')
117
133
  # end
@@ -139,7 +155,7 @@ module Shoulda
139
155
  # # RSpec
140
156
  # describe Person do
141
157
  # it do
142
- # should ensure_inclusion_of(:age).
158
+ # should validate_inclusion_of(:age).
143
159
  # in_range(0..21).
144
160
  # with_high_message("You're too old for this stuff")
145
161
  # end
@@ -147,7 +163,7 @@ module Shoulda
147
163
  #
148
164
  # # Test::Unit
149
165
  # class PersonTest < ActiveSupport::TestCase
150
- # should ensure_inclusion_of(:age).
166
+ # should validate_inclusion_of(:age).
151
167
  # in_range(0..21).
152
168
  # with_high_message("You're too old for this stuff")
153
169
  # end
@@ -169,7 +185,7 @@ module Shoulda
169
185
  # # RSpec
170
186
  # describe Issue do
171
187
  # it do
172
- # should ensure_inclusion_of(:state).
188
+ # should validate_inclusion_of(:state).
173
189
  # in_array(%w(open resolved unresolved)).
174
190
  # allow_nil
175
191
  # end
@@ -177,7 +193,7 @@ module Shoulda
177
193
  #
178
194
  # # Test::Unit
179
195
  # class IssueTest < ActiveSupport::TestCase
180
- # should ensure_inclusion_of(:state).
196
+ # should validate_inclusion_of(:state).
181
197
  # in_array(%w(open resolved unresolved)).
182
198
  # allow_nil
183
199
  # end
@@ -199,7 +215,7 @@ module Shoulda
199
215
  # # RSpec
200
216
  # describe Issue do
201
217
  # it do
202
- # should ensure_inclusion_of(:state).
218
+ # should validate_inclusion_of(:state).
203
219
  # in_array(%w(open resolved unresolved)).
204
220
  # allow_blank
205
221
  # end
@@ -207,35 +223,43 @@ module Shoulda
207
223
  #
208
224
  # # Test::Unit
209
225
  # class IssueTest < ActiveSupport::TestCase
210
- # should ensure_inclusion_of(:state).
226
+ # should validate_inclusion_of(:state).
211
227
  # in_array(%w(open resolved unresolved)).
212
228
  # allow_blank
213
229
  # end
214
230
  #
215
- # @return [EnsureInclusionOfMatcher]
231
+ # @return [ValidateInclusionOfMatcher]
216
232
  #
233
+ def validate_inclusion_of(attr)
234
+ ValidateInclusionOfMatcher.new(attr)
235
+ end
236
+
237
+ # @deprecated Use {#validate_inclusion_of} instead.
238
+ # @return [ValidateInclusionOfMatcher]
217
239
  def ensure_inclusion_of(attr)
218
- EnsureInclusionOfMatcher.new(attr)
240
+ Shoulda::Matchers.warn_about_deprecated_method(
241
+ :ensure_inclusion_of,
242
+ :validate_inclusion_of
243
+ )
244
+ validate_inclusion_of(attr)
219
245
  end
220
246
 
221
247
  # @private
222
- class EnsureInclusionOfMatcher < ValidationMatcher
248
+ class ValidateInclusionOfMatcher < ValidationMatcher
223
249
  ARBITRARY_OUTSIDE_STRING = 'shouldamatchersteststring'
224
250
  ARBITRARY_OUTSIDE_FIXNUM = 123456789
225
251
  ARBITRARY_OUTSIDE_DECIMAL = BigDecimal.new('0.123456789')
226
252
  BOOLEAN_ALLOWS_BOOLEAN_MESSAGE = <<EOT
227
- You are using `ensure_inclusion_of` to assert that a boolean column allows
228
- boolean values and disallows non-boolean ones. Assuming you are using
229
- `validates_format_of` in your model, be aware that it is not possible to fully
230
- test this, and in fact the validation is superfluous, as boolean columns will
231
- automatically convert non-boolean values to boolean ones. Hence, you should
232
- consider removing this test and the corresponding validation.
253
+ You are using `validate_inclusion_of` to assert that a boolean column allows
254
+ boolean values and disallows non-boolean ones. Be aware that it is not possible
255
+ to fully test this, as boolean columns will automatically convert non-boolean
256
+ values to boolean ones. Hence, you should consider removing this test.
233
257
  EOT
234
258
  BOOLEAN_ALLOWS_NIL_MESSAGE = <<EOT
235
- You are using `ensure_inclusion_of` to assert that a boolean column allows nil.
259
+ You are using `validate_inclusion_of` to assert that a boolean column allows nil.
236
260
  Be aware that it is not possible to fully test this, as anything other than
237
261
  true, false or nil will be converted to false. Hence, you should consider
238
- removing this test and the corresponding validation.
262
+ removing this test.
239
263
  EOT
240
264
 
241
265
  def initialize(attribute)
@@ -372,7 +396,7 @@ EOT
372
396
  def disallows_value_outside_of_array?
373
397
  if attribute_type == :boolean
374
398
  case @array
375
- when [true, false]
399
+ when [false, true], [true, false]
376
400
  Shoulda::Matchers.warn BOOLEAN_ALLOWS_BOOLEAN_MESSAGE
377
401
  return true
378
402
  when [nil]
@@ -4,10 +4,10 @@ module Shoulda
4
4
  # The `validate_uniqueness_of` matcher tests usage of the
5
5
  # `validates_uniqueness_of` validation. It first checks for an existing
6
6
  # instance of your model in the database, creating one if necessary. It
7
- # then takes a new record and asserts that it fails validation if the
8
- # attribute or attributes you've specified in the validation are set to
9
- # values which are the same as those of the pre-existing record (thereby
10
- # failing the uniqueness check).
7
+ # then takes a new instance of that model and asserts that it fails
8
+ # validation if the attribute or attributes you've specified in the
9
+ # validation are set to values which are the same as those of the
10
+ # pre-existing record (thereby failing the uniqueness check).
11
11
  #
12
12
  # class Post < ActiveRecord::Base
13
13
  # validates_uniqueness_of :permalink
@@ -29,8 +29,8 @@ module Shoulda
29
29
  # before, it will create an instance of your model if one doesn't already
30
30
  # exist. Sometimes this step fails, especially if you have database-level
31
31
  # restrictions on any attributes other than the one which is unique. In
32
- # this case, the solution is to **create a record manually** before you
33
- # call `validate_uniqueness_of`.
32
+ # this case, the solution is to populate these attributes with values
33
+ # before you call `validate_uniqueness_of`.
34
34
  #
35
35
  # For example, say you have the following migration and model:
36
36
  #
@@ -62,23 +62,27 @@ module Shoulda
62
62
  # ActiveRecord::StatementInvalid:
63
63
  # SQLite3::ConstraintException: posts.content may not be NULL: INSERT INTO "posts" ("title") VALUES (?)
64
64
  #
65
- # To fix this, you'll need to write this instead:
65
+ # This happens because `validate_uniqueness_of` tries to create a new post
66
+ # 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:
66
69
  #
67
70
  # describe Post do
68
- # it do
69
- # Post.create!(content: 'Here is the content')
70
- # should validate_uniqueness_of(:title)
71
+ # describe "validations" do
72
+ # subject { Post.new(content: 'Here is the content') }
73
+ # it { should validate_uniqueness_of(:title) }
71
74
  # end
72
75
  # end
73
76
  #
74
77
  # Or, if you're using
75
78
  # [FactoryGirl](http://github.com/thoughtbot/factory_girl) and you have a
76
- # `post` factory defined which automatically sets `content`, you can say:
79
+ # `post` factory defined which automatically fills in `content`, you can
80
+ # say:
77
81
  #
78
82
  # describe Post do
79
- # it do
80
- # FactoryGirl.create(:post)
81
- # should validate_uniqueness_of(:title)
83
+ # describe "validations" do
84
+ # subject { FactoryGirl.build(:post) }
85
+ # it { should validate_uniqueness_of(:title) }
82
86
  # end
83
87
  # end
84
88
  #
@@ -209,6 +213,7 @@ module Shoulda
209
213
  end
210
214
 
211
215
  def matches?(subject)
216
+ @original_subject = subject
212
217
  @subject = subject.class.new
213
218
  @expected_message ||= :taken
214
219
  set_scoped_attributes &&
@@ -253,16 +258,20 @@ module Shoulda
253
258
  value = 'a'
254
259
  end
255
260
 
256
- @subject.class.new.tap do |instance|
261
+ @original_subject.tap do |instance|
257
262
  instance.__send__("#{@attribute}=", value)
258
- if has_secure_password?
259
- instance.password = 'password'
260
- instance.password_confirmation = 'password'
261
- end
263
+ ensure_secure_password_set(instance)
262
264
  instance.save(validate: false)
263
265
  end
264
266
  end
265
267
 
268
+ def ensure_secure_password_set(instance)
269
+ if has_secure_password?
270
+ instance.password = "password"
271
+ instance.password_confirmation = "password"
272
+ end
273
+ end
274
+
266
275
  def has_secure_password?
267
276
  @subject.class.ancestors.map(&:to_s).include?('ActiveModel::SecurePassword::InstanceMethodsOnActivation')
268
277
  end