shoulda-matchers 3.1.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (288) hide show
  1. checksums.yaml +5 -5
  2. data/{MIT-LICENSE → LICENSE} +1 -1
  3. data/README.md +407 -232
  4. data/docs/errors/NonCaseSwappableValueError.md +2 -2
  5. data/lib/shoulda/matchers/action_controller/callback_matcher.rb +7 -80
  6. data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +4 -3
  7. data/lib/shoulda/matchers/action_controller/flash_store.rb +2 -4
  8. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +36 -30
  9. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +8 -10
  10. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +7 -9
  11. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +18 -15
  12. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +3 -2
  13. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +3 -3
  14. data/lib/shoulda/matchers/action_controller/route_matcher.rb +88 -29
  15. data/lib/shoulda/matchers/action_controller/route_params.rb +2 -2
  16. data/lib/shoulda/matchers/action_controller/set_flash_matcher.rb +4 -4
  17. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +3 -3
  18. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +19 -13
  19. data/lib/shoulda/matchers/action_controller.rb +2 -0
  20. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +1 -1
  21. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +5 -9
  22. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +2 -2
  23. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +1 -1
  24. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +1 -1
  25. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +42 -39
  26. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +1 -1
  27. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +52 -26
  28. data/lib/shoulda/matchers/active_model/helpers.rb +2 -2
  29. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +32 -30
  30. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +2 -1
  31. data/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
  32. data/lib/shoulda/matchers/active_model/qualifiers/allow_nil.rb +26 -0
  33. data/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +1 -1
  34. data/lib/shoulda/matchers/active_model/qualifiers.rb +2 -0
  35. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +30 -6
  36. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +8 -3
  37. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +31 -16
  38. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +52 -16
  39. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +137 -84
  40. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +159 -46
  41. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +130 -66
  42. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +251 -24
  43. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +12 -9
  44. data/lib/shoulda/matchers/active_model/validation_matcher.rb +38 -6
  45. data/lib/shoulda/matchers/active_model/validation_message_finder.rb +2 -4
  46. data/lib/shoulda/matchers/active_model/validator.rb +4 -9
  47. data/lib/shoulda/matchers/active_model.rb +3 -5
  48. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +10 -7
  49. data/lib/shoulda/matchers/active_record/association_matcher.rb +386 -111
  50. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +5 -2
  51. data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +4 -4
  52. data/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb +1 -1
  53. data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +11 -6
  54. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +14 -15
  55. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +30 -8
  56. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +34 -11
  57. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +69 -0
  58. data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +1 -1
  59. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +74 -0
  60. data/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +3 -2
  61. data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +7 -5
  62. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +458 -42
  63. data/lib/shoulda/matchers/active_record/have_attached_matcher.rb +185 -0
  64. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +63 -23
  65. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +164 -48
  66. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +106 -0
  67. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +13 -11
  68. data/lib/shoulda/matchers/active_record/have_rich_text_matcher.rb +83 -0
  69. data/lib/shoulda/matchers/active_record/have_secure_token_matcher.rb +132 -0
  70. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +18 -18
  71. data/lib/shoulda/matchers/active_record/uniqueness/test_model_creator.rb +1 -3
  72. data/lib/shoulda/matchers/active_record/uniqueness/test_models.rb +0 -2
  73. data/lib/shoulda/matchers/active_record/uniqueness.rb +1 -1
  74. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +430 -200
  75. data/lib/shoulda/matchers/active_record.rb +28 -20
  76. data/lib/shoulda/matchers/configuration.rb +12 -1
  77. data/lib/shoulda/matchers/doublespeak/double.rb +1 -1
  78. data/lib/shoulda/matchers/doublespeak/double_collection.rb +3 -3
  79. data/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +8 -5
  80. data/lib/shoulda/matchers/doublespeak/object_double.rb +6 -2
  81. data/lib/shoulda/matchers/doublespeak/stub_implementation.rb +1 -5
  82. data/lib/shoulda/matchers/doublespeak/world.rb +2 -2
  83. data/lib/shoulda/matchers/doublespeak.rb +2 -1
  84. data/lib/shoulda/matchers/error.rb +1 -1
  85. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +109 -29
  86. data/lib/shoulda/matchers/independent.rb +2 -2
  87. data/lib/shoulda/matchers/integrations/configuration.rb +8 -4
  88. data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +1 -1
  89. data/lib/shoulda/matchers/integrations/libraries/rails.rb +2 -2
  90. data/lib/shoulda/matchers/integrations/test_frameworks/active_support_test_case.rb +1 -1
  91. data/lib/shoulda/matchers/integrations/test_frameworks/minitest_4.rb +1 -1
  92. data/lib/shoulda/matchers/integrations/test_frameworks/minitest_5.rb +1 -1
  93. data/lib/shoulda/matchers/integrations/test_frameworks/missing_test_framework.rb +1 -1
  94. data/lib/shoulda/matchers/integrations/test_frameworks/test_unit.rb +1 -1
  95. data/lib/shoulda/matchers/rails_shim.rb +172 -51
  96. data/lib/shoulda/matchers/routing.rb +2 -2
  97. data/lib/shoulda/matchers/util/word_wrap.rb +17 -12
  98. data/lib/shoulda/matchers/util.rb +39 -5
  99. data/lib/shoulda/matchers/version.rb +1 -1
  100. data/lib/shoulda/matchers/warn.rb +4 -3
  101. data/shoulda-matchers.gemspec +33 -15
  102. metadata +31 -338
  103. data/.gitignore +0 -12
  104. data/.hound.yml +0 -3
  105. data/.hound_config/ruby.yml +0 -12
  106. data/.travis.yml +0 -19
  107. data/.yardopts +0 -10
  108. data/Appraisals +0 -73
  109. data/CONTRIBUTING.md +0 -101
  110. data/Gemfile +0 -15
  111. data/Gemfile.lock +0 -70
  112. data/NEWS.md +0 -986
  113. data/Rakefile +0 -39
  114. data/custom_plan.rb +0 -88
  115. data/doc_config/gh-pages/index.html.erb +0 -9
  116. data/doc_config/yard/setup.rb +0 -22
  117. data/doc_config/yard/templates/default/fulldoc/html/css/bootstrap.css +0 -5967
  118. data/doc_config/yard/templates/default/fulldoc/html/css/full_list.css +0 -12
  119. data/doc_config/yard/templates/default/fulldoc/html/css/global.css +0 -62
  120. data/doc_config/yard/templates/default/fulldoc/html/css/solarized.css +0 -69
  121. data/doc_config/yard/templates/default/fulldoc/html/css/style.css +0 -312
  122. data/doc_config/yard/templates/default/fulldoc/html/full_list.erb +0 -32
  123. data/doc_config/yard/templates/default/fulldoc/html/full_list_class.erb +0 -1
  124. data/doc_config/yard/templates/default/fulldoc/html/full_list_method.erb +0 -8
  125. data/doc_config/yard/templates/default/fulldoc/html/js/app.js +0 -298
  126. data/doc_config/yard/templates/default/fulldoc/html/js/full_list.js +0 -1
  127. data/doc_config/yard/templates/default/fulldoc/html/js/jquery.stickyheaders.js +0 -289
  128. data/doc_config/yard/templates/default/fulldoc/html/js/underscore.min.js +0 -6
  129. data/doc_config/yard/templates/default/fulldoc/html/setup.rb +0 -8
  130. data/doc_config/yard/templates/default/layout/html/breadcrumb.erb +0 -14
  131. data/doc_config/yard/templates/default/layout/html/fonts.erb +0 -1
  132. data/doc_config/yard/templates/default/layout/html/footer.erb +0 -6
  133. data/doc_config/yard/templates/default/layout/html/layout.erb +0 -23
  134. data/doc_config/yard/templates/default/layout/html/search.erb +0 -13
  135. data/doc_config/yard/templates/default/layout/html/setup.rb +0 -40
  136. data/doc_config/yard/templates/default/method_details/html/source.erb +0 -10
  137. data/doc_config/yard/templates/default/module/html/box_info.erb +0 -31
  138. data/gemfiles/4.0.0.gemfile +0 -38
  139. data/gemfiles/4.0.0.gemfile.lock +0 -223
  140. data/gemfiles/4.0.1.gemfile +0 -38
  141. data/gemfiles/4.0.1.gemfile.lock +0 -225
  142. data/gemfiles/4.1.gemfile +0 -38
  143. data/gemfiles/4.1.gemfile.lock +0 -220
  144. data/gemfiles/4.2.gemfile +0 -38
  145. data/gemfiles/4.2.gemfile.lock +0 -243
  146. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +0 -159
  147. data/lib/shoulda/matchers/independent/delegate_method_matcher/stubbed_target.rb +0 -37
  148. data/script/SUPPORTED_VERSIONS +0 -1
  149. data/script/install_gems_in_all_appraisals +0 -14
  150. data/script/run_all_tests +0 -14
  151. data/script/update_gem_in_all_appraisals +0 -15
  152. data/script/update_gems_in_all_appraisals +0 -14
  153. data/spec/acceptance/active_model_integration_spec.rb +0 -23
  154. data/spec/acceptance/independent_matchers_spec.rb +0 -125
  155. data/spec/acceptance/multiple_libraries_integration_spec.rb +0 -55
  156. data/spec/acceptance/rails_integration_spec.rb +0 -156
  157. data/spec/acceptance_spec_helper.rb +0 -23
  158. data/spec/doublespeak_spec_helper.rb +0 -2
  159. data/spec/report_warnings.rb +0 -7
  160. data/spec/spec_helper.rb +0 -21
  161. data/spec/support/acceptance/adds_shoulda_matchers_to_project.rb +0 -133
  162. data/spec/support/acceptance/helpers/active_model_helpers.rb +0 -11
  163. data/spec/support/acceptance/helpers/array_helpers.rb +0 -13
  164. data/spec/support/acceptance/helpers/base_helpers.rb +0 -19
  165. data/spec/support/acceptance/helpers/command_helpers.rb +0 -55
  166. data/spec/support/acceptance/helpers/file_helpers.rb +0 -19
  167. data/spec/support/acceptance/helpers/gem_helpers.rb +0 -31
  168. data/spec/support/acceptance/helpers/minitest_helpers.rb +0 -11
  169. data/spec/support/acceptance/helpers/n_unit_helpers.rb +0 -25
  170. data/spec/support/acceptance/helpers/pluralization_helpers.rb +0 -13
  171. data/spec/support/acceptance/helpers/rails_version_helpers.rb +0 -11
  172. data/spec/support/acceptance/helpers/rspec_helpers.rb +0 -24
  173. data/spec/support/acceptance/helpers/ruby_version_helpers.rb +0 -9
  174. data/spec/support/acceptance/helpers/step_helpers.rb +0 -127
  175. data/spec/support/acceptance/helpers.rb +0 -31
  176. data/spec/support/acceptance/matchers/have_output.rb +0 -31
  177. data/spec/support/acceptance/matchers/indicate_number_of_tests_was_run_matcher.rb +0 -55
  178. data/spec/support/acceptance/matchers/indicate_that_tests_were_run_matcher.rb +0 -103
  179. data/spec/support/tests/bundle.rb +0 -94
  180. data/spec/support/tests/command_runner.rb +0 -230
  181. data/spec/support/tests/current_bundle.rb +0 -61
  182. data/spec/support/tests/database.rb +0 -28
  183. data/spec/support/tests/database_adapters/postgresql.rb +0 -25
  184. data/spec/support/tests/database_adapters/sqlite3.rb +0 -26
  185. data/spec/support/tests/database_configuration.rb +0 -33
  186. data/spec/support/tests/database_configuration_registry.rb +0 -28
  187. data/spec/support/tests/filesystem.rb +0 -100
  188. data/spec/support/tests/version.rb +0 -45
  189. data/spec/support/unit/active_record/create_table.rb +0 -54
  190. data/spec/support/unit/attribute.rb +0 -47
  191. data/spec/support/unit/capture.rb +0 -40
  192. data/spec/support/unit/change_value.rb +0 -111
  193. data/spec/support/unit/create_model_arguments/basic.rb +0 -135
  194. data/spec/support/unit/create_model_arguments/has_many.rb +0 -15
  195. data/spec/support/unit/create_model_arguments/uniqueness_matcher.rb +0 -74
  196. data/spec/support/unit/helpers/active_model_helpers.rb +0 -27
  197. data/spec/support/unit/helpers/active_model_versions.rb +0 -28
  198. data/spec/support/unit/helpers/active_record_versions.rb +0 -24
  199. data/spec/support/unit/helpers/active_resource_builder.rb +0 -27
  200. data/spec/support/unit/helpers/allow_value_matcher_helpers.rb +0 -15
  201. data/spec/support/unit/helpers/class_builder.rb +0 -90
  202. data/spec/support/unit/helpers/column_type_helpers.rb +0 -26
  203. data/spec/support/unit/helpers/confirmation_matcher_helpers.rb +0 -17
  204. data/spec/support/unit/helpers/controller_builder.rb +0 -63
  205. data/spec/support/unit/helpers/database_helpers.rb +0 -20
  206. data/spec/support/unit/helpers/i18n_faker.rb +0 -15
  207. data/spec/support/unit/helpers/mailer_builder.rb +0 -12
  208. data/spec/support/unit/helpers/model_builder.rb +0 -114
  209. data/spec/support/unit/helpers/rails_versions.rb +0 -28
  210. data/spec/support/unit/helpers/validation_matcher_scenario_helpers.rb +0 -44
  211. data/spec/support/unit/i18n.rb +0 -7
  212. data/spec/support/unit/load_environment.rb +0 -12
  213. data/spec/support/unit/matchers/deprecate.rb +0 -60
  214. data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +0 -51
  215. data/spec/support/unit/matchers/fail_with_message_matcher.rb +0 -62
  216. data/spec/support/unit/matchers/print_warning_including.rb +0 -59
  217. data/spec/support/unit/model_creation_strategies/active_model.rb +0 -111
  218. data/spec/support/unit/model_creation_strategies/active_record.rb +0 -77
  219. data/spec/support/unit/model_creators/active_model.rb +0 -39
  220. data/spec/support/unit/model_creators/active_record/has_and_belongs_to_many.rb +0 -95
  221. data/spec/support/unit/model_creators/active_record/has_many.rb +0 -67
  222. data/spec/support/unit/model_creators/active_record/uniqueness_matcher.rb +0 -42
  223. data/spec/support/unit/model_creators/active_record.rb +0 -43
  224. data/spec/support/unit/model_creators/basic.rb +0 -97
  225. data/spec/support/unit/model_creators.rb +0 -19
  226. data/spec/support/unit/rails_application.rb +0 -126
  227. data/spec/support/unit/record_builder_with_i18n_validation_message.rb +0 -69
  228. data/spec/support/unit/record_validating_confirmation_builder.rb +0 -51
  229. data/spec/support/unit/record_with_different_error_attribute_builder.rb +0 -92
  230. data/spec/support/unit/shared_examples/ignoring_interference_by_writer.rb +0 -79
  231. data/spec/support/unit/shared_examples/numerical_submatcher.rb +0 -17
  232. data/spec/support/unit/shared_examples/set_session_or_flash.rb +0 -360
  233. data/spec/support/unit/validation_matcher_scenario.rb +0 -62
  234. data/spec/unit/shoulda/matchers/action_controller/callback_matcher_spec.rb +0 -82
  235. data/spec/unit/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +0 -28
  236. data/spec/unit/shoulda/matchers/action_controller/permit_matcher_spec.rb +0 -592
  237. data/spec/unit/shoulda/matchers/action_controller/redirect_to_matcher_spec.rb +0 -42
  238. data/spec/unit/shoulda/matchers/action_controller/render_template_matcher_spec.rb +0 -76
  239. data/spec/unit/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +0 -62
  240. data/spec/unit/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +0 -90
  241. data/spec/unit/shoulda/matchers/action_controller/respond_with_matcher_spec.rb +0 -31
  242. data/spec/unit/shoulda/matchers/action_controller/route_matcher_spec.rb +0 -330
  243. data/spec/unit/shoulda/matchers/action_controller/route_params_spec.rb +0 -30
  244. data/spec/unit/shoulda/matchers/action_controller/set_flash_matcher_spec.rb +0 -67
  245. data/spec/unit/shoulda/matchers/action_controller/set_session_matcher_spec.rb +0 -17
  246. data/spec/unit/shoulda/matchers/action_controller/set_session_or_flash_matcher_spec.rb +0 -562
  247. data/spec/unit/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +0 -115
  248. data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +0 -823
  249. data/spec/unit/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +0 -86
  250. data/spec/unit/shoulda/matchers/active_model/have_secure_password_matcher_spec.rb +0 -20
  251. data/spec/unit/shoulda/matchers/active_model/helpers_spec.rb +0 -162
  252. data/spec/unit/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +0 -266
  253. data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +0 -91
  254. data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +0 -149
  255. data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +0 -207
  256. data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +0 -1015
  257. data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +0 -288
  258. data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +0 -1837
  259. data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +0 -380
  260. data/spec/unit/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +0 -107
  261. data/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +0 -1242
  262. data/spec/unit/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +0 -251
  263. data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +0 -168
  264. data/spec/unit/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +0 -111
  265. data/spec/unit/shoulda/matchers/active_record/have_db_index_matcher_spec.rb +0 -85
  266. data/spec/unit/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +0 -41
  267. data/spec/unit/shoulda/matchers/active_record/serialize_matcher_spec.rb +0 -86
  268. data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +0 -1418
  269. data/spec/unit/shoulda/matchers/doublespeak/double_collection_spec.rb +0 -190
  270. data/spec/unit/shoulda/matchers/doublespeak/double_implementation_registry_spec.rb +0 -21
  271. data/spec/unit/shoulda/matchers/doublespeak/double_spec.rb +0 -271
  272. data/spec/unit/shoulda/matchers/doublespeak/object_double_spec.rb +0 -77
  273. data/spec/unit/shoulda/matchers/doublespeak/proxy_implementation_spec.rb +0 -72
  274. data/spec/unit/shoulda/matchers/doublespeak/stub_implementation_spec.rb +0 -101
  275. data/spec/unit/shoulda/matchers/doublespeak/world_spec.rb +0 -80
  276. data/spec/unit/shoulda/matchers/doublespeak_spec.rb +0 -27
  277. data/spec/unit/shoulda/matchers/independent/delegate_method_matcher/stubbed_target_spec.rb +0 -43
  278. data/spec/unit/shoulda/matchers/independent/delegate_method_matcher_spec.rb +0 -517
  279. data/spec/unit/shoulda/matchers/routing/route_matcher_spec.rb +0 -242
  280. data/spec/unit/shoulda/matchers/util/word_wrap_spec.rb +0 -252
  281. data/spec/unit_spec_helper.rb +0 -46
  282. data/spec/warnings_spy/filesystem.rb +0 -45
  283. data/spec/warnings_spy/partitioner.rb +0 -36
  284. data/spec/warnings_spy/reader.rb +0 -53
  285. data/spec/warnings_spy/reporter.rb +0 -88
  286. data/spec/warnings_spy.rb +0 -64
  287. data/tasks/documentation.rb +0 -199
  288. data/zeus.json +0 -11
