mcmire-shoulda-matchers 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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