shoulda-matchers 2.7.0 → 2.8.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +13 -3
  3. data/Appraisals +18 -0
  4. data/CONTRIBUTING.md +13 -29
  5. data/Gemfile +1 -4
  6. data/Gemfile.lock +2 -10
  7. data/NEWS.md +100 -14
  8. data/README.md +62 -58
  9. data/Rakefile +9 -16
  10. data/gemfiles/3.0.gemfile +11 -12
  11. data/gemfiles/3.0.gemfile.lock +15 -10
  12. data/gemfiles/3.1.gemfile +11 -12
  13. data/gemfiles/3.1.gemfile.lock +14 -10
  14. data/gemfiles/3.1_1.9.2.gemfile +12 -11
  15. data/gemfiles/3.1_1.9.2.gemfile.lock +14 -3
  16. data/gemfiles/3.2.gemfile +11 -12
  17. data/gemfiles/3.2.gemfile.lock +15 -10
  18. data/gemfiles/3.2_1.9.2.gemfile +12 -11
  19. data/gemfiles/3.2_1.9.2.gemfile.lock +14 -2
  20. data/gemfiles/4.0.0.gemfile +10 -12
  21. data/gemfiles/4.0.0.gemfile.lock +13 -10
  22. data/gemfiles/4.0.1.gemfile +10 -12
  23. data/gemfiles/4.0.1.gemfile.lock +13 -10
  24. data/gemfiles/4.1.gemfile +13 -15
  25. data/gemfiles/4.1.gemfile.lock +45 -50
  26. data/gemfiles/4.2.gemfile +36 -0
  27. data/gemfiles/4.2.gemfile.lock +245 -0
  28. data/lib/shoulda/matchers.rb +3 -1
  29. data/lib/shoulda/matchers/action_controller.rb +1 -1
  30. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +1 -1
  31. data/lib/shoulda/matchers/action_controller/route_params.rb +9 -4
  32. data/lib/shoulda/matchers/action_controller/{set_the_flash_matcher.rb → set_flash_matcher.rb} +34 -26
  33. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +125 -69
  34. data/lib/shoulda/matchers/active_model.rb +1 -2
  35. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +18 -5
  36. data/lib/shoulda/matchers/active_model/exception_message_finder.rb +2 -2
  37. data/lib/shoulda/matchers/active_model/helpers.rb +4 -4
  38. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +10 -3
  39. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +1 -1
  40. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +3 -1
  41. data/lib/shoulda/matchers/active_model/{ensure_length_of_matcher.rb → validate_length_of_matcher.rb} +30 -20
  42. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +21 -0
  43. data/lib/shoulda/matchers/active_model/validation_message_finder.rb +2 -2
  44. data/lib/shoulda/matchers/active_record.rb +2 -0
  45. data/lib/shoulda/matchers/active_record/association_matcher.rb +96 -2
  46. data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +1 -1
  47. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +3 -3
  48. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +22 -2
  49. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +30 -4
  50. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +19 -3
  51. data/lib/shoulda/matchers/active_record/uniqueness.rb +14 -0
  52. data/lib/shoulda/matchers/active_record/uniqueness/model.rb +45 -0
  53. data/lib/shoulda/matchers/active_record/uniqueness/namespace.rb +36 -0
  54. data/lib/shoulda/matchers/active_record/uniqueness/test_model_creator.rb +50 -0
  55. data/lib/shoulda/matchers/active_record/uniqueness/test_models.rb +24 -0
  56. data/lib/shoulda/matchers/{active_model → active_record}/validate_uniqueness_of_matcher.rb +76 -16
  57. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +117 -51
  58. data/lib/shoulda/matchers/independent/delegate_method_matcher/target_not_defined_error.rb +1 -1
  59. data/lib/shoulda/matchers/matcher_context.rb +35 -0
  60. data/lib/shoulda/matchers/rails_shim.rb +23 -0
  61. data/lib/shoulda/matchers/util.rb +28 -0
  62. data/lib/shoulda/matchers/version.rb +1 -1
  63. data/script/SUPPORTED_VERSIONS +1 -1
  64. data/spec/acceptance/active_model_integration_spec.rb +20 -0
  65. data/spec/acceptance/independent_matchers_spec.rb +64 -0
  66. data/spec/acceptance/rails_integration_spec.rb +142 -0
  67. data/spec/acceptance_spec_helper.rb +23 -0
  68. data/spec/support/acceptance/helpers.rb +29 -0
  69. data/spec/support/acceptance/helpers/active_model_helpers.rb +11 -0
  70. data/spec/support/acceptance/helpers/array_helpers.rb +13 -0
  71. data/spec/support/acceptance/helpers/base_helpers.rb +14 -0
  72. data/spec/support/acceptance/helpers/command_helpers.rb +51 -0
  73. data/spec/support/acceptance/helpers/file_helpers.rb +19 -0
  74. data/spec/support/acceptance/helpers/gem_helpers.rb +31 -0
  75. data/spec/support/acceptance/helpers/minitest_helpers.rb +19 -0
  76. data/spec/support/acceptance/helpers/pluralization_helpers.rb +13 -0
  77. data/spec/support/acceptance/helpers/rails_version_helpers.rb +11 -0
  78. data/spec/support/acceptance/helpers/rspec_helpers.rb +26 -0
  79. data/spec/support/acceptance/helpers/ruby_version_helpers.rb +9 -0
  80. data/spec/support/acceptance/helpers/step_helpers.rb +117 -0
  81. data/spec/support/acceptance/matchers/have_output.rb +31 -0
  82. data/spec/support/acceptance/matchers/indicate_number_of_tests_was_run_matcher.rb +55 -0
  83. data/spec/support/acceptance/matchers/indicate_that_tests_were_run_matcher.rb +103 -0
  84. data/spec/support/tests/bundle.rb +94 -0
  85. data/spec/support/tests/command_runner.rb +214 -0
  86. data/spec/support/tests/filesystem.rb +77 -0
  87. data/spec/support/tests/version.rb +45 -0
  88. data/spec/support/unit/capture.rb +34 -0
  89. data/spec/support/unit/helpers/active_model_helpers.rb +25 -0
  90. data/spec/support/unit/helpers/active_model_versions.rb +20 -0
  91. data/spec/support/unit/helpers/active_resource_builder.rb +27 -0
  92. data/spec/support/unit/helpers/allow_value_matcher_helpers.rb +15 -0
  93. data/spec/support/unit/helpers/class_builder.rb +72 -0
  94. data/spec/support/unit/helpers/confirmation_matcher_helpers.rb +17 -0
  95. data/spec/support/unit/helpers/controller_builder.rb +91 -0
  96. data/spec/support/unit/helpers/i18n_faker.rb +15 -0
  97. data/spec/support/unit/helpers/mailer_builder.rb +12 -0
  98. data/spec/support/unit/helpers/model_builder.rb +102 -0
  99. data/spec/support/unit/helpers/rails_versions.rb +28 -0
  100. data/spec/support/unit/i18n.rb +7 -0
  101. data/spec/support/unit/matchers/deprecate.rb +60 -0
  102. data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +50 -0
  103. data/spec/support/unit/matchers/fail_with_message_matcher.rb +50 -0
  104. data/spec/support/unit/matchers/print_warning_including.rb +59 -0
  105. data/spec/support/unit/rails_application.rb +110 -0
  106. data/spec/support/unit/record_builder_with_i18n_validation_message.rb +69 -0
  107. data/spec/support/unit/record_validating_confirmation_builder.rb +56 -0
  108. data/spec/support/unit/record_with_different_error_attribute_builder.rb +92 -0
  109. data/spec/support/{shared_examples → unit/shared_examples}/numerical_submatcher.rb +0 -2
  110. data/spec/support/{shared_examples → unit/shared_examples}/numerical_type_submatcher.rb +0 -2
  111. data/spec/{shoulda → unit/shoulda}/matchers/action_controller/callback_matcher_spec.rb +2 -2
  112. data/spec/{shoulda → unit/shoulda}/matchers/action_controller/filter_param_matcher_spec.rb +2 -2
  113. data/spec/{shoulda → unit/shoulda}/matchers/action_controller/redirect_to_matcher_spec.rb +3 -3
  114. data/spec/{shoulda → unit/shoulda}/matchers/action_controller/render_template_matcher_spec.rb +2 -4
  115. data/spec/{shoulda → unit/shoulda}/matchers/action_controller/render_with_layout_matcher_spec.rb +9 -6
  116. data/spec/{shoulda → unit/shoulda}/matchers/action_controller/rescue_from_matcher_spec.rb +2 -2
  117. data/spec/{shoulda → unit/shoulda}/matchers/action_controller/respond_with_matcher_spec.rb +2 -2
  118. data/spec/unit/shoulda/matchers/action_controller/route_matcher_spec.rb +126 -0
  119. data/spec/{shoulda → unit/shoulda}/matchers/action_controller/route_params_spec.rb +2 -2
  120. data/spec/unit/shoulda/matchers/action_controller/set_flash_matcher_spec.rb +167 -0
  121. data/spec/unit/shoulda/matchers/action_controller/set_session_matcher_spec.rb +294 -0
  122. data/spec/{shoulda → unit/shoulda}/matchers/action_controller/strong_parameters_matcher_spec.rb +19 -11
  123. data/spec/{shoulda → unit/shoulda}/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +2 -2
  124. data/spec/{shoulda → unit/shoulda}/matchers/active_model/allow_value_matcher_spec.rb +49 -21
  125. data/spec/{shoulda → unit/shoulda}/matchers/active_model/disallow_value_matcher_spec.rb +8 -4
  126. data/spec/{shoulda → unit/shoulda}/matchers/active_model/exception_message_finder_spec.rb +4 -4
  127. data/spec/{shoulda → unit/shoulda}/matchers/active_model/have_secure_password_matcher_spec.rb +2 -2
  128. data/spec/{shoulda → unit/shoulda}/matchers/active_model/helpers_spec.rb +7 -3
  129. data/spec/{shoulda → unit/shoulda}/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +1 -1
  130. data/spec/{shoulda → unit/shoulda}/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +1 -1
  131. data/spec/{shoulda → unit/shoulda}/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +1 -1
  132. data/spec/{shoulda → unit/shoulda}/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +1 -1
  133. data/spec/{shoulda → unit/shoulda}/matchers/active_model/validate_absence_of_matcher_spec.rb +3 -3
  134. data/spec/{shoulda → unit/shoulda}/matchers/active_model/validate_acceptance_of_matcher_spec.rb +2 -2
  135. data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +63 -0
  136. data/spec/{shoulda → unit/shoulda}/matchers/active_model/validate_exclusion_of_matcher_spec.rb +5 -4
  137. data/spec/{shoulda → unit/shoulda}/matchers/active_model/validate_inclusion_of_matcher_spec.rb +7 -14
  138. data/spec/{shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb → unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb} +43 -23
  139. data/spec/{shoulda → unit/shoulda}/matchers/active_model/validate_numericality_of_matcher_spec.rb +3 -4
  140. data/spec/{shoulda → unit/shoulda}/matchers/active_model/validate_presence_of_matcher_spec.rb +3 -3
  141. data/spec/{shoulda → unit/shoulda}/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +127 -2
  142. data/spec/{shoulda → unit/shoulda}/matchers/active_model/validation_message_finder_spec.rb +8 -6
  143. data/spec/{shoulda → unit/shoulda}/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +2 -2
  144. data/spec/{shoulda → unit/shoulda}/matchers/active_record/association_matcher_spec.rb +217 -26
  145. data/spec/{shoulda → unit/shoulda}/matchers/active_record/association_matchers/model_reflection_spec.rb +2 -2
  146. data/spec/{shoulda → unit/shoulda}/matchers/active_record/define_enum_for_matcher_spec.rb +2 -2
  147. data/spec/{shoulda → unit/shoulda}/matchers/active_record/have_db_column_matcher_spec.rb +2 -2
  148. data/spec/{shoulda → unit/shoulda}/matchers/active_record/have_db_index_matcher_spec.rb +8 -5
  149. data/spec/{shoulda → unit/shoulda}/matchers/active_record/have_readonly_attributes_matcher_spec.rb +2 -2
  150. data/spec/{shoulda → unit/shoulda}/matchers/active_record/serialize_matcher_spec.rb +3 -3
  151. data/spec/{shoulda → unit/shoulda}/matchers/doublespeak/double_collection_spec.rb +29 -7
  152. data/spec/{shoulda → unit/shoulda}/matchers/doublespeak/double_implementation_registry_spec.rb +1 -1
  153. data/spec/{shoulda → unit/shoulda}/matchers/doublespeak/double_spec.rb +20 -10
  154. data/spec/{shoulda → unit/shoulda}/matchers/doublespeak/object_double_spec.rb +1 -1
  155. data/spec/{shoulda → unit/shoulda}/matchers/doublespeak/proxy_implementation_spec.rb +13 -6
  156. data/spec/{shoulda → unit/shoulda}/matchers/doublespeak/stub_implementation_spec.rb +2 -2
  157. data/spec/unit/shoulda/matchers/doublespeak/world_spec.rb +77 -0
  158. data/spec/{shoulda → unit/shoulda}/matchers/doublespeak_spec.rb +11 -3
  159. data/spec/{shoulda → unit/shoulda}/matchers/independent/delegate_method_matcher/stubbed_target_spec.rb +1 -1
  160. data/spec/unit/shoulda/matchers/independent/delegate_method_matcher_spec.rb +517 -0
  161. data/spec/unit_spec_helper.rb +66 -0
  162. data/spec/warnings_spy/partitioner.rb +10 -3
  163. data/spec/warnings_spy/reader.rb +9 -20
  164. data/spec/warnings_spy/reporter.rb +2 -1
  165. metadata +212 -149
  166. data/features/activemodel_integration.feature +0 -15
  167. data/features/rails_integration.feature +0 -160
  168. data/features/step_definitions/activemodel_steps.rb +0 -21
  169. data/features/step_definitions/rails_steps.rb +0 -227
  170. data/features/support/env.rb +0 -6
  171. data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +0 -70
  172. data/spec/shoulda/matchers/action_controller/set_session_matcher_spec.rb +0 -113
  173. data/spec/shoulda/matchers/action_controller/set_the_flash_matcher_spec.rb +0 -153
  174. data/spec/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +0 -47
  175. data/spec/shoulda/matchers/doublespeak/world_spec.rb +0 -70
  176. data/spec/shoulda/matchers/independent/delegate_method_matcher_spec.rb +0 -309
  177. data/spec/spec_helper.rb +0 -42
  178. data/spec/support/active_model_versions.rb +0 -13
  179. data/spec/support/active_resource_builder.rb +0 -29
  180. data/spec/support/activemodel_helpers.rb +0 -23
  181. data/spec/support/capture_helpers.rb +0 -19
  182. data/spec/support/class_builder.rb +0 -46
  183. data/spec/support/controller_builder.rb +0 -102
  184. data/spec/support/fail_with_message_including_matcher.rb +0 -44
  185. data/spec/support/fail_with_message_matcher.rb +0 -44
  186. data/spec/support/i18n_faker.rb +0 -10
  187. data/spec/support/mailer_builder.rb +0 -10
  188. data/spec/support/model_builder.rb +0 -81
  189. data/spec/support/rails_versions.rb +0 -26
  190. data/spec/support/test_application.rb +0 -120
