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.
- checksums.yaml +5 -5
- data/{MIT-LICENSE → LICENSE} +1 -1
- data/README.md +407 -232
- data/docs/errors/NonCaseSwappableValueError.md +2 -2
- data/lib/shoulda/matchers/action_controller/callback_matcher.rb +7 -80
- data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +4 -3
- data/lib/shoulda/matchers/action_controller/flash_store.rb +2 -4
- data/lib/shoulda/matchers/action_controller/permit_matcher.rb +36 -30
- data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +8 -10
- data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +7 -9
- data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +18 -15
- data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +3 -2
- data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +3 -3
- data/lib/shoulda/matchers/action_controller/route_matcher.rb +88 -29
- data/lib/shoulda/matchers/action_controller/route_params.rb +2 -2
- data/lib/shoulda/matchers/action_controller/set_flash_matcher.rb +4 -4
- data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +3 -3
- data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +19 -13
- data/lib/shoulda/matchers/action_controller.rb +2 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +1 -1
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +5 -9
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +2 -2
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +1 -1
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +1 -1
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +42 -39
- data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +52 -26
- data/lib/shoulda/matchers/active_model/helpers.rb +2 -2
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +32 -30
- data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +2 -1
- data/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
- data/lib/shoulda/matchers/active_model/qualifiers/allow_nil.rb +26 -0
- data/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +1 -1
- data/lib/shoulda/matchers/active_model/qualifiers.rb +2 -0
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +30 -6
- data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +8 -3
- data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +31 -16
- data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +52 -16
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +137 -84
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +159 -46
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +130 -66
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +251 -24
- data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +12 -9
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +38 -6
- data/lib/shoulda/matchers/active_model/validation_message_finder.rb +2 -4
- data/lib/shoulda/matchers/active_model/validator.rb +4 -9
- data/lib/shoulda/matchers/active_model.rb +3 -5
- data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +10 -7
- data/lib/shoulda/matchers/active_record/association_matcher.rb +386 -111
- data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +5 -2
- data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +4 -4
- data/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +11 -6
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +14 -15
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +30 -8
- data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +34 -11
- data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +69 -0
- data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +74 -0
- data/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +3 -2
- data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +7 -5
- data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +458 -42
- data/lib/shoulda/matchers/active_record/have_attached_matcher.rb +185 -0
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +63 -23
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +164 -48
- data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +106 -0
- data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +13 -11
- data/lib/shoulda/matchers/active_record/have_rich_text_matcher.rb +83 -0
- data/lib/shoulda/matchers/active_record/have_secure_token_matcher.rb +132 -0
- data/lib/shoulda/matchers/active_record/serialize_matcher.rb +18 -18
- data/lib/shoulda/matchers/active_record/uniqueness/test_model_creator.rb +1 -3
- data/lib/shoulda/matchers/active_record/uniqueness/test_models.rb +0 -2
- data/lib/shoulda/matchers/active_record/uniqueness.rb +1 -1
- data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +430 -200
- data/lib/shoulda/matchers/active_record.rb +28 -20
- data/lib/shoulda/matchers/configuration.rb +12 -1
- data/lib/shoulda/matchers/doublespeak/double.rb +1 -1
- data/lib/shoulda/matchers/doublespeak/double_collection.rb +3 -3
- data/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +8 -5
- data/lib/shoulda/matchers/doublespeak/object_double.rb +6 -2
- data/lib/shoulda/matchers/doublespeak/stub_implementation.rb +1 -5
- data/lib/shoulda/matchers/doublespeak/world.rb +2 -2
- data/lib/shoulda/matchers/doublespeak.rb +2 -1
- data/lib/shoulda/matchers/error.rb +1 -1
- data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +109 -29
- data/lib/shoulda/matchers/independent.rb +2 -2
- data/lib/shoulda/matchers/integrations/configuration.rb +8 -4
- data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +1 -1
- data/lib/shoulda/matchers/integrations/libraries/rails.rb +2 -2
- data/lib/shoulda/matchers/integrations/test_frameworks/active_support_test_case.rb +1 -1
- data/lib/shoulda/matchers/integrations/test_frameworks/minitest_4.rb +1 -1
- data/lib/shoulda/matchers/integrations/test_frameworks/minitest_5.rb +1 -1
- data/lib/shoulda/matchers/integrations/test_frameworks/missing_test_framework.rb +1 -1
- data/lib/shoulda/matchers/integrations/test_frameworks/test_unit.rb +1 -1
- data/lib/shoulda/matchers/rails_shim.rb +172 -51
- data/lib/shoulda/matchers/routing.rb +2 -2
- data/lib/shoulda/matchers/util/word_wrap.rb +17 -12
- data/lib/shoulda/matchers/util.rb +39 -5
- data/lib/shoulda/matchers/version.rb +1 -1
- data/lib/shoulda/matchers/warn.rb +4 -3
- data/shoulda-matchers.gemspec +33 -15
- metadata +31 -338
- data/.gitignore +0 -12
- data/.hound.yml +0 -3
- data/.hound_config/ruby.yml +0 -12
- data/.travis.yml +0 -19
- data/.yardopts +0 -10
- data/Appraisals +0 -73
- data/CONTRIBUTING.md +0 -101
- data/Gemfile +0 -15
- data/Gemfile.lock +0 -70
- data/NEWS.md +0 -986
- data/Rakefile +0 -39
- data/custom_plan.rb +0 -88
- data/doc_config/gh-pages/index.html.erb +0 -9
- data/doc_config/yard/setup.rb +0 -22
- data/doc_config/yard/templates/default/fulldoc/html/css/bootstrap.css +0 -5967
- data/doc_config/yard/templates/default/fulldoc/html/css/full_list.css +0 -12
- data/doc_config/yard/templates/default/fulldoc/html/css/global.css +0 -62
- data/doc_config/yard/templates/default/fulldoc/html/css/solarized.css +0 -69
- data/doc_config/yard/templates/default/fulldoc/html/css/style.css +0 -312
- data/doc_config/yard/templates/default/fulldoc/html/full_list.erb +0 -32
- data/doc_config/yard/templates/default/fulldoc/html/full_list_class.erb +0 -1
- data/doc_config/yard/templates/default/fulldoc/html/full_list_method.erb +0 -8
- data/doc_config/yard/templates/default/fulldoc/html/js/app.js +0 -298
- data/doc_config/yard/templates/default/fulldoc/html/js/full_list.js +0 -1
- data/doc_config/yard/templates/default/fulldoc/html/js/jquery.stickyheaders.js +0 -289
- data/doc_config/yard/templates/default/fulldoc/html/js/underscore.min.js +0 -6
- data/doc_config/yard/templates/default/fulldoc/html/setup.rb +0 -8
- data/doc_config/yard/templates/default/layout/html/breadcrumb.erb +0 -14
- data/doc_config/yard/templates/default/layout/html/fonts.erb +0 -1
- data/doc_config/yard/templates/default/layout/html/footer.erb +0 -6
- data/doc_config/yard/templates/default/layout/html/layout.erb +0 -23
- data/doc_config/yard/templates/default/layout/html/search.erb +0 -13
- data/doc_config/yard/templates/default/layout/html/setup.rb +0 -40
- data/doc_config/yard/templates/default/method_details/html/source.erb +0 -10
- data/doc_config/yard/templates/default/module/html/box_info.erb +0 -31
- data/gemfiles/4.0.0.gemfile +0 -38
- data/gemfiles/4.0.0.gemfile.lock +0 -223
- data/gemfiles/4.0.1.gemfile +0 -38
- data/gemfiles/4.0.1.gemfile.lock +0 -225
- data/gemfiles/4.1.gemfile +0 -38
- data/gemfiles/4.1.gemfile.lock +0 -220
- data/gemfiles/4.2.gemfile +0 -38
- data/gemfiles/4.2.gemfile.lock +0 -243
- data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +0 -159
- data/lib/shoulda/matchers/independent/delegate_method_matcher/stubbed_target.rb +0 -37
- data/script/SUPPORTED_VERSIONS +0 -1
- data/script/install_gems_in_all_appraisals +0 -14
- data/script/run_all_tests +0 -14
- data/script/update_gem_in_all_appraisals +0 -15
- data/script/update_gems_in_all_appraisals +0 -14
- data/spec/acceptance/active_model_integration_spec.rb +0 -23
- data/spec/acceptance/independent_matchers_spec.rb +0 -125
- data/spec/acceptance/multiple_libraries_integration_spec.rb +0 -55
- data/spec/acceptance/rails_integration_spec.rb +0 -156
- data/spec/acceptance_spec_helper.rb +0 -23
- data/spec/doublespeak_spec_helper.rb +0 -2
- data/spec/report_warnings.rb +0 -7
- data/spec/spec_helper.rb +0 -21
- data/spec/support/acceptance/adds_shoulda_matchers_to_project.rb +0 -133
- data/spec/support/acceptance/helpers/active_model_helpers.rb +0 -11
- data/spec/support/acceptance/helpers/array_helpers.rb +0 -13
- data/spec/support/acceptance/helpers/base_helpers.rb +0 -19
- data/spec/support/acceptance/helpers/command_helpers.rb +0 -55
- data/spec/support/acceptance/helpers/file_helpers.rb +0 -19
- data/spec/support/acceptance/helpers/gem_helpers.rb +0 -31
- data/spec/support/acceptance/helpers/minitest_helpers.rb +0 -11
- data/spec/support/acceptance/helpers/n_unit_helpers.rb +0 -25
- data/spec/support/acceptance/helpers/pluralization_helpers.rb +0 -13
- data/spec/support/acceptance/helpers/rails_version_helpers.rb +0 -11
- data/spec/support/acceptance/helpers/rspec_helpers.rb +0 -24
- data/spec/support/acceptance/helpers/ruby_version_helpers.rb +0 -9
- data/spec/support/acceptance/helpers/step_helpers.rb +0 -127
- data/spec/support/acceptance/helpers.rb +0 -31
- data/spec/support/acceptance/matchers/have_output.rb +0 -31
- data/spec/support/acceptance/matchers/indicate_number_of_tests_was_run_matcher.rb +0 -55
- data/spec/support/acceptance/matchers/indicate_that_tests_were_run_matcher.rb +0 -103
- data/spec/support/tests/bundle.rb +0 -94
- data/spec/support/tests/command_runner.rb +0 -230
- data/spec/support/tests/current_bundle.rb +0 -61
- data/spec/support/tests/database.rb +0 -28
- data/spec/support/tests/database_adapters/postgresql.rb +0 -25
- data/spec/support/tests/database_adapters/sqlite3.rb +0 -26
- data/spec/support/tests/database_configuration.rb +0 -33
- data/spec/support/tests/database_configuration_registry.rb +0 -28
- data/spec/support/tests/filesystem.rb +0 -100
- data/spec/support/tests/version.rb +0 -45
- data/spec/support/unit/active_record/create_table.rb +0 -54
- data/spec/support/unit/attribute.rb +0 -47
- data/spec/support/unit/capture.rb +0 -40
- data/spec/support/unit/change_value.rb +0 -111
- data/spec/support/unit/create_model_arguments/basic.rb +0 -135
- data/spec/support/unit/create_model_arguments/has_many.rb +0 -15
- data/spec/support/unit/create_model_arguments/uniqueness_matcher.rb +0 -74
- data/spec/support/unit/helpers/active_model_helpers.rb +0 -27
- data/spec/support/unit/helpers/active_model_versions.rb +0 -28
- data/spec/support/unit/helpers/active_record_versions.rb +0 -24
- data/spec/support/unit/helpers/active_resource_builder.rb +0 -27
- data/spec/support/unit/helpers/allow_value_matcher_helpers.rb +0 -15
- data/spec/support/unit/helpers/class_builder.rb +0 -90
- data/spec/support/unit/helpers/column_type_helpers.rb +0 -26
- data/spec/support/unit/helpers/confirmation_matcher_helpers.rb +0 -17
- data/spec/support/unit/helpers/controller_builder.rb +0 -63
- data/spec/support/unit/helpers/database_helpers.rb +0 -20
- data/spec/support/unit/helpers/i18n_faker.rb +0 -15
- data/spec/support/unit/helpers/mailer_builder.rb +0 -12
- data/spec/support/unit/helpers/model_builder.rb +0 -114
- data/spec/support/unit/helpers/rails_versions.rb +0 -28
- data/spec/support/unit/helpers/validation_matcher_scenario_helpers.rb +0 -44
- data/spec/support/unit/i18n.rb +0 -7
- data/spec/support/unit/load_environment.rb +0 -12
- data/spec/support/unit/matchers/deprecate.rb +0 -60
- data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +0 -51
- data/spec/support/unit/matchers/fail_with_message_matcher.rb +0 -62
- data/spec/support/unit/matchers/print_warning_including.rb +0 -59
- data/spec/support/unit/model_creation_strategies/active_model.rb +0 -111
- data/spec/support/unit/model_creation_strategies/active_record.rb +0 -77
- data/spec/support/unit/model_creators/active_model.rb +0 -39
- data/spec/support/unit/model_creators/active_record/has_and_belongs_to_many.rb +0 -95
- data/spec/support/unit/model_creators/active_record/has_many.rb +0 -67
- data/spec/support/unit/model_creators/active_record/uniqueness_matcher.rb +0 -42
- data/spec/support/unit/model_creators/active_record.rb +0 -43
- data/spec/support/unit/model_creators/basic.rb +0 -97
- data/spec/support/unit/model_creators.rb +0 -19
- data/spec/support/unit/rails_application.rb +0 -126
- data/spec/support/unit/record_builder_with_i18n_validation_message.rb +0 -69
- data/spec/support/unit/record_validating_confirmation_builder.rb +0 -51
- data/spec/support/unit/record_with_different_error_attribute_builder.rb +0 -92
- data/spec/support/unit/shared_examples/ignoring_interference_by_writer.rb +0 -79
- data/spec/support/unit/shared_examples/numerical_submatcher.rb +0 -17
- data/spec/support/unit/shared_examples/set_session_or_flash.rb +0 -360
- data/spec/support/unit/validation_matcher_scenario.rb +0 -62
- data/spec/unit/shoulda/matchers/action_controller/callback_matcher_spec.rb +0 -82
- data/spec/unit/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +0 -28
- data/spec/unit/shoulda/matchers/action_controller/permit_matcher_spec.rb +0 -592
- data/spec/unit/shoulda/matchers/action_controller/redirect_to_matcher_spec.rb +0 -42
- data/spec/unit/shoulda/matchers/action_controller/render_template_matcher_spec.rb +0 -76
- data/spec/unit/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +0 -62
- data/spec/unit/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +0 -90
- data/spec/unit/shoulda/matchers/action_controller/respond_with_matcher_spec.rb +0 -31
- data/spec/unit/shoulda/matchers/action_controller/route_matcher_spec.rb +0 -330
- data/spec/unit/shoulda/matchers/action_controller/route_params_spec.rb +0 -30
- data/spec/unit/shoulda/matchers/action_controller/set_flash_matcher_spec.rb +0 -67
- data/spec/unit/shoulda/matchers/action_controller/set_session_matcher_spec.rb +0 -17
- data/spec/unit/shoulda/matchers/action_controller/set_session_or_flash_matcher_spec.rb +0 -562
- data/spec/unit/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +0 -115
- data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +0 -823
- data/spec/unit/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +0 -86
- data/spec/unit/shoulda/matchers/active_model/have_secure_password_matcher_spec.rb +0 -20
- data/spec/unit/shoulda/matchers/active_model/helpers_spec.rb +0 -162
- data/spec/unit/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +0 -266
- data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +0 -91
- data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +0 -149
- data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +0 -207
- data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +0 -1015
- data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +0 -288
- data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +0 -1837
- data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +0 -380
- data/spec/unit/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +0 -107
- data/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +0 -1242
- data/spec/unit/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +0 -251
- data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +0 -168
- data/spec/unit/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +0 -111
- data/spec/unit/shoulda/matchers/active_record/have_db_index_matcher_spec.rb +0 -85
- data/spec/unit/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +0 -41
- data/spec/unit/shoulda/matchers/active_record/serialize_matcher_spec.rb +0 -86
- data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +0 -1418
- data/spec/unit/shoulda/matchers/doublespeak/double_collection_spec.rb +0 -190
- data/spec/unit/shoulda/matchers/doublespeak/double_implementation_registry_spec.rb +0 -21
- data/spec/unit/shoulda/matchers/doublespeak/double_spec.rb +0 -271
- data/spec/unit/shoulda/matchers/doublespeak/object_double_spec.rb +0 -77
- data/spec/unit/shoulda/matchers/doublespeak/proxy_implementation_spec.rb +0 -72
- data/spec/unit/shoulda/matchers/doublespeak/stub_implementation_spec.rb +0 -101
- data/spec/unit/shoulda/matchers/doublespeak/world_spec.rb +0 -80
- data/spec/unit/shoulda/matchers/doublespeak_spec.rb +0 -27
- data/spec/unit/shoulda/matchers/independent/delegate_method_matcher/stubbed_target_spec.rb +0 -43
- data/spec/unit/shoulda/matchers/independent/delegate_method_matcher_spec.rb +0 -517
- data/spec/unit/shoulda/matchers/routing/route_matcher_spec.rb +0 -242
- data/spec/unit/shoulda/matchers/util/word_wrap_spec.rb +0 -252
- data/spec/unit_spec_helper.rb +0 -46
- data/spec/warnings_spy/filesystem.rb +0 -45
- data/spec/warnings_spy/partitioner.rb +0 -36
- data/spec/warnings_spy/reader.rb +0 -53
- data/spec/warnings_spy/reporter.rb +0 -88
- data/spec/warnings_spy.rb +0 -64
- data/tasks/documentation.rb +0 -199
- 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 `:
|
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
|
-
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
desc << " of
|
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 =
|
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 =
|
156
|
-
|
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
|
-
|
233
|
-
@
|
234
|
-
|
235
|
-
|
236
|
-
|
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
|
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
|
-
|
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
|
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
|
-
#
|
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, :
|
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)
|
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)
|
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
|
-
#
|
54
|
-
#
|
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
|
-
@
|
76
|
-
@
|
117
|
+
@expected_columns = normalize_columns_to_array(columns)
|
118
|
+
@qualifiers = {}
|
77
119
|
end
|
78
120
|
|
79
121
|
def unique(unique = true)
|
80
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
108
|
-
|
164
|
+
description << 'index on '
|
165
|
+
|
166
|
+
description << inspected_expected_columns
|
109
167
|
end
|
110
168
|
|
111
|
-
|
112
|
-
return true unless @options.key?(:unique)
|
169
|
+
private
|
113
170
|
|
114
|
-
|
171
|
+
attr_reader :expected_columns, :qualifiers, :subject, :reason
|
115
172
|
|
116
|
-
|
173
|
+
def normalize_columns_to_array(columns)
|
174
|
+
Array.wrap(columns).map(&:to_s)
|
175
|
+
end
|
117
176
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
end
|
177
|
+
def index_exists?
|
178
|
+
!matched_index.nil?
|
179
|
+
end
|
122
180
|
|
123
|
-
|
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
|
-
|
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
|
131
|
-
|
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
|
-
|
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
|
139
|
-
|
240
|
+
def negative_expectation
|
241
|
+
description
|
140
242
|
end
|
141
243
|
|
142
|
-
def
|
143
|
-
|
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
|
253
|
+
if qualifiers[:unique]
|
148
254
|
'unique'
|
149
255
|
else
|
150
256
|
'non-unique'
|
151
257
|
end
|
152
258
|
end
|
153
259
|
|
154
|
-
def
|
155
|
-
|
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
|