@@ -10,11 +10,11 @@ module Shoulda
10
10
  # pre-existing record (thereby failing the uniqueness check).
11
11
  #
12
12
  # class Post < ActiveRecord::Base
13
- # validates_uniqueness_of :permalink
13
+ # validates :permalink, uniqueness: true
14
14
  # end
15
15
  #
16
16
  # # RSpec
17
- # describe Post do
17
+ # RSpec.describe Post, type: :model do
18
18
  # it { should validate_uniqueness_of(:permalink) }
19
19
  # end
20
20
  #
@@ -49,39 +49,50 @@ module Shoulda
49
49
  #
50
50
  # You may be tempted to test the model like this:
51
51
  #
52
- # describe Post do
52
+ # RSpec.describe Post, type: :model do
53
53
  # it { should validate_uniqueness_of(:title) }
54
54
  # end
55
55
  #
56
- # However, running this test will fail with something like:
56
+ # However, running this test will fail with an exception such as:
57
57
  #
58
- # Failures:
58
+ # Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher::ExistingRecordInvalid:
59
+ # validate_uniqueness_of works by matching a new record against an
60
+ # existing record. If there is no existing record, it will create one
61
+ # using the record you provide.
59
62
  #
60
- # 1) Post should validate :title to be case-sensitively unique
61
- # Failure/Error: it { should validate_uniqueness_of(:title) }
62
- # ActiveRecord::StatementInvalid:
63
- # SQLite3::ConstraintException: posts.content may not be NULL: INSERT INTO "posts" ("title") VALUES (?)
63
+ # While doing this, the following error was raised:
64
+ #
65
+ # PG::NotNullViolation: ERROR: null value in column "content" violates not-null constraint
66
+ # DETAIL: Failing row contains (1, null, null).
67
+ # : INSERT INTO "posts" DEFAULT VALUES RETURNING "id"
68
+ #
69
+ # The best way to fix this is to provide the matcher with a record where
70
+ # any required attributes are filled in with valid values beforehand.
71
+ #
72
+ # (The exact error message will differ depending on which database you're
73
+ # using, but you get the idea.)
64
74
  #
