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
@@ -0,0 +1,185 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ # The `have_one_attached` matcher tests usage of the
5
+ # `has_one_attached` macro.
6
+ #
7
+ # #### Example
8
+ #
9
+ # class User < ApplicationRecord
10
+ # has_one_attached :avatar
11
+ # end
12
+ #
13
+ # # RSpec
14
+ # RSpec.describe User, type: :model do
15
+ # it { should have_one_attached(:avatar) }
16
+ # end
17
+ #
18
+ # # Minitest (Shoulda)
19
+ # class UserTest < ActiveSupport::TestCase
20
+ # should have_one_attached(:avatar)
21
+ # end
22
+ #
23
+ # @return [HaveAttachedMatcher]
24
+ #
25
+ def have_one_attached(name)
26
+ HaveAttachedMatcher.new(:one, name)
27
+ end
28
+
29
+ # The `have_many_attached` matcher tests usage of the
30
+ # `has_many_attached` macro.
31
+ #
32
+ # #### Example
33
+ #
34
+ # class Message < ApplicationRecord
35
+ # has_many_attached :images
36
+ # end
37
+ #
38
+ # # RSpec
39
+ # RSpec.describe Message, type: :model do
40
+ # it { should have_many_attached(:images) }
41
+ # end
42
+ #
43
+ # # Minitest (Shoulda)
44
+ # class MessageTest < ActiveSupport::TestCase
45
+ # should have_many_attached(:images)
46
+ # end
47
+ #
48
+ # @return [HaveAttachedMatcher]
49
+ #
50
+ def have_many_attached(name)
51
+ HaveAttachedMatcher.new(:many, name)
52
+ end
53
+
54
+ # @private
55
+ class HaveAttachedMatcher
56
+ attr_reader :name
57
+
58
+ def initialize(macro, name)
59
+ @macro = macro
60
+ @name = name
61
+ end
62
+
63
+ def description
64
+ "have a has_#{macro}_attached called #{name}"
65
+ end
66
+
67
+ def failure_message
68
+ <<-MESSAGE
69
+ Expected #{expectation}, but this could not be proved.
70
+ #{@failure}
71
+ MESSAGE
72
+ end
73
+
74
+ def failure_message_when_negated
75
+ <<-MESSAGE
76
+ Did not expect #{expectation}, but it does.
77
+ MESSAGE
78
+ end
79
+
80
+ def expectation
81
+ "#{model_class.name} to #{description}"
82
+ end
83
+
84
+ def matches?(subject)
85
+ @subject = subject
86
+ reader_attribute_exists? &&
87
+ writer_attribute_exists? &&
88
+ attachments_association_exists? &&
89
+ blobs_association_exists? &&
90
+ eager_loading_scope_exists?
91
+ end
92
+
93
+ private
94
+
95
+ attr_reader :subject, :macro
96
+
97
+ def reader_attribute_exists?
98
+ if subject.respond_to?(name)
99
+ true
100
+ else
101
+ @failure = "#{model_class.name} does not have a :#{name} method."
102
+ false
103
+ end
104
+ end
105
+
106
+ def writer_attribute_exists?
107
+ if subject.respond_to?("#{name}=")
108
+ true
109
+ else
110
+ @failure = "#{model_class.name} does not have a :#{name}= method."
111
+ false
112
+ end
113
+ end
114
+
115
+ def attachments_association_exists?
116
+ if attachments_association_matcher.matches?(subject)
117
+ true
118
+ else
119
+ @failure = attachments_association_matcher.failure_message
120
+ false
121
+ end
122
+ end
123
+
124
+ def attachments_association_matcher
125
+ @_attachments_association_matcher ||=
126
+ AssociationMatcher.new(
127
+ :"has_#{macro}",
128
+ attachments_association_name,
129
+ ).
130
+ conditions(name: name).
131
+ class_name('ActiveStorage::Attachment').
132
+ inverse_of(:record)
133
+ end
134
+
135
+ def attachments_association_name
136
+ case macro
137
+ when :one then "#{name}_attachment"
138
+ when :many then "#{name}_attachments"
139
+ end
140
+ end
141
+
142
+ def blobs_association_exists?
143
+ if blobs_association_matcher.matches?(subject)
144
+ true
145
+ else
146
+ @failure = blobs_association_matcher.failure_message
147
+ false
148
+ end
149
+ end
150
+
151
+ def blobs_association_matcher
152
+ @_blobs_association_matcher ||=
153
+ AssociationMatcher.new(
154
+ :"has_#{macro}",
155
+ blobs_association_name,
156
+ ).
157
+ through(attachments_association_name).
158
+ class_name('ActiveStorage::Blob').
159
+ source(:blob)
160
+ end
161
+
162
+ def blobs_association_name
163
+ case macro
164
+ when :one then "#{name}_blob"
165
+ when :many then "#{name}_blobs"
166
+ end
167
+ end
168
+
169
+ def eager_loading_scope_exists?
170
+ if model_class.respond_to?("with_attached_#{name}")
171
+ true
172
+ else
173
+ @failure = "#{model_class.name} does not have a " \
174
+ ":with_attached_#{name} scope."
175
+ false
176
+ end
177
+ end
178
+
179
+ def model_class
180
+ subject.class
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -13,7 +13,7 @@ module Shoulda
13
13
  # end
