mcmire-shoulda-matchers 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +32 -0
  4. data/.yardopts +7 -0
  5. data/Appraisals +45 -0
  6. data/CONTRIBUTING.md +41 -0
  7. data/Gemfile +31 -0
  8. data/Gemfile.lock +166 -0
  9. data/MIT-LICENSE +22 -0
  10. data/NEWS.md +299 -0
  11. data/README.md +163 -0
  12. data/Rakefile +116 -0
  13. data/doc_config/gh-pages/index.html.erb +9 -0
  14. data/doc_config/yard/setup.rb +22 -0
  15. data/doc_config/yard/templates/default/fulldoc/html/css/bootstrap.css +5967 -0
  16. data/doc_config/yard/templates/default/fulldoc/html/css/full_list.css +12 -0
  17. data/doc_config/yard/templates/default/fulldoc/html/css/global.css +45 -0
  18. data/doc_config/yard/templates/default/fulldoc/html/css/solarized.css +69 -0
  19. data/doc_config/yard/templates/default/fulldoc/html/css/style.css +283 -0
  20. data/doc_config/yard/templates/default/fulldoc/html/full_list.erb +32 -0
  21. data/doc_config/yard/templates/default/fulldoc/html/full_list_class.erb +1 -0
  22. data/doc_config/yard/templates/default/fulldoc/html/full_list_method.erb +8 -0
  23. data/doc_config/yard/templates/default/fulldoc/html/js/app.js +300 -0
  24. data/doc_config/yard/templates/default/fulldoc/html/js/full_list.js +1 -0
  25. data/doc_config/yard/templates/default/fulldoc/html/js/jquery.stickyheaders.js +289 -0
  26. data/doc_config/yard/templates/default/fulldoc/html/js/underscore.min.js +6 -0
  27. data/doc_config/yard/templates/default/fulldoc/html/setup.rb +8 -0
  28. data/doc_config/yard/templates/default/layout/html/breadcrumb.erb +14 -0
  29. data/doc_config/yard/templates/default/layout/html/fonts.erb +1 -0
  30. data/doc_config/yard/templates/default/layout/html/layout.erb +23 -0
  31. data/doc_config/yard/templates/default/layout/html/search.erb +13 -0
  32. data/doc_config/yard/templates/default/layout/html/setup.rb +8 -0
  33. data/doc_config/yard/templates/default/method_details/html/source.erb +10 -0
  34. data/doc_config/yard/templates/default/module/html/box_info.erb +31 -0
  35. data/features/rails_integration.feature +113 -0
  36. data/features/step_definitions/rails_steps.rb +162 -0
  37. data/features/support/env.rb +5 -0
  38. data/gemfiles/3.0.gemfile +24 -0
  39. data/gemfiles/3.0.gemfile.lock +150 -0
  40. data/gemfiles/3.1.gemfile +27 -0
  41. data/gemfiles/3.1.gemfile.lock +173 -0
  42. data/gemfiles/3.2.gemfile +27 -0
  43. data/gemfiles/3.2.gemfile.lock +171 -0
  44. data/gemfiles/4.0.0.gemfile +28 -0
  45. data/gemfiles/4.0.0.gemfile.lock +172 -0
  46. data/gemfiles/4.0.1.gemfile +28 -0
  47. data/gemfiles/4.0.1.gemfile.lock +172 -0
  48. data/lib/shoulda-matchers.rb +1 -0
  49. data/lib/shoulda/matchers.rb +11 -0
  50. data/lib/shoulda/matchers/action_controller.rb +17 -0
  51. data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +64 -0
  52. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +97 -0
  53. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +81 -0
  54. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +117 -0
  55. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +114 -0
  56. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +154 -0
  57. data/lib/shoulda/matchers/action_controller/route_matcher.rb +116 -0
  58. data/lib/shoulda/matchers/action_controller/route_params.rb +48 -0
  59. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +164 -0
  60. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +296 -0
  61. data/lib/shoulda/matchers/active_model.rb +30 -0
  62. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +167 -0
  63. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +314 -0
  64. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +46 -0
  65. data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +160 -0
  66. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +417 -0
  67. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +337 -0
  68. data/lib/shoulda/matchers/active_model/errors.rb +10 -0
  69. data/lib/shoulda/matchers/active_model/exception_message_finder.rb +58 -0
  70. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +92 -0
  71. data/lib/shoulda/matchers/active_model/helpers.rb +46 -0
  72. data/lib/shoulda/matchers/active_model/numericality_matchers.rb +9 -0
  73. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +75 -0
  74. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +27 -0
  75. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +41 -0
  76. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +27 -0
  77. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +26 -0
  78. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +112 -0
  79. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +77 -0
  80. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +121 -0
  81. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +380 -0
  82. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +89 -0
  83. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +372 -0
  84. data/lib/shoulda/matchers/active_model/validation_matcher.rb +97 -0
  85. data/lib/shoulda/matchers/active_model/validation_message_finder.rb +69 -0
  86. data/lib/shoulda/matchers/active_record.rb +22 -0
  87. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +204 -0
  88. data/lib/shoulda/matchers/active_record/association_matcher.rb +901 -0
  89. data/lib/shoulda/matchers/active_record/association_matchers.rb +9 -0
  90. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +41 -0
  91. data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +41 -0
  92. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +81 -0
  93. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +65 -0
  94. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +94 -0
  95. data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +41 -0
  96. data/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +41 -0
  97. data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +63 -0
  98. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +261 -0
  99. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +149 -0
  100. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +72 -0
  101. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +181 -0
  102. data/lib/shoulda/matchers/assertion_error.rb +19 -0
  103. data/lib/shoulda/matchers/error.rb +6 -0
  104. data/lib/shoulda/matchers/integrations/rspec.rb +20 -0
  105. data/lib/shoulda/matchers/integrations/test_unit.rb +30 -0
  106. data/lib/shoulda/matchers/rails_shim.rb +50 -0
  107. data/lib/shoulda/matchers/version.rb +6 -0
  108. data/lib/shoulda/matchers/warn.rb +8 -0
  109. data/shoulda-matchers.gemspec +23 -0
  110. data/spec/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +22 -0
  111. data/spec/shoulda/matchers/action_controller/redirect_to_matcher_spec.rb +42 -0
  112. data/spec/shoulda/matchers/action_controller/render_template_matcher_spec.rb +78 -0
  113. data/spec/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +63 -0
  114. data/spec/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +63 -0
  115. data/spec/shoulda/matchers/action_controller/respond_with_matcher_spec.rb +31 -0
  116. data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +70 -0
  117. data/spec/shoulda/matchers/action_controller/route_params_spec.rb +30 -0
  118. data/spec/shoulda/matchers/action_controller/set_session_matcher_spec.rb +51 -0
  119. data/spec/shoulda/matchers/action_controller/set_the_flash_matcher_spec.rb +153 -0
  120. data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +111 -0
  121. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +170 -0
  122. data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +81 -0
  123. data/spec/shoulda/matchers/active_model/ensure_exclusion_of_matcher_spec.rb +95 -0
  124. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +320 -0
  125. data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +166 -0
  126. data/spec/shoulda/matchers/active_model/exception_message_finder_spec.rb +111 -0
  127. data/spec/shoulda/matchers/active_model/have_secure_password_matcher_spec.rb +20 -0
  128. data/spec/shoulda/matchers/active_model/helpers_spec.rb +158 -0
  129. data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +169 -0
  130. data/spec/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +59 -0
  131. data/spec/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +59 -0
  132. data/spec/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +57 -0
  133. data/spec/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +139 -0
  134. data/spec/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +41 -0
  135. data/spec/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +47 -0
  136. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +331 -0
  137. data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +180 -0
  138. data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +398 -0
  139. data/spec/shoulda/matchers/active_model/validation_message_finder_spec.rb +127 -0
  140. data/spec/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +107 -0
  141. data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +860 -0
  142. data/spec/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +247 -0
  143. data/spec/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +111 -0
  144. data/spec/shoulda/matchers/active_record/have_db_index_matcher_spec.rb +78 -0
  145. data/spec/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +41 -0
  146. data/spec/shoulda/matchers/active_record/serialize_matcher_spec.rb +86 -0
  147. data/spec/spec_helper.rb +26 -0
  148. data/spec/support/active_model_versions.rb +13 -0
  149. data/spec/support/active_resource_builder.rb +29 -0
  150. data/spec/support/activemodel_helpers.rb +19 -0
  151. data/spec/support/capture_helpers.rb +19 -0
  152. data/spec/support/class_builder.rb +42 -0
  153. data/spec/support/controller_builder.rb +74 -0
  154. data/spec/support/fail_with_message_including_matcher.rb +33 -0
  155. data/spec/support/fail_with_message_matcher.rb +32 -0
  156. data/spec/support/i18n_faker.rb +10 -0
  157. data/spec/support/mailer_builder.rb +10 -0
  158. data/spec/support/model_builder.rb +81 -0
  159. data/spec/support/rails_versions.rb +18 -0
  160. data/spec/support/shared_examples/numerical_submatcher.rb +19 -0
  161. data/spec/support/shared_examples/numerical_type_submatcher.rb +17 -0
  162. data/spec/support/test_application.rb +120 -0
  163. data/yard.watchr +5 -0
  164. metadata +281 -0