65
75
  # This happens because `validate_uniqueness_of` tries to create a new post
66
76
  # but cannot do so because of the `content` attribute: though unrelated to
67
- # this test, it nevertheless needs to be filled in. The solution is to
68
- # build a custom Post object ahead of time with `content` filled in:
77
+ # this test, it nevertheless needs to be filled in. As indicated at the
78
+ # end of the error message, the solution is to build a custom Post object
79
+ # ahead of time with `content` filled in:
69
80
  #
70
- # describe Post do
81
+ # RSpec.describe Post, type: :model do
71
82
  # describe "validations" do
72
- # subject { Post.new(content: 'Here is the content') }
83
+ # subject { Post.new(content: "Here is the content") }
73
84
  # it { should validate_uniqueness_of(:title) }
74
85
  # end
75
86
  # end
76
87
  #
77
88
  # Or, if you're using
78
- # [FactoryGirl](http://github.com/thoughtbot/factory_girl) and you have a
89
+ # [FactoryBot](https://github.com/thoughtbot/factory_bot) and you have a
79
90
  # `post` factory defined which automatically fills in `content`, you can
80
91
  # say:
81
92
  #
82
- # describe Post do
93
+ # RSpec.describe Post, type: :model do
83
94
  # describe "validations" do
84
- # subject { FactoryGirl.build(:post) }
95
+ # subject { FactoryBot.build(:post) }
85
96
  # it { should validate_uniqueness_of(:title) }