14
14
  #
15
15
  # # RSpec
16
- # describe Phone do
16
+ # RSpec.describe Phone, type: :model do
17
17
  # it { should have_db_column(:supported_ios_version) }
18
18
  # end
19
19
  #
@@ -37,7 +37,7 @@ module Shoulda
37
37
  # end
38
38
  #
39
39
  # # RSpec
40
- # describe Phone do
40
+ # RSpec.describe Phone, type: :model do
41
41
  # it do
42
42
  # should have_db_column(:camera_aperture).of_type(:decimal)
43
43
  # end
@@ -52,7 +52,7 @@ module Shoulda
52
52
  #
53
53
  # Use `with_options` to assert that a column has been defined with
54
54
  # certain options (`:precision`, `:limit`, `:default`, `:null`, `:scale`,
55
- # or `:primary`).
55
+ # `:primary` or `:array`).
56
56
  #
57
57
  # class CreatePhones < ActiveRecord::Migration
58
58
  # def change
@@ -63,7 +63,7 @@ module Shoulda
63
63
  # end
64
64
  #
65
65
  # # RSpec
66
- # describe Phone do
66
+ # RSpec.describe Phone, type: :model do
67
67
  # it do
68
68
  # should have_db_column(:camera_aperture).
69
69
  # with_options(precision: 1, null: false)
@@ -84,6 +84,8 @@ module Shoulda
84
84
 
85
85
  # @private
86
86
  class HaveDbColumnMatcher
87
+ OPTIONS = %i(precision limit default null scale primary array).freeze
88
+
87
89
  def initialize(column)
88
90
  @column = column
89
91
  @options = {}
@@ -95,7 +97,8 @@ module Shoulda
95
97
  end
96
98
 
97
99
  def with_options(opts = {})
98
- %w(precision limit default null scale primary).each do |attribute|
100
+ validate_options(opts)
101
+ OPTIONS.each do |attribute|
99
102
  if opts.key?(attribute.to_sym)
100
103
  @options[attribute.to_sym] = opts[attribute.to_sym]
101
104
  end
@@ -112,7 +115,8 @@ module Shoulda
112
115
  correct_default? &&
113
116
  correct_null? &&
114
117
  correct_scale? &&
115
- correct_primary?
118
+ correct_primary? &&
119
+ correct_array?
116
120
  end
117
121
 
118
122
  def failure_message
@@ -125,23 +129,38 @@ module Shoulda
125
129
 
126
130
  def description
127
131
  desc = "have db column named #{@column}"
128
- desc << " of type #{@options[:column_type]}" if @options.key?(:column_type)
129
- desc << " of precision #{@options[:precision]}" if @options.key?(:precision)
130
- desc << " of limit #{@options[:limit]}" if @options.key?(:limit)
131
- desc << " of default #{@options[:default]}" if @options.key?(:default)
132
- desc << " of null #{@options[:null]}" if @options.key?(:null)
133
- desc << " of primary #{@options[:primary]}" if @options.key?(:primary)
134
- desc << " of scale #{@options[:scale]}" if @options.key?(:scale)
132
+ if @options.key?(:column_type)
133
+ desc << " of type #{@options[:column_type]}"
134
+ end
135
+ if @options.key?(:precision)
136
+ desc << " of precision #{@options[:precision]}"
137
+ end
138
+ desc << " of limit #{@options[:limit]}" if @options.key?(:limit)
139
+ desc << " of default #{@options[:default]}" if @options.key?(:default)
140
+ desc << " of null #{@options[:null]}" if @options.key?(:null)
141
+ desc << " of primary #{@options[:primary]}" if @options.key?(:primary)
142
+ desc << " of scale #{@options[:scale]}" if @options.key?(:scale)
135
143
  desc