@@ -189,11 +189,11 @@ module Shoulda
189
189
  def correct_default?
190
190
  return true unless @options.key?(:default)
191
191
 
192
- if matched_column.default.to_s == @options[:default].to_s
192
+ if matched_column.type_cast_default.to_s == @options[:default].to_s
193
193
  true
194
194
  else
195
195
  @missing = "#{model_class} has a db column named #{@column} " <<
196
- "of default #{matched_column.default}, " <<
196
+ "of default #{matched_column.type_cast_default}, " <<
197
197
  "not #{@options[:default]}."
198
198
  false
199
199
  end
@@ -227,7 +227,7 @@ module Shoulda
227
227
  def correct_primary?
228
228
  return true unless @options.key?(:primary)
229
229
 
230
- if matched_column.primary == @options[:primary]
230
+ if matched_column.primary? == @options[:primary]
231
231
  true
232
232
  else
233
233
  @missing = "#{model_class} has a db column named #{@column} "
@@ -241,7 +241,10 @@ module Shoulda
241
241
  end
242
242
 
243
243
  def matched_column
244
- model_class.columns.detect { |each| each.name == @column.to_s }
244
+ @_matched_column ||= begin
245
+ column = model_class.columns.detect { |each| each.name == @column.to_s }
246
+ DecoratedColumn.new(model_class, column)
247
+ end
245
248
  end