86
97
  # end
87
98
  # end
@@ -91,11 +102,11 @@ module Shoulda
91
102
  # Use `on` if your validation applies only under a certain context.
92
103
  #
93
104
  # class Post < ActiveRecord::Base
94
- # validates_uniqueness_of :title, on: :create
105
+ # validates :title, uniqueness: true, on: :create
95
106
  # end
96
107
  #
97
108
  # # RSpec
98
- # describe Post do
109
+ # RSpec.describe Post, type: :model do
99
110
  # it { should validate_uniqueness_of(:title).on(:create) }
100
111
  # end
101
112
  #
@@ -109,11 +120,11 @@ module Shoulda
109
120
  # Use `with_message` if you are using a custom validation message.
110
121
  #
111
122
  # class Post < ActiveRecord::Base
112
- # validates_uniqueness_of :title, message: 'Please choose another title'
123
+ # validates :title, uniqueness: true, message: 'Please choose another title'
113
124
  # end
114
125
  #
115
126
  # # RSpec
116
- # describe Post do
127
+ # RSpec.describe Post, type: :model do
117
128
  # it do
118
129
  # should validate_uniqueness_of(:title).
119
130
  # with_message('Please choose another title')
@@ -133,11 +144,11 @@ module Shoulda
133
144
  # unique, but the scoped attributes are not unique either.
134
145
  #
135
146
  # class Post < ActiveRecord::Base
136
- # validates_uniqueness_of :slug, scope: :journal_id
147
+ # validates :slug, uniqueness: { scope: :journal_id }
137
148
  # end
138
149
  #
139
150
  # # RSpec
140
- # describe Post do
151
+ # RSpec.describe Post, type: :model do
141
152
  # it { should validate_uniqueness_of(:slug).scoped_to(:journal_id) }
142
153
  # end
143
154
  #
@@ -146,6 +157,12 @@ module Shoulda
146
157
  # should validate_uniqueness_of(:slug).scoped_to(:journal_id)
147
158
  # end
148
159
  #
160
+ # NOTE: Support for testing uniqueness validation scoped to an array of
161
+ # associations is not available.
162
+ #
163
+ # For more information, please refer to
164
+ # https://github.com/thoughtbot/shoulda-matchers/issues/814
165
+ #
149
166
  # ##### case_insensitive
150
167
  #
151
168
  # Use `case_insensitive` to test usage of the `:case_sensitive` option
@@ -154,11 +171,11 @@ module Shoulda
154
171
  # attributes in the pre-existing record.
155
172
  #
156
173
  # class Post < ActiveRecord::Base
157
- # validates_uniqueness_of :key, case_sensitive: false
174
+ # validates :key, uniqueness: { case_sensitive: false }
158
175
  # end
159
176
  #
160
177
  # # RSpec
161
- # describe Post do
178
+ # RSpec.describe Post, type: :model do
162
179
  # it { should validate_uniqueness_of(:key).case_insensitive }
163
180
  # end
164
181
  #
@@ -182,7 +199,7 @@ module Shoulda
182
199
  # attribute.
183
200
  #
184
201
  # class User < ActiveRecord::Base
185
- # validates_uniqueness_of :email
202
+ # validates :email, uniqueness: true
186
203
  #
187
204
  # def email=(value)
188
205
  # super(value.downcase)
@@ -190,7 +207,7 @@ module Shoulda
190
207
  # end
191
208
  #
192
209
  # # RSpec
193
- # describe Post do
210
+ # RSpec.describe Post, type: :model do
194
211
  # it do
195
212
  # should validate_uniqueness_of(:email).ignoring_case_sensitivity
196
213
  # end
@@ -206,11 +223,11 @@ module Shoulda
206
223
  # Use `allow_nil` to assert that the attribute allows nil.
207
224
  #
208
225
  # class Post < ActiveRecord::Base
209
- # validates_uniqueness_of :author_id, allow_nil: true
226
+ # validates :author_id, uniqueness: true, allow_nil: true
210
227
  # end
211
228
  #
212
229
  # # RSpec
213
- # describe Post do
230
+ # RSpec.describe Post, type: :model do
214
231
  # it { should validate_uniqueness_of(:author_id).allow_nil }
215
232
  # end
216
233
  #
@@ -226,11 +243,11 @@ module Shoulda
226
243
  # Use `allow_blank` to assert that the attribute allows a blank value.
227
244
  #
228
245
  # class Post < ActiveRecord::Base
229
- # validates_uniqueness_of :author_id, allow_blank: true
246
+ # validates :author_id, uniqueness: true, allow_blank: true
230
247
  # end
231
248
  #
232
249
  # # RSpec
233
- # describe Post do
250
+ # RSpec.describe Post, type: :model do
234
251
  # it { should validate_uniqueness_of(:author_id).allow_blank }
235
252
  # end
236
253
  #
@@ -253,19 +270,19 @@ module Shoulda
253
270
  super(attribute)
254
271
  @expected_message = :taken
255
272
  @options = {
256
- case_sensitivity_strategy: :sensitive
273
+ case_sensitivity_strategy: :sensitive,
257
274
  }
258
275
  @existing_record_created = false
259
276
  @failure_reason = nil
260
277
  @failure_reason_when_negated = nil
261
278
  @attribute_setters = {
262
- existing_record: [],
263
- new_record: []
279
+ existing_record: AttributeSetters.new,
280
+ new_record: AttributeSetters.new,
264
281
  }
265
282
  end
266
283
 
267
284
  def scoped_to(*scopes)
268
- @options[:scopes] = [*scopes].flatten
285
+ @options[:scopes] = [*scopes].flatten.map(&:to_sym)
269
286
  self
270
287
  end
271
288
 
@@ -285,7 +302,7 @@ module Shoulda
285
302
  end
286
303
 
287
304
  def expects_to_allow_nil?
288
- @options[:allow_nil]
305
+ @options[:allow_nil] == true
289
306
  end
290
307
 
291
308
  def allow_blank
@@ -294,7 +311,7 @@ module Shoulda
294
311
  end
295
312
 