136
144
  end
137
145
 
138
146
  protected
139
147
 
148
+ def validate_options(opts)
149
+ invalid_options = opts.keys.map(&:to_sym) - OPTIONS
150
+ if invalid_options.any?
151
+ raise(
152
+ ArgumentError,
153
+ "Unknown option(s): #{invalid_options.map(&:inspect).join(', ')}",
154
+ )
155
+ end
156
+ end
157
+
140
158
  def column_exists?
141
159
  if model_class.column_names.include?(@column.to_s)
142
160
  true
143
161
  else
144
- @missing = "#{model_class} does not have a db column named #{@column}."
162
+ @missing =
163
+ "#{model_class} does not have a db column named #{@column}."
145
164
  false
146
165
  end
147
166
  end
@@ -152,8 +171,9 @@ module Shoulda
152
171
  if matched_column.type.to_s == @options[:column_type].to_s
153
172
  true
154
173
  else
155
- @missing = "#{model_class} has a db column named #{@column} " <<
156
- "of type #{matched_column.type}, not #{@options[:column_type]}."
174
+ @missing =
175
+ "#{model_class} has a db column named #{@column} " <<
176
+ "of type #{matched_column.type}, not #{@options[:column_type]}."
157
177
  false
158
178
  end
159
179
  end
@@ -229,18 +249,38 @@ module Shoulda
229
249
  true
230
250
  else
231
251
  @missing = "#{model_class} has a db column named #{@column} "
232
- if @options[:primary]
233
- @missing << 'that is not primary, but should be'
234
- else
235
- @missing << 'that is primary, but should not be'
236
- end
252
+ @missing <<
253
+ if @options[:primary]
254
+ 'that is not primary, but should be'
255
+ else
256
+ 'that is primary, but should not be'
257
+ end
258
+ false
259
+ end
260
+ end
261
+
262
+ def correct_array?
263
+ return true unless @options.key?(:array)
264
+
265
+ if matched_column.array? == @options[:array]
266
+ true
267
+ else
268
+ @missing = "#{model_class} has a db column named #{@column} "
269
+ @missing <<
270
+ if @options[:primary]
271
+ 'that is not array, but should be'
272
+ else
273
+ 'that is array, but should not be'
274
+ end
237
275
  false
238
276
  end
239
277
  end
240
278
 
241
279
  def matched_column
242
280
  @_matched_column ||= begin
243
- column = model_class.columns.detect { |each| each.name == @column.to_s }
281
+ column = model_class.columns.detect do |each|
282
+ each.name == @column.to_s
283
+ end
244
284
  DecoratedColumn.new(model_class, column)
245
285
  end
246
286
  end
@@ -269,7 +309,7 @@ module Shoulda
269
309
  end
270
310
 
271
311
  def type_cast_default
272
- Shoulda::Matchers::RailsShim.type_cast_default_for(model, self)
312
+ model.column_defaults[name]
273
313
  end
274
314
 
275
315
  def primary?
@@ -2,7 +2,9 @@ module Shoulda
2
2
  module Matchers
3
3
  module ActiveRecord
4
4
  # The `have_db_index` matcher tests that the table that backs your model
5
- # has a index on a specific column.
5
+ # has a specific index.
6
+ #
7
+ # You can specify one column:
6
8
  #
7
9
  # class CreateBlogs < ActiveRecord::Migration
8
10
  # def change
@@ -15,7 +17,7 @@ module Shoulda
15
17
  # end
16
18
  #
17
19
  # # RSpec
18
- # describe Blog do
20
+ # RSpec.describe Blog, type: :model do
19
21
  # it { should have_db_index(:user_id) }
20
22
  # end
21
23
  #
@@ -24,43 +26,83 @@ module Shoulda
24
26
  # should have_db_index(:user_id)
25
27
  # end
26
28
  #
27
- # #### Qualifiers
28
- #
29
- # ##### unique
30
- #
31
- # Use `unique` to assert that the index is unique.
29
+ # Or you can specify a group of columns:
32
30
  #
33
31
  # class CreateBlogs < ActiveRecord::Migration
34
32
  # def change
