shoulda-matchers 3.1.0 → 5.2.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 (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