246
249
 
247
250
  def model_class
@@ -252,9 +255,32 @@ module Shoulda
252
255
  matched_column.scale
253
256
  end
254
257
 
258
+ def actual_primary?
259
+ model_class.primary_key == matched_column.name
260
+ end
261
+
255
262
  def expectation
256
263
  "#{model_class.name} to #{description}"
257
264
  end
265
+
266
+ class DecoratedColumn < SimpleDelegator
267
+ def initialize(model, column)
268
+ @model = model
269
+ super(column)
270
+ end
271
+
272
+ def type_cast_default
273
+ Shoulda::Matchers::RailsShim.type_cast_default_for(model, self)
274
+ end
275
+
276
+ def primary?
277
+ model.primary_key == name
278
+ end
279
+
280
+ protected
281
+
282
+ attr_reader :model
283
+ end
258
284
  end
259
285
  end
260
286
  end
@@ -130,7 +130,7 @@ module Shoulda
130
130
  protected
131
131
 
132
132
  def serialization_valid?
133
- if model_class.serialized_attributes.keys.include?(@name)
133
+ if attribute_is_serialized?
134
134
  true
135
135
  else
136
136
  @missing = "no serialized attribute called :#{@name}"
@@ -140,7 +140,7 @@ module Shoulda
140
140
 