@@ -0,0 +1,89 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # The `validate_presence_of` matcher tests usage of the
5
+ # `validates_presence_of` validation.
6
+ #
7
+ # class Robot < ActiveRecord::Base
8
+ # validates_presence_of :arms
9
+ # end
10
+ #
11
+ # # RSpec
12
+ # describe Robot do
13
+ # it { should validate_presence_of(:arms) }
14
+ # end
15
+ #
16
+ # # Test::Unit
17
+ # class RobotTest < ActiveSupport::TestCase
18
+ # should validate_presence_of(:arms)
19
+ # end
20
+ #
21
+ # #### Qualifiers
22
+ #
23
+ # ##### with_message
24
+ #
25
+ # Use `with_message` if you are using a custom validation message.
26
+ #
27
+ # class Robot < ActiveRecord::Base
28
+ # validates_presence_of :legs, message: 'Robot has no legs'
29
+ # end
30
+ #
31
+ # # RSpec
32
+ # describe Robot do
33
+ # it { should validate_presence_of(:legs).with_message('Robot has no legs') }
34
+ # end
35
+ #
36
+ # # Test::Unit
37
+ # class RobotTest < ActiveSupport::TestCase
38
+ # should validate_presence_of(:legs).with_message('Robot has no legs')
39
+ # end
40
+ #
41
+ # @return [ValidatePresenceOfMatcher]
42
+ #
43
+ def validate_presence_of(attr)
44
+ ValidatePresenceOfMatcher.new(attr)
45
+ end
46
+
47
+ # @private
48
+ class ValidatePresenceOfMatcher < ValidationMatcher
49
+ def with_message(message)
50
+ @expected_message = message if message
51
+ self
52
+ end
53
+
54
+ def matches?(subject)
55
+ super(subject)
56
+ @expected_message ||= :blank
57
+ disallows_value_of(blank_value, @expected_message)
58
+ end
59
+
60
+ def description
61
+ "require #{@attribute} to be set"
62
+ end
63
+
64
+ private
65
+
66
+ def blank_value
67
+ if collection?
68
+ []
69
+ else
70
+ nil
71
+ end
72
+ end
73
+
74
+ def collection?
75
+ if reflection
76
+ [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
77
+ else
78
+ false
79
+ end
80
+ end
81
+
82
+ def reflection
83
+ @subject.class.respond_to?(:reflect_on_association) &&
84
+ @subject.class.reflect_on_association(@attribute)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,372 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # The `validate_uniqueness_of` matcher tests usage of the
5
+ # `validates_uniqueness_of` validation. It first checks for an existing
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).
11
+ #
12
+ # class Post < ActiveRecord::Base
13
+ # validates_uniqueness_of :permalink
14
+ # end
15
+ #
16
+ # # RSpec
17
+ # describe Post do
18
+ # it { should validate_uniqueness_of(:permalink) }
19
+ # end
20
+ #
21
+ # # Test::Unit
22
+ # class PostTest < ActiveSupport::TestCase
23
+ # should validate_uniqueness_of(:permalink)
24
+ # end
25
+ #
26
+ # #### Caveat
27
+ #
28
+ # This matcher works a bit differently than other matchers. As noted
29
+ # before, it will create an instance of your model if one doesn't already
30
+ # exist. Sometimes this step fails, especially if you have database-level
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`.
34
+ #
35
+ # For example, say you have the following migration and model:
36
+ #
37
+ # class CreatePosts < ActiveRecord::Migration
38
+ # def change
39
+ # create_table :posts do |t|
40
+ # t.string :title
41
+ # t.text :content, null: false
42
+ # end
43
+ # end
44
+ # end
45
+ #
46
+ # class Post < ActiveRecord::Base
47
+ # validates :title, uniqueness: true
48
+ # end
49
+ #
50
+ # You may be tempted to test the model like this:
51
+ #
52
+ # describe Post do
53
+ # it { should validate_uniqueness_of(:title) }
54
+ # end
55
+ #
56
+ # However, running this test will fail with something like:
57
+ #
58
+ # Failures:
59
+ #
60
+ # 1) Post should require case sensitive unique value for title
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 (?)
64
+ #
65
+ # To fix this, you'll need to write this instead:
66
+ #
67
+ # describe Post do
68
+ # it do
69
+ # Post.create!(content: 'Here is the content')
70
+ # should validate_uniqueness_of(:title)
71
+ # end
72
+ # end
73
+ #
74
+ # Or, if you're using
75
+ # [FactoryGirl](http://github.com/thoughtbot/factory_girl) and you have a
76
+ # `post` factory defined which automatically sets `content`, you can say:
77
+ #
78
+ # describe Post do
79
+ # it do
80
+ # FactoryGirl.create(:post)
81
+ # should validate_uniqueness_of(:title)
82
+ # end
83
+ # end
84
+ #
85
+ # #### Qualifiers
86
+ #
87
+ # ##### with_message
88
+ #
89
+ # Use `with_message` if you are using a custom validation message.
90
+ #
91
+ # class Post < ActiveRecord::Base
92
+ # validates_uniqueness_of :title, message: 'Please choose another title'
93
+ # end
94
+ #
95
+ # # RSpec
96
+ # describe Post do
97
+ # it do
98
+ # should validate_uniqueness_of(:title).
99
+ # with_message('Please choose another title')
100
+ # end
101
+ # end
102
+ #
103
+ # # Test::Unit
104
+ # class PostTest < ActiveSupport::TestCase
105
+ # should validate_uniqueness_of(:title).
106
+ # with_message('Please choose another title')
107
+ # end
108
+ #
109
+ # ##### scoped_to
110
+ #
111
+ # Use `scoped_to` to test usage of the `:scope` option. This asserts that
112
+ # a new record fails validation if not only the primary attribute is not
113
+ # unique, but the scoped attributes are not unique either.
114
+ #
115
+ # class Post < ActiveRecord::Base
116
+ # validates_uniqueness_of :slug, scope: :user_id
117
+ # end
118
+ #
119
+ # # RSpec
120
+ # describe Post do
121
+ # it { should validate_uniqueness_of(:slug).scoped_to(:journal_id) }
122
+ # end
123
+ #
124
+ # # Test::Unit
125
+ # class PostTest < ActiveSupport::TestCase
126
+ # should validate_uniqueness_of(:slug).scoped_to(:journal_id)
127
+ # end
128
+ #
129
+ # ##### case_insensitive
130
+ #
131
+ # Use `case_insensitive` to test usage of the `:case_sensitive` option
132
+ # with a false value. This asserts that the uniquable attributes fail
133
+ # validation even if their values are a different case than corresponding
134
+ # attributes in the pre-existing record.
135
+ #
136
+ # class Post < ActiveRecord::Base
137
+ # validates_uniqueness_of :key, case_sensitive: false
138
+ # end
139
+ #
140
+ # # RSpec
141
+ # describe Post do
142
+ # it { should validate_uniqueness_of(:key).case_insensitive }
143
+ # end
144
+ #
145
+ # # Test::Unit
146
+ # class PostTest < ActiveSupport::TestCase
147
+ # should validate_uniqueness_of(:key).case_insensitive
148
+ # end
149
+ #
150
+ # ##### allow_nil
151
+ #
152
+ # Use `allow_nil` to assert that the attribute allows nil.
153
+ #
154
+ # class Post < ActiveRecord::Base
155
+ # validates_uniqueness_of :author_id, allow_nil: true
156
+ # end
157
+ #
158
+ # # RSpec
159
+ # describe Post do
160
+ # it { should validate_uniqueness_of(:author_id).allow_nil }
161
+ # end
162
+ #
163
+ # # Test::Unit
164
+ # class PostTest < ActiveSupport::TestCase
165
+ # should validate_uniqueness_of(:author_id).allow_nil
166
+ # end
167
+ #
168
+ # @return [ValidateUniquenessOfMatcher]
169
+ #
170
+ def validate_uniqueness_of(attr)
171
+ ValidateUniquenessOfMatcher.new(attr)
172
+ end
173
+
174
+ # @private
175
+ class ValidateUniquenessOfMatcher < ValidationMatcher
176
+ include Helpers
177
+
178
+ def initialize(attribute)
179
+ super(attribute)
180
+ @options = {}
181
+ end
182
+
183
+ def scoped_to(*scopes)
184
+ @options[:scopes] = [*scopes].flatten
185
+ self
186
+ end
187
+
188
+ def with_message(message)
189
+ @expected_message = message
190
+ self
191
+ end
192
+
193
+ def case_insensitive
194
+ @options[:case_insensitive] = true
195
+ self
196
+ end
197
+
198
+ def allow_nil
199
+ @options[:allow_nil] = true
200
+ self
201
+ end
202
+
203
+ def description
204
+ result = "require "
205
+ result << "case sensitive " unless @options[:case_insensitive]
206
+ result << "unique value for #{@attribute}"
207
+ result << " scoped to #{@options[:scopes].join(', ')}" if @options[:scopes].present?
208
+ result
209
+ end
210
+
211
+ def matches?(subject)
212
+ @subject = subject.class.new
213
+ @expected_message ||= :taken
214
+ set_scoped_attributes &&
215
+ validate_everything_except_duplicate_nils? &&
216
+ validate_after_scope_change? &&
217
+ allows_nil?
218
+ end
219
+
220
+ private
221
+
222
+ def allows_nil?
223
+ if @options[:allow_nil]
224
+ ensure_nil_record_in_database
225
+ allows_value_of(nil, @expected_message)
226
+ else
227
+ true
228
+ end
229
+ end
230
+
231
+ def existing_record
232
+ @existing_record ||= first_instance
233
+ end
234
+
235
+ def first_instance
236
+ @subject.class.first || create_record_in_database
237
+ end
238
+
239
+ def ensure_nil_record_in_database
240
+ unless existing_record_is_nil?
241
+ create_record_in_database(nil_value: true)
242
+ end
243
+ end
244
+
245
+ def existing_record_is_nil?
246
+ @existing_record.present? && existing_value.nil?
247
+ end
248
+
249
+ def create_record_in_database(options = {})
250
+ if options[:nil_value]
251
+ value = nil
252
+ else
253
+ value = 'a'
254
+ end
255
+
256
+ @subject.class.new.tap do |instance|
257
+ instance.__send__("#{@attribute}=", value)
258
+
259
+ other_non_nullable_columns.each do |non_nullable_column|
260
+ instance.__send__("#{non_nullable_column.name}=", correct_type_for_column(non_nullable_column))
261
+ end
262
+
263
+ if has_secure_password?
264
+ instance.password = 'password'
265
+ instance.password_confirmation = 'password'
266
+ end
267
+ instance.save(validate: false)
268
+ end
269
+ end
270
+
271
+ def has_secure_password?
272
+ @subject.class.ancestors.map(&:to_s).include?('ActiveModel::SecurePassword::InstanceMethodsOnActivation')
273
+ end
274
+
275
+ def set_scoped_attributes
276
+ if @options[:scopes].present?
277
+ @options[:scopes].all? do |scope|
278
+ setter = :"#{scope}="
279
+ if @subject.respond_to?(setter)
280
+ @subject.__send__(setter, existing_record.__send__(scope))
281
+ true
282
+ else
283
+ @failure_message = "#{class_name} doesn't seem to have a #{scope} attribute."
284
+ false
285
+ end
286
+ end
287
+ else
288
+ true
289
+ end
290
+ end
291
+
292
+ def validate_everything_except_duplicate_nils?
293
+ if @options[:allow_nil] && existing_value.nil?
294
+ create_record_without_nil
295
+ end
296
+
297
+ disallows_value_of(existing_value, @expected_message)
298
+ end
299
+
300
+ def create_record_without_nil
301
+ @existing_record = create_record_in_database
302
+ end
303
+
304
+ def validate_after_scope_change?
305
+ if @options[:scopes].blank?
306
+ true
307
+ else
308
+ all_records = @subject.class.all
309
+ @options[:scopes].all? do |scope|
310
+ previous_value = all_records.map(&scope).max
311
+
312
+ # Assume the scope is a foreign key if the field is nil
313
+ previous_value ||= correct_type_for_column(@subject.class.columns_hash[scope.to_s])
314
+
315
+ next_value =
316
+ if previous_value.respond_to?(:next)
317
+ previous_value.next
318
+ elsif previous_value.respond_to?(:to_datetime)
319
+ previous_value.to_datetime.next
320
+ else
321
+ previous_value.to_s.next
322
+ end
323
+
324
+ @subject.__send__("#{scope}=", next_value)
325
+
326
+ if allows_value_of(existing_value, @expected_message)
327
+ @subject.__send__("#{scope}=", previous_value)
328
+
329
+ @failure_message_when_negated <<
330
+ " (with different value of #{scope})"
331
+ true
332
+ else
333
+ @failure_message << " (with different value of #{scope})"
334
+ false
335
+ end
336
+ end
337
+ end
338
+ end
339
+
340
+ def correct_type_for_column(column)
341
+ if column.type == :string || column.type == :binary
342
+ '0'
343
+ elsif column.type == :datetime
344
+ DateTime.now
345
+ elsif column.type == :uuid
346
+ SecureRandom.uuid
347
+ else
348
+ 0
349
+ end
350
+ end
351
+
352
+ def class_name
353
+ @subject.class.name
354
+ end
355
+
356
+ def existing_value
357
+ value = existing_record.__send__(@attribute)
358
+ if @options[:case_insensitive] && value.respond_to?(:swapcase!)
359
+ value.swapcase!
360
+ end
361
+ value
362
+ end
363
+
364
+ def other_non_nullable_columns
365
+ @subject.class.columns.select do |column|
366
+ column.name != @attribute && !column.null && !column.primary
367
+ end
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end