35
33
  # create_table :blogs do |t|
34
+ # t.integer :user_id
36
35
  # t.string :name
37
36
  # end
38
37
  #
39
- # add_index :blogs, :name, unique: true
38
+ # add_index :blogs, :user_id, :name
40
39
  # end
41
40
  # end
42
41
  #
43
42
  # # RSpec
44
- # describe Blog do
45
- # it { should have_db_index(:name).unique(true) }
43
+ # RSpec.describe Blog, type: :model do
44
+ # it { should have_db_index([:user_id, :name]) }
46
45
  # end
47
46
  #
48
47
  # # Minitest (Shoulda)
49
48
  # class BlogTest < ActiveSupport::TestCase
50
- # should have_db_index(:name).unique(true)
49
+ # should have_db_index([:user_id, :name])
50
+ # end
51
+ #
52
+ # Finally, if you're using Rails 5 and PostgreSQL, you can also specify an
53
+ # expression:
54
+ #
55
+ # class CreateLoggedErrors < ActiveRecord::Migration
56
+ # def change
57
+ # create_table :logged_errors do |t|
58
+ # t.string :code
59
+ # t.jsonb :content
60
+ # end
61
+ #
62
+ # add_index :logged_errors, 'lower(code)::text'
63
+ # end
64
+ # end
65
+ #
66
+ # # RSpec
67
+ # RSpec.describe LoggedError, type: :model do
68
+ # it { should have_db_index('lower(code)::text') }
69
+ # end
70
+ #
71
+ # # Minitest (Shoulda)
72
+ # class LoggedErrorTest < ActiveSupport::TestCase
73
+ # should have_db_index('lower(code)::text')
51
74
  # end
52
75
  #
53
- # Since it only ever makes since for `unique` to be `true`, you can also
54
- # leave off the argument to save some keystrokes:
76
+ # #### Qualifiers
77
+ #
78
+ # ##### unique
79
+ #
80
+ # Use `unique` to assert that the index is either unique or non-unique:
81
+ #
82
+ # class CreateBlogs < ActiveRecord::Migration
83
+ # def change
84
+ # create_table :blogs do |t|
85
+ # t.string :domain
86
+ # t.integer :user_id
87
+ # end
88
+ #
89
+ # add_index :blogs, :domain, unique: true
90
+ # add_index :blogs, :user_id
91
+ # end
92
+ # end
55
93
  #
56
94
  # # RSpec
57
- # describe Blog do
95
+ # RSpec.describe Blog, type: :model do
58
96
  # it { should have_db_index(:name).unique }
97
+ # it { should have_db_index(:name).unique(true) } # if you want to be explicit
98
+ # it { should have_db_index(:user_id).unique(false) }
59
99
  # end
60
100
  #
61
101
  # # Minitest (Shoulda)
62
102
  # class BlogTest < ActiveSupport::TestCase
63
103
  # should have_db_index(:name).unique
104
+ # should have_db_index(:name).unique(true) # if you want to be explicit
105
+ # should have_db_index(:user_id).unique(false)
64
106
  # end
65
107
  #
66
108
  # @return [HaveDbIndexMatcher]
@@ -72,12 +114,12 @@ module Shoulda
72
114
  # @private
73
115
  class HaveDbIndexMatcher
74
116
  def initialize(columns)
75
- @columns = normalize_columns_to_array(columns)
76
- @options = {}
117
+ @expected_columns = normalize_columns_to_array(columns)
118
+ @qualifiers = {}
77
119
  end
78
120
 
79
121
  def unique(unique = true)
80
- @options[:unique] = unique
122
+ @qualifiers[:unique] = unique
81
123
  self
82
124
  end
83
125
 
@@ -87,72 +129,146 @@ module Shoulda
87
129
  end
88
130
 
89
131
  def failure_message
90
- "Expected #{expectation} (#{@missing})"
132
+ message =
133
+ "Expected #{described_table_name} to #{positive_expectation}"
134
+
135
+ message <<
136
+ if index_exists?
137
+ ". The index does exist, but #{reason}."
138
+ elsif reason
139
+ ", but #{reason}."
140
+ else
141
+ ', but it does not.'
142
+ end
143
+
144
+ Shoulda::Matchers.word_wrap(message)
91
145
  end
92
146
 
93
147
  def failure_message_when_negated