296
313
  def expects_to_allow_blank?
297
- @options[:allow_blank]
314
+ @options[:allow_blank] == true
298
315
  end
299
316
 
300
317
  def simple_description
@@ -313,15 +330,29 @@ module Shoulda
313
330
  @given_record = given_record
314
331
  @all_records = model.all
315
332
 
316
- existing_record_valid? &&
317
- validate_attribute_present? &&
318
- validate_scopes_present? &&
319
- scopes_match? &&
320
- validate_two_records_with_same_non_blank_value_cannot_coexist? &&
321
- validate_case_sensitivity? &&
322
- validate_after_scope_change? &&
323
- allows_nil? &&
324
- allows_blank?
333
+ matches_presence_of_attribute? &&
334
+ matches_presence_of_scopes? &&
335
+ matches_scopes_configuration? &&
336
+ matches_uniqueness_without_scopes? &&
337
+ matches_uniqueness_with_case_sensitivity_strategy? &&
338
+ matches_uniqueness_with_scopes? &&
339
+ matches_allow_nil? &&
340
+ matches_allow_blank?
341
+ ensure
342
+ Uniqueness::TestModels.remove_all
343
+ end
344
+
345
+ def does_not_match?(given_record)
346
+ @given_record = given_record
347
+ @all_records = model.all
348
+
349
+ does_not_match_presence_of_scopes? ||
350
+ does_not_match_scopes_configuration? ||
351
+ does_not_match_uniqueness_without_scopes? ||
352
+ does_not_match_uniqueness_with_case_sensitivity_strategy? ||
353
+ does_not_match_uniqueness_with_scopes? ||
354
+ does_not_match_allow_nil? ||
355
+ does_not_match_allow_blank?
325
356
  ensure
326
357
  Uniqueness::TestModels.remove_all
327
358
  end
@@ -370,83 +401,121 @@ module Shoulda
370
401
  end
371
402
  end
372
403
 
373
- def validation
374
- model._validators[@attribute].detect do |validator|
404
+ def validations
405
+ model.validators_on(@attribute).select do |validator|
375
406
  validator.is_a?(::ActiveRecord::Validations::UniquenessValidator)
376
407
  end
377
408
  end
378
409
 
379
- def scopes_match?
380
- if expected_scopes == actual_scopes
410
+ def matches_scopes_configuration?
411
+ if scopes_match?
381
412
  true
382
413
  else
383
- @failure_reason = 'Expected the validation'
414
+ @failure_reason = 'Expected the validation '
384
415
 
385
- if expected_scopes.empty?
386
- @failure_reason << ' not to be scoped to anything'
416
+ @failure_reason <<
417
+ if expected_scopes.empty?
418
+ 'not to be scoped to anything, '
419
+ else
420
+ "to be scoped to #{inspected_expected_scopes}, "
421
+ end
422
+
423
+ if actual_sets_of_scopes.any?
424
+ @failure_reason << 'but it was scoped to '
425
+ @failure_reason << "#{inspected_actual_scopes} instead."
387
426
  else
388
- @failure_reason << " to be scoped to #{inspected_expected_scopes}"
427
+ @failure_reason << 'but it was not scoped to anything.'
389
428
  end
390
429
 
391
- if actual_scopes.empty?
392
- @failure_reason << ', but it was not scoped to anything.'
393
- else
394
- @failure_reason << ', but it was scoped to '
430
+ false
431
+ end
432
+ end
433
+
434
+ def does_not_match_scopes_configuration?
435
+ if scopes_match?
436
+ @failure_reason = 'Expected the validation '
437
+
438
+ if expected_scopes.empty?
439
+ @failure_reason << 'to be scoped to nothing, '
440
+ @failure_reason << 'but it was scoped to '
395
441
  @failure_reason << "#{inspected_actual_scopes} instead."
442
+ else
443
+ @failure_reason << 'not to be scoped to '
444
+ @failure_reason << inspected_expected_scopes
396
445
  end
397
446
 
398
447
  false
448
+ else
449
+ true
399
450
  end
400
451
  end
401
452
 
402
- def expected_scopes
403
- Array.wrap(@options[:scopes])
453
+ def scopes_match?
454
+ actual_sets_of_scopes.empty? && expected_scopes.empty? ||
455
+ actual_sets_of_scopes.any? { |scopes| scopes == expected_scopes }
404
456
  end
405
457
 
406
458
  def inspected_expected_scopes
407
459
  expected_scopes.map(&:inspect).to_sentence
408
460
  end
409
461
 
410
- def actual_scopes
411
- if validation
412
- Array.wrap(validation.options[:scope])
462
+ def inspected_actual_scopes
463
+ inspected_actual_sets_of_scopes.to_sentence(
464
+ words_connector: ' and ',
465
+ last_word_connector: ', and',
466
+ )
467
+ end
468
+
469
+ def inspected_actual_sets_of_scopes
470
+ inspected_sets_of_scopes = actual_sets_of_scopes.map do |scopes|
471
+ scopes.map(&:inspect)
472
+ end
473
+
474
+ if inspected_sets_of_scopes.many?
475
+ inspected_sets_of_scopes.map { |x| "(#{x.to_sentence})" }
413
476
  else
414
- []
477
+ inspected_sets_of_scopes.map(&:to_sentence)
415
478
  end
416
479
  end
417
480
 
418
- def inspected_actual_scopes
419
- actual_scopes.map(&:inspect).to_sentence
481
+ def expected_scopes
482
+ Array.wrap(@options[:scopes])
420
483
  end
421
484
 
422
- def allows_nil?
423
- if expects_to_allow_nil?
424
- update_existing_record!(nil)
485
+ def actual_sets_of_scopes
486
+ validations.map do |validation|
487
+ Array.wrap(validation.options[:scope]).map(&:to_sym)
488
+ end.reject(&:empty?)
489
+ end
490
+
491
+ def matches_allow_nil?
492
+ !expects_to_allow_nil? || (
493
+ update_existing_record!(nil) &&
425
494
  allows_value_of(nil, @expected_message)
426
- else
427
- true
428
- end
495
+ )
496
+ end
497
+
498
+ def does_not_match_allow_nil?
499
+ expects_to_allow_nil? && (
500
+ update_existing_record!(nil) &&
501
+ (@failure_reason = nil ||
502
+ disallows_value_of(nil, @expected_message)
503
+ )
504
+ )
429
505
  end
430
506
 
431
- def allows_blank?
432
- if expects_to_allow_blank?
433
- update_existing_record!('')
507
+ def matches_allow_blank?
508
+ !expects_to_allow_blank? || (
509
+ update_existing_record!('') &&
434
510
  allows_value_of('', @expected_message)
435
- else
436
- true
437
- end
511
+ )
438
512
  end
439
513
 
440
- def existing_record_valid?
441
- if existing_record.valid?
442
- true
443
- else
444
- @failure_reason =
445
- "The record you provided could not be created, " +
446
- "as it failed with the following validation errors:\n\n" +
447
- format_validation_errors(existing_record.errors)
448
- false
449
- end
514
+ def does_not_match_allow_blank?
515
+ expects_to_allow_blank? && (
516
+ update_existing_record!('') &&
517
+ (@failure_reason = nil || disallows_value_of('', @expected_message))
518
+ )
450
519
  end
451
520
 