141
141
  def class_valid?
142
142
  if @options[:type]
143
- klass = model_class.serialized_attributes[@name]
143
+ klass = serialization_coder
144
144
  if klass == @options[:type]
145
145
  true
146
146
  else
@@ -162,7 +162,7 @@ module Shoulda
162
162
 
163
163
  def instance_class_valid?
164
164
  if @options.key?(:instance_type)
165
- if model_class.serialized_attributes[@name].class == @options[:instance_type]
165
+ if serialization_coder.is_a?(@options[:instance_type])
166
166
  true
167
167
  else
168
168
  @missing = ":#{@name} should be an instance of #{@options[:type]}"
@@ -183,6 +183,22 @@ module Shoulda
183
183
  expectation += " with an instance of #{@options[:instance_type]}" if @options[:instance_type]
184
184
  expectation
185
185
  end
186
+
187
+ def attribute_is_serialized?
188
+ serialized_attributes.include?(@name)
189
+ end
190
+
191
+ def serialization_coder
192
+ serialized_attributes[@name]
193
+ end
194
+
195
+ def serialized_attributes
196
+ Shoulda::Matchers::RailsShim.serialized_attributes_for(model)
197
+ end
198
+
199
+ def model
200
+ @subject.class
201
+ end
186
202
  end
187
203
  end
