shoulda-matchers 3.1.0 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|