94
- "Did not expect #{expectation}"
148
+ Shoulda::Matchers.word_wrap(
149
+ "Expected #{described_table_name} not to " +
150
+ "#{negative_expectation}, but it does.",
151
+ )
95
152
  end
96
153
 
97
154
  def description
98
- if @options.key?(:unique)
99
- "have a #{index_type} index on columns #{@columns.join(' and ')}"
100
- else
101
- "have an index on columns #{@columns.join(' and ')}"
102
- end
103
- end
155
+ description = 'have '
104
156
 
105
- protected
157
+ description <<
158
+ if qualifiers.include?(:unique)
159
+ "#{Shoulda::Matchers::Util.a_or_an(index_type)} "
160
+ else
161
+ 'an '
162
+ end
106
163
 
107
- def index_exists?
108
- ! matched_index.nil?
164
+ description << 'index on '
165
+
166
+ description << inspected_expected_columns
109
167
  end
110
168
 
111
- def correct_unique?
112
- return true unless @options.key?(:unique)
169
+ private
113
170
 
114
- is_unique = matched_index.unique
171
+ attr_reader :expected_columns, :qualifiers, :subject, :reason
115
172
 
116
- is_unique = !is_unique unless @options[:unique]
173
+ def normalize_columns_to_array(columns)
174
+ Array.wrap(columns).map(&:to_s)
175
+ end
117
176
 
118
- unless is_unique
119
- @missing = "#{table_name} has an index named #{matched_index.name} " <<
120
- "of unique #{matched_index.unique}, not #{@options[:unique]}."
121
- end
177
+ def index_exists?
178
+ !matched_index.nil?
179
+ end
122
180
 
123
- is_unique
181
+ def correct_unique?
182
+ if qualifiers.include?(:unique)
183
+ if qualifiers[:unique] && !matched_index.unique
184
+ @reason = 'it is not unique'
185
+ false
186
+ elsif !qualifiers[:unique] && matched_index.unique
187
+ @reason = 'it is unique'
188
+ false
189
+ else
190
+ true
191
+ end
192
+ else
193
+ true
194
+ end
124
195
  end
125
196
 
126
197
  def matched_index
127
- indexes.detect { |each| each.columns == @columns }
198
+ @_matched_index ||=
199
+ if expected_columns.one?
200
+ actual_indexes.detect do |index|
201
+ Array.wrap(index.columns) == expected_columns
202
+ end
203
+ else
204
+ actual_indexes.detect do |index|
205
+ index.columns == expected_columns
206
+ end
207
+ end
128
208
  end
129
209
 
130
- def model_class
131
- @subject.class
210
+ def actual_indexes
211
+ model.connection.indexes(table_name)
212
+ end
213
+
214
+ def described_table_name
215
+ if model
216
+ "the #{table_name} table"
217
+ else
218
+ 'a table'
219
+ end
132
220
  end
133
221
 
134
222
  def table_name
135
- model_class.table_name
223
+ model.table_name
224
+ end
225
+
226
+ def positive_expectation
227
+ if index_exists?
228
+ expectation = "have an index on #{inspected_expected_columns}"
229
+
230
+ if qualifiers.include?(:unique)
231
+ expectation << " and for it to be #{index_type}"
232
+ end
233
+
234
+ expectation
235
+ else
236
+ description
237
+ end
136
238
  end
137
239
 
138
- def indexes
139
- ::ActiveRecord::Base.connection.indexes(table_name)
240
+ def negative_expectation
241
+ description
140
242
  end
141
243
 
142
- def expectation
143
- "#{model_class.name} to #{description}"
244
+ def inspected_expected_columns
245
+ if formatted_expected_columns.one?
246
+ formatted_expected_columns.first.inspect
247
+ else
248
+ formatted_expected_columns.inspect
249
+ end
144
250
  end
145
251
 
146
252
  def index_type
147
- if @options[:unique]
253
+ if qualifiers[:unique]
148
254
  'unique'
149
255
  else
150
256
  'non-unique'
151
257
  end
152
258
  end
153
259
 
154
- def normalize_columns_to_array(columns)
155
- Array.wrap(columns).map(&:to_s)
260
+ def formatted_expected_columns
261
+ expected_columns.map do |column|
262
+ if column.match?(/^\w+$/)
263
+ column.to_sym
264
+ else
265
+ column
266
+ end
267
+ end
268
+ end
269
+
270
+ def model
271
+ subject&.class
156
272
  end
157
273
  end
158
274
  end