188
204
  end
@@ -0,0 +1,14 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # @private
5
+ module Uniqueness
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ require 'shoulda/matchers/active_record/uniqueness/model'
12
+ require 'shoulda/matchers/active_record/uniqueness/namespace'
13
+ require 'shoulda/matchers/active_record/uniqueness/test_model_creator'
14
+ require 'shoulda/matchers/active_record/uniqueness/test_models'
@@ -0,0 +1,45 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ module Uniqueness
5
+ # @private
6
+ class Model
7
+ def self.next_unique_copy_of(model_name, namespace)
8
+ model = new(model_name, namespace)
9
+
10
+ while model.already_exists?
11
+ model = model.next
12
+ end
13
+
14
+ model
15
+ end
16
+
17
+ def initialize(name, namespace)
18
+ @name = name
19
+ @namespace = namespace
20
+ end
21
+
22
+ def already_exists?
23
+ namespace.has?(name)
24
+ end
25
+
26
+ def next
27
+ Model.new(name.next, namespace)
28
+ end
29
+
30
+ def symlink_to(parent)
31
+ namespace.set(name, parent.dup)
32
+ end
33
+
34
+ def to_s
35
+ [namespace, name].join('::')
36
+ end
37
+
38
+ protected
39
+
40
+ attr_reader :name, :namespace
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ module Uniqueness
5
+ # @private
6
+ class Namespace
7
+ def initialize(constant)
8
+ @constant = constant
9
+ end
10
+
11
+ def has?(name)
12
+ constant.const_defined?(name)
13
+ end
14
+
15
+ def set(name, value)
16
+ constant.const_set(name, value)
17
+ end
18
+
19
+ def clear
20
+ constant.constants.each do |child_constant|
21
+ constant.__send__(:remove_const, child_constant)
22
+ end
23
+ end
24
+
25
+ def to_s
26
+ constant.to_s
27
+ end
28
+
29
+ protected
30
+
31
+ attr_reader :constant
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,50 @@
1
+ require 'thread'
2
+
3
+ module Shoulda
4
+ module Matchers
5
+ module ActiveRecord
6
+ module Uniqueness
7
+ # @private
8
+ class TestModelCreator
9
+ def self.create(model_name, namespace)
10
+ Mutex.new.synchronize do
11
+ new(model_name, namespace).create
12
+ end
13
+ end
14
+
15
+ def initialize(model_name, namespace)
16
+ @model_name = model_name
17
+ @namespace = namespace
18
+ end
19
+
20
+ def create
21
+ new_model.tap do |new_model|
22
+ new_model.symlink_to(existing_model)
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ attr_reader :model_name, :namespace
29
+
30
+ private
31
+
32
+ def model_name_without_namespace
33
+ model_name.demodulize
34
+ end
35
+
36
+ def new_model
37
+ @_new_model ||= Model.next_unique_copy_of(
38
+ model_name_without_namespace,
39
+ namespace
40
+ )
41
+ end
42
+
43
+ def existing_model
44
+ @_existing_model ||= model_name.constantize
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,24 @@
1
+ require 'thread'
2
+
3
+ module Shoulda
4
+ module Matchers
5
+ module ActiveRecord
6
+ module Uniqueness
7
+ # @private
8
+ module TestModels
9
+ def self.create(model_name)
10
+ TestModelCreator.create(model_name, root_namespace)
11
+ end
12
+
13
+ def self.remove_all
14
+ root_namespace.clear
15
+ end
16
+
17
+ def self.root_namespace
18
+ @_root_namespace ||= Namespace.new(self)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,6 +1,6 @@
1
1
  module Shoulda