452
521
  def existing_record
@@ -467,46 +536,34 @@ module Shoulda
467
536
  end
468
537
 
469
538
  def find_existing_record
470
- record = model.first
471
-
472
- if record.present?
473
- record
474
- else
475
- nil
476
- end
539
+ model.first.presence
477
540
  end
478
541
 
479
542
  def create_existing_record
480
543
  @given_record.tap do |existing_record|
481
- ensure_secure_password_set(existing_record)
482
- existing_record.save
544
+ existing_record.save(validate: false)
483
545
  end
546
+ rescue ::ActiveRecord::StatementInvalid => e
547
+ raise ExistingRecordInvalid.create(underlying_exception: e)
484
548
  end
485
549
 
486
550
  def update_existing_record!(value)
487
551
  if existing_value_read != value
488
- set_attribute_on!(
489
- :existing_record,
490
- existing_record,
491
- @attribute,
492
- value
493
- )
494
- existing_record.save!
552
+ set_attribute_on_existing_record!(@attribute, value)
553
+ # It would be nice if we could ensure that the record was valid,
554
+ # but that would break users' existing tests
555
+ existing_record.save(validate: false)
495
556
  end
496
- end
497
557
 
498
- def ensure_secure_password_set(instance)
499
- if has_secure_password?
500
- instance.password = "password"
501
- instance.password_confirmation = "password"
502
- end
558
+ true
503
559
  end
504
560
 
505
561
  def arbitrary_non_blank_value
562
+ non_blank_value = dummy_value_for(@attribute)
506
563
  limit = column_limit_for(@attribute)
507
- non_blank_value = 'an arbitrary value'
508
564
 
509
- if limit && limit < non_blank_value.length
565
+ is_string_value = non_blank_value.is_a?(String)
566
+ if is_string_value && limit && limit < non_blank_value.length
510
567
  'x' * limit
511
568
  else
512
569
  non_blank_value
@@ -514,9 +571,7 @@ module Shoulda
514
571
  end
515
572
 
516
573
  def has_secure_password?
517
- model.ancestors.map(&:to_s).include?(
518
- 'ActiveModel::SecurePassword::InstanceMethodsOnActivation'
519
- )
574
+ Shoulda::Matchers::RailsShim.has_secure_password?(subject, @attribute)
520
575
  end
521
576
 
522
577
  def build_new_record
@@ -525,15 +580,15 @@ module Shoulda
525
580
  attribute_names_under_test.each do |attribute_name|
526
581
  set_attribute_on_new_record!(
527
582
  attribute_name,
528
- existing_record.public_send(attribute_name)
583
+ existing_record.public_send(attribute_name),
529
584
  )
530
585
  end
531
586
 
532
587
  @new_record
533
588
  end
534
589
 
535
- def validate_attribute_present?
536
- if model.method_defined?("#{attribute}=")
590
+ def matches_presence_of_attribute?
591
+ if attribute_present_on_model?
537
592
  true
538
593
  else
539
594
  @failure_reason =
@@ -542,19 +597,37 @@ module Shoulda
542
597
  end
543
598
  end
544
599
 
545
- def validate_scopes_present?
546
- if all_scopes_present_on_model?
600
+ def does_not_match_presence_of_attribute?
601
+ if attribute_present_on_model?
602
+ @failure_reason =
603
+ ":#{attribute} seems to be an attribute on #{model.name}."
604
+ false
605
+ else
606
+ true
607
+ end
608
+ end
609
+
610
+ def attribute_present_on_model?
611
+ model.method_defined?("#{attribute}=") ||
612
+ model.columns_hash.key?(attribute.to_s)
613
+ end
614
+
615
+ def matches_presence_of_scopes?
616
+ if scopes_missing_on_model.none?
547
617
  true
548
618
  else
619
+ inspected_scopes = scopes_missing_on_model.map(&:inspect)
620
+
549
621
  reason = ''
550
622
 
551
- reason << inspected_scopes_missing_on_model.to_sentence
623
+ reason << inspected_scopes.to_sentence
552
624
 
553
- if inspected_scopes_missing_on_model.many?
554
- reason << " do not seem to be attributes"
555
- else
556
- reason << " does not seem to be an attribute"
557
- end
625
+ reason <<
626
+ if inspected_scopes.many?
627
+ ' do not seem to be attributes'
628
+ else
629
+ ' does not seem to be an attribute'
630
+ end
558
631
 
559
632
  reason << " on #{model.name}."
560
633
 
@@ -564,21 +637,44 @@ module Shoulda
564
637
  end
565
638
  end
566
639
 
567
- def all_scopes_present_on_model?
568
- scopes_missing_on_model.none?
640
+ def does_not_match_presence_of_scopes?
641
+ if scopes_missing_on_model.any?
642
+ true
643
+ else
644
+ inspected_scopes = scopes_present_on_model.map(&:inspect)
645
+
646
+ reason = ''
647
+
648
+ reason << inspected_scopes.to_sentence
649
+
650
+ reason <<
651
+ if inspected_scopes.many?
652
+ ' seem to be attributes'
653
+ else
654
+ ' seems to be an attribute'
655
+ end
656
+
657
+ reason << " on #{model.name}."
658
+
659
+ @failure_reason = reason
660
+
661
+ false
662
+ end
569
663
  end
570
664
 
571
- def scopes_missing_on_model
572
- @_missing_scopes ||= expected_scopes.select do |scope|
573
- !model.method_defined?("#{scope}=")
665
+ def scopes_present_on_model
666
+ @_scopes_present_on_model ||= expected_scopes.select do |scope|
667
+ model.method_defined?("#{scope}=")
574
668
  end
575
669
  end
576
670
 
577
- def inspected_scopes_missing_on_model
578
- scopes_missing_on_model.map(&:inspect)
671
+ def scopes_missing_on_model
672
+ @_scopes_missing_on_model ||= expected_scopes.reject do |scope|
673
+ model.method_defined?("#{scope}=")
674
+ end
579
675
  end
580
676
 
581
- def validate_two_records_with_same_non_blank_value_cannot_coexist?
677
+ def matches_uniqueness_without_scopes?
582
678
  if existing_value_read.blank?
583
679
  update_existing_record!(arbitrary_non_blank_value)
584
680
  end
@@ -586,8 +682,18 @@ module Shoulda
586
682
  disallows_value_of(existing_value_read, @expected_message)
587
683
  end
588
684
 
589
- def validate_case_sensitivity?
590
- if should_validate_case_sensitivity?
685
+ def does_not_match_uniqueness_without_scopes?
686
+ @failure_reason = nil
687
+
688
+ if existing_value_read.blank?
689
+ update_existing_record!(arbitrary_non_blank_value)
690
+ end
691
+
692
+ allows_value_of(existing_value_read, @expected_message)
693
+ end
694
+
695
+ def matches_uniqueness_with_case_sensitivity_strategy?
696
+ if should_test_case_sensitivity?
591
697
  value = existing_value_read
592
698
  swapcased_value = value.swapcase
593
699
 
@@ -596,7 +702,7 @@ module Shoulda
596
702
  raise NonCaseSwappableValueError.create(
597
703
  model: model,
598
704
  attribute: @attribute,
599
- value: value
705
+ value: value,
600
706
  )
601
707
  end
602
708
 