2
2
  module Matchers
3
- module ActiveModel
3
+ module ActiveRecord
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
@@ -171,13 +171,33 @@ module Shoulda
171
171
  #
172
172
  # @return [ValidateUniquenessOfMatcher]
173
173
  #
174
+ # ##### allow_blank
175
+ #
176
+ # Use `allow_blank` to assert that the attribute allows a blank value.
177
+ #
178
+ # class Post < ActiveRecord::Base
179
+ # validates_uniqueness_of :author_id, allow_blank: true
180
+ # end
181
+ #
182
+ # # RSpec
183
+ # describe Post do
184
+ # it { should validate_uniqueness_of(:author_id).allow_blank }
185
+ # end
186
+ #
187
+ # # Test::Unit
188
+ # class PostTest < ActiveSupport::TestCase
189
+ # should validate_uniqueness_of(:author_id).allow_blank
190
+ # end
191
+ #
192
+ # @return [ValidateUniquenessOfMatcher]
193
+ #
174
194
  def validate_uniqueness_of(attr)
175
195
  ValidateUniquenessOfMatcher.new(attr)
176
196
  end
177
197
 
178
198
  # @private
179
- class ValidateUniquenessOfMatcher < ValidationMatcher
180
- include Helpers
199
+ class ValidateUniquenessOfMatcher < ActiveModel::ValidationMatcher
200
+ include ActiveModel::Helpers
181
201
 
182
202
  def initialize(attribute)
183
203
  super(attribute)
@@ -204,6 +224,11 @@ module Shoulda
204
224
  self
205
225
  end
206
226
 
227
+ def allow_blank
228
+ @options[:allow_blank] = true
229
+ self
230
+ end
231
+
207
232
  def description
208
233
  result = "require "
209
234
  result << "case sensitive " unless @options[:case_insensitive]
@@ -216,10 +241,14 @@ module Shoulda
216
241
  @original_subject = subject
217
242
  @subject = subject.class.new
218
243
  @expected_message ||= :taken
244
+
219
245
  set_scoped_attributes &&
220
- validate_everything_except_duplicate_nils? &&
246
+ validate_everything_except_duplicate_nils_or_blanks? &&
221
247
  validate_after_scope_change? &&
222
- allows_nil?
248
+ allows_nil? &&
249
+ allows_blank?
250
+ ensure
251
+ Uniqueness::TestModels.remove_all
223
252
  end
224
253
 
225
254
  private
@@ -233,6 +262,15 @@ module Shoulda
233
262
  end
234
263
  end
235
264
 
265
+ def allows_blank?
266
+ if @options[:allow_blank]
267
+ ensure_blank_record_in_database
268
+ allows_value_of('', @expected_message)
269
+ else
270
+ true
271
+ end
272
+ end
273
+
236
274
  def existing_record
237
275
  @existing_record ||= first_instance
238
276
  end
@@ -247,21 +285,26 @@ module Shoulda
247
285
  end
248
286
  end
249
287
 
288
+ def ensure_blank_record_in_database
289
+ unless existing_record_is_blank?
290
+ create_record_in_database(blank_value: true)
291
+ end
292
+ end
293
+
250
294
  def existing_record_is_nil?
251
295
  @existing_record.present? && existing_value.nil?
252
296
  end
253
297
 
254
- def create_record_in_database(options = {})
255
- if options[:nil_value]
256
- value = nil
257
- else
258
- value = 'a'
259
- end
298
+ def existing_record_is_blank?
299
+ @existing_record.present? && existing_value.strip == ''
300
+ end
260
301
 
302
+ def create_record_in_database(options = {})
261
303
  @original_subject.tap do |instance|
262
- instance.__send__("#{@attribute}=", value)
304
+ instance.__send__("#{@attribute}=", value_for_new_record(options))
263
305
  ensure_secure_password_set(instance)
264
306
  instance.save(validate: false)
307
+ @created_record = instance
265
308
  end
266
309
  end
267
310
 
@@ -272,6 +315,14 @@ module Shoulda
272
315
  end
273
316
  end
274
317
 
318
+ def value_for_new_record(options = {})
319
+ case
320
+ when options[:nil_value] then nil
321
+ when options[:blank_value] then ''
322
+ else 'a'
323
+ end
324
+ end
325
+
275
326
  def has_secure_password?
276
327
  @subject.class.ancestors.map(&:to_s).include?('ActiveModel::SecurePassword::InstanceMethodsOnActivation')
277
328
  end
@@ -293,18 +344,25 @@ module Shoulda
293
344
  end
294
345
  end
295
346
 
296
- def validate_everything_except_duplicate_nils?
297
- if @options[:allow_nil] && existing_value.nil?
298
- create_record_without_nil
347
+ def validate_everything_except_duplicate_nils_or_blanks?
348
+ if (@options[:allow_nil] && existing_value.nil?) ||
349
+ (@options[:allow_blank] && existing_value.blank?)
350
+ create_record_with_value
299
351
  end
300
352
 
301
353
  disallows_value_of(existing_value, @expected_message)
302
354
  end
303
355
 
304
- def create_record_without_nil
356
+ def create_record_with_value
305
357
  @existing_record = create_record_in_database
306
358
  end
307
359
 
360
+ def model_class?(model_name)
361
+ model_name.constantize.ancestors.include?(::ActiveRecord::Base)
362
+ rescue NameError
363
+ false
364
+ end
365
+
308
366
  def validate_after_scope_change?
309
367
  if @options[:scopes].blank?
310
368
  true
@@ -322,6 +380,8 @@ module Shoulda
322
380
  key == previous_value
323
381
  end
324
382
  available_values.keys.last
383
+ elsif scope.to_s =~ /_type$/ && model_class?(previous_value)
384
+ Uniqueness::TestModels.create(previous_value).to_s
325
385
  elsif previous_value.respond_to?(:next)
326
386
  previous_value.next
327
387
  elsif previous_value.respond_to?(:to_datetime)