@@ -609,7 +715,32 @@ module Shoulda
609
715
  end
610
716
  end
611
717
 
612
- def should_validate_case_sensitivity?
718
+ def does_not_match_uniqueness_with_case_sensitivity_strategy?
719
+ if should_test_case_sensitivity?
720
+ @failure_reason = nil
721
+
722
+ value = existing_value_read
723
+ swapcased_value = value.swapcase
724
+
725
+ if case_sensitivity_strategy == :sensitive
726
+ disallows_value_of(swapcased_value, @expected_message)
727
+ else
728
+ if value == swapcased_value
729
+ raise NonCaseSwappableValueError.create(
730
+ model: model,
731
+ attribute: @attribute,
732
+ value: value,
733
+ )
734
+ end
735
+
736
+ allows_value_of(swapcased_value, @expected_message)
737
+ end
738
+ else
739
+ true
740
+ end
741
+ end
742
+
743
+ def should_test_case_sensitivity?
613
744
  case_sensitivity_strategy != :ignore &&
614
745
  existing_value_read.respond_to?(:swapcase) &&
615
746
  !existing_value_read.empty?
@@ -621,37 +752,49 @@ module Shoulda
621
752
  false
622
753
  end
623
754
 
624
- def validate_after_scope_change?
625
- if expected_scopes.empty? || all_scopes_are_booleans?
626
- true
627
- else
755
+ def matches_uniqueness_with_scopes?
756
+ expected_scopes.none? ||
757
+ all_scopes_are_booleans? ||
628
758
  expected_scopes.all? do |scope|
629
- previous_value = @all_records.map(&scope).compact.max
759
+ setting_next_value_for(scope) do
760
+ allows_value_of(existing_value_read, @expected_message)
761
+ end
762
+ end
763
+ end
630
764
 
631
- next_value =
632
- if previous_value.blank?
633
- dummy_value_for(scope)
634
- else
635
- next_value_for(scope, previous_value)
636
- end
765
+ def does_not_match_uniqueness_with_scopes?
766
+ expected_scopes.any? &&
767
+ !all_scopes_are_booleans? &&
768
+ expected_scopes.any? do |scope|
769
+ setting_next_value_for(scope) do
770
+ @failure_reason = nil
771
+ disallows_value_of(existing_value_read, @expected_message)
772
+ end
773
+ end
774
+ end
637
775
 
638
- set_attribute_on_new_record!(scope, next_value)
776
+ def setting_next_value_for(scope)
777
+ previous_value = @all_records.map(&scope).compact.max
639
778
 
640
- if allows_value_of(existing_value_read, @expected_message)
641
- set_attribute_on_new_record!(scope, previous_value)
642
- true
643
- else
644
- false
645
- end
779
+ next_value =
780
+ if previous_value.blank?
781
+ dummy_value_for(scope)
782
+ else
783
+ next_value_for(scope, previous_value)
646
784
  end
647
- end
785
+
786
+ set_attribute_on_new_record!(scope, next_value)
787
+
788
+ yield
789
+ ensure
790
+ set_attribute_on_new_record!(scope, previous_value)
648
791
  end
649
792
 
650
793
  def dummy_value_for(scope)
651
794
  column = column_for(scope)
652
795
 
653
796
  if column.respond_to?(:array) && column.array
654
- [ dummy_scalar_value_for(column) ]
797
+ [dummy_scalar_value_for(column)]
655
798
  else
656
799
  dummy_scalar_value_for(column)
657
800
  end
@@ -663,7 +806,7 @@ module Shoulda
663
806
 
664
807
  def next_value_for(scope, previous_value)
665
808
  if previous_value.is_a?(Array)
666
- [ next_scalar_value_for(scope, previous_value[0]) ]
809
+ [next_scalar_value_for(scope, previous_value[0])]
667
810
  else
668
811
  next_scalar_value_for(scope, previous_value)
669
812
  end
@@ -682,7 +825,7 @@ module Shoulda
682
825
  elsif previous_value.respond_to?(:next)
683
826
  previous_value.next
684
827
  elsif previous_value.respond_to?(:to_datetime)
685
- previous_value.to_datetime.next
828
+ previous_value.to_datetime.in(60).next
686
829
  elsif boolean_value?(previous_value)
687
830
  !previous_value
688
831
  else
@@ -697,7 +840,7 @@ module Shoulda
697
840
  end
698
841
 
699
842
  def boolean_value?(value)
700
- value.in?([true, false])
843
+ [true, false].include?(value)
701
844
  end
702
845
 
703
846
  def defined_as_enum?(scope)
@@ -719,7 +862,7 @@ module Shoulda
719
862
  attribute_setter = build_attribute_setter(
720
863
  record,
721
864
  attribute_name,
722
- value
865
+ value,
723
866
  )
724
867
  attribute_setter.set!
725
868
 
@@ -731,7 +874,7 @@ module Shoulda
731
874
  :existing_record,
732
875
  existing_record,
733
876
  attribute_name,
734
- value
877
+ value,
735
878
  )
736
879
  end
737
880
 
@@ -740,7 +883,7 @@ module Shoulda
740
883
  :new_record,
741
884
  new_record,
742
885
  attribute_name,
743
- value
886
+ value,
744
887
  )
745
888
  end
746
889
 
@@ -748,18 +891,24 @@ module Shoulda
748
891
  @attribute_setters[:existing_record].last
749
892
  end
750
893
 
894
+ def attribute_setters_for_new_record
895
+ @attribute_setters[:new_record] +
896
+ [last_attribute_setter_used_on_new_record]
897
+ end
898
+
751
899
  def attribute_names_under_test
752
900
  [@attribute] + expected_scopes
753
901
  end
754
902
 
755
903
  def build_attribute_setter(record, attribute_name, value)
756
- Shoulda::Matchers::ActiveModel::AllowValueMatcher::AttributeSetter.new(
757
- matcher_name: :validate_uniqueness_of,
758
- object: record,
759
- attribute_name: attribute_name,
760
- value: value,
761
- ignore_interference_by_writer: ignore_interference_by_writer
762
- )
904
+ Shoulda::Matchers::ActiveModel::AllowValueMatcher::AttributeSetter.
905
+ new(
906
+ matcher_name: :validate_uniqueness_of,
907
+ object: record,
908
+ attribute_name: attribute_name,
909
+ value: value,
910
+ ignore_interference_by_writer: ignore_interference_by_writer,
911
+ )
763
912
  end
764
913
 
765
914
  def existing_value_read
@@ -786,47 +935,42 @@ module Shoulda
786
935
  @given_record.class
787
936
  end
788
937
 
789
- def failure_message_preface
938
+ def failure_message_preface # rubocop:disable Metrics/MethodLength
790
939
  prefix = ''
791
940
 
792
941
  if @existing_record_created
793
942
  prefix << "After taking the given #{model.name}"
794
943
 
795
944
  if attribute_setter_for_existing_record
796
- prefix << ', setting its '
945
+ prefix << ', setting '
797
946
  prefix << description_for_attribute_setter(
798
- attribute_setter_for_existing_record
947
+ attribute_setter_for_existing_record,
799
948
  )
800
949
  else
801
950
  prefix << ", whose :#{attribute} is "
802
951
  prefix << "‹#{existing_value_read.inspect}›"
803
952
  end
804
953
 
805
- prefix << ", and saving it as the existing record, then"
954
+ prefix << ', and saving it as the existing record, then'
955
+ elsif attribute_setter_for_existing_record
956
+ prefix << "Given an existing #{model.name},"
957
+ prefix << ' after setting '
958
+ prefix << description_for_attribute_setter(
959
+ attribute_setter_for_existing_record,
960
+ )
961
+ prefix << ', then'
806
962
  else
807
- if attribute_setter_for_existing_record
808
- prefix << "Given an existing #{model.name},"
809
- prefix << ' after setting its '
810
- prefix << description_for_attribute_setter(
811
- attribute_setter_for_existing_record
812
- )
813
- prefix << ', then'
814
- else
815
- prefix << "Given an existing #{model.name} whose :#{attribute}"
816
- prefix << ' is '
817
- prefix << Shoulda::Matchers::Util.inspect_value(
818
- existing_value_read
819
- )
820
- prefix << ', after'
821
- end
963
+ prefix << "Given an existing #{model.name} whose :#{attribute}"
964
+ prefix << ' is '
965
+ prefix << Shoulda::Matchers::Util.inspect_value(
966
+ existing_value_read,
967
+ )
968
+ prefix << ', after'
822
969
  end
823
970
 
824
- prefix << " making a new #{model.name} and setting its "
971
+ prefix << " making a new #{model.name} and setting "
825
972
 
826
- prefix << description_for_attribute_setter(
827
- last_attribute_setter_used_on_new_record,
828
- same_as_existing: existing_and_new_values_are_same?
829
- )
973
+ prefix << descriptions_for_attribute_setters_for_new_record
830
974
 
831
975
  prefix << ", the matcher expected the new #{model.name} to be"
832
976
 
@@ -848,21 +992,24 @@ different altogether.
848
992
  MESSAGE
849
993
  end
850
994
 
851
- def description_for_attribute_setter(attribute_setter, same_as_existing: nil)
852
- description = ":#{attribute_setter.attribute_name} to "
995
+ def description_for_attribute_setter(
996
+ attribute_setter,
997
+ same_as_existing: nil
998
+ )
999
+ description = "its :#{attribute_setter.attribute_name} to "
853
1000
 
854
1001
  if same_as_existing == false
855
1002
  description << 'a different value, '
856
1003
  end
857
1004
 
858
1005
  description << Shoulda::Matchers::Util.inspect_value(
859
- attribute_setter.value_written
1006
+ attribute_setter.value_written,
860
1007
  )
861
1008
 
862
1009
  if attribute_setter.attribute_changed_value?
863
1010
  description << ' (read back as '
864
1011
  description << Shoulda::Matchers::Util.inspect_value(
865
- attribute_setter.value_read
1012
+ attribute_setter.value_read,
866
1013
  )
867
1014
  description << ')'
868
1015
  end
@@ -874,6 +1021,23 @@ different altogether.
874
1021
  description
875
1022
  end
876
1023
 
1024
+ def descriptions_for_attribute_setters_for_new_record
1025
+ attribute_setter_descriptions_for_new_record.to_sentence
1026
+ end
1027
+
1028
+ def attribute_setter_descriptions_for_new_record
1029
+ attribute_setters_for_new_record.map do |attribute_setter|
1030
+ same_as_existing = (
1031
+ attribute_setter.value_written ==
1032
+ existing_value_written
1033
+ )
1034
+ description_for_attribute_setter(
1035
+ attribute_setter,
1036
+ same_as_existing: same_as_existing,
1037
+ )
1038
+ end
1039
+ end
1040
+
877
1041
  def existing_and_new_values_are_same?
878
1042
  last_value_set_on_new_record == existing_value_written
879
1043
  end
@@ -886,6 +1050,50 @@ different altogether.
886
1050
  last_submatcher_run.last_value_set
887
1051
  end
888
1052
 
1053
+ # @private
1054
+ class AttributeSetters
1055
+ include Enumerable
1056
+
1057
+ def initialize
1058
+ @attribute_setters = []
1059
+ end
1060
+
1061
+ def <<(given_attribute_setter)
1062
+ index = find_index_of(given_attribute_setter)
1063
+
1064
+ if index
1065
+ @attribute_setters[index] = given_attribute_setter
1066
+ else
1067
+ @attribute_setters << given_attribute_setter
1068
+ end
1069
+ end
1070
+
1071
+ def +(other_attribute_setters)
1072
+ dup.tap do |attribute_setters|
1073
+ other_attribute_setters.each do |attribute_setter|
1074
+ attribute_setters << attribute_setter
1075
+ end
1076
+ end
1077
+ end
1078
+
1079
+ def each(&block)
1080
+ @attribute_setters.each(&block)
1081
+ end
1082
+
1083
+ def last
1084
+ @attribute_setters.last
1085
+ end
1086
+
1087
+ private
1088
+
1089
+ def find_index_of(given_attribute_setter)
1090
+ @attribute_setters.find_index do |attribute_setter|
1091
+ attribute_setter.attribute_name ==
1092
+ given_attribute_setter.attribute_name
1093
+ end
1094
+ end
1095
+ end
1096
+
889
1097
  # @private
890
1098
  class NonCaseSwappableValueError < Shoulda::Matchers::Error
891
1099
  attr_accessor :model, :attribute, :value
@@ -895,7 +1103,7 @@ different altogether.
895
1103
  Your #{model.name} model has a uniqueness validation on :#{attribute} which is
896
1104
  declared to be case-sensitive, but the value the uniqueness matcher used,
897
1105
  #{value.inspect}, doesn't contain any alpha characters, so using it to
898
- to test the case-sensitivity part of the validation is ineffective. There are
1106
+ test the case-sensitivity part of the validation is ineffective. There are
899
1107
  two possible solutions for this depending on what you're trying to do here:
900
1108
 
901
1109
  a) If you meant for the validation to be case-sensitive, then you need to give
@@ -908,7 +1116,29 @@ b) If you meant for the validation to be case-insensitive, then you need to
908
1116
 
909
1117
  For more information, please see:
910
1118
 
911
- http://matchers.shoulda.io/docs/v#{Shoulda::Matchers::VERSION}/file.NonCaseSwappableValueError.html
1119
+ https://matchers.shoulda.io/docs/v#{Shoulda::Matchers::VERSION}/file.NonCaseSwappableValueError.html
1120
+ MESSAGE
1121
+ end
1122
+ end
1123
+
1124
+ # @private
1125
+ class ExistingRecordInvalid < Shoulda::Matchers::Error
1126
+ include Shoulda::Matchers::ActiveModel::Helpers
1127
+
1128
+ attr_accessor :underlying_exception
1129
+
1130
+ def message
1131
+ <<-MESSAGE.strip
1132
+ validate_uniqueness_of works by matching a new record against an
1133
+ existing record. If there is no existing record, it will create one
1134
+ using the record you provide.
1135
+
1136
+ While doing this, the following error was raised:
1137
+
1138
+ #{Shoulda::Matchers::Util.indent(underlying_exception.message, 2)}
1139
+
1140
+ The best way to fix this is to provide the matcher with a record where
1141
+ any required attributes are filled in with valid values beforehand.
912
1142
  MESSAGE
913
1143
  end
914
1144
  end