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
@@ -10,11 +10,11 @@ module Shoulda
|
|
10
10
|
# pre-existing record (thereby failing the uniqueness check).
|
11
11
|
#
|
12
12
|
# class Post < ActiveRecord::Base
|
13
|
-
#
|
13
|
+
# validates :permalink, uniqueness: true
|
14
14
|
# end
|
15
15
|
#
|
16
16
|
# # RSpec
|
17
|
-
# describe Post do
|
17
|
+
# RSpec.describe Post, type: :model do
|
18
18
|
# it { should validate_uniqueness_of(:permalink) }
|
19
19
|
# end
|
20
20
|
#
|
@@ -49,39 +49,50 @@ module Shoulda
|
|
49
49
|
#
|
50
50
|
# You may be tempted to test the model like this:
|
51
51
|
#
|
52
|
-
# describe Post do
|
52
|
+
# RSpec.describe Post, type: :model do
|
53
53
|
# it { should validate_uniqueness_of(:title) }
|
54
54
|
# end
|
55
55
|
#
|
56
|
-
# However, running this test will fail with
|
56
|
+
# However, running this test will fail with an exception such as:
|
57
57
|
#
|
58
|
-
#
|
58
|
+
# Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher::ExistingRecordInvalid:
|
59
|
+
# validate_uniqueness_of works by matching a new record against an
|
60
|
+
# existing record. If there is no existing record, it will create one
|
61
|
+
# using the record you provide.
|
59
62
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
63
|
+
# While doing this, the following error was raised:
|
64
|
+
#
|
65
|
+
# PG::NotNullViolation: ERROR: null value in column "content" violates not-null constraint
|
66
|
+
# DETAIL: Failing row contains (1, null, null).
|
67
|
+
# : INSERT INTO "posts" DEFAULT VALUES RETURNING "id"
|
68
|
+
#
|
69
|
+
# The best way to fix this is to provide the matcher with a record where
|
70
|
+
# any required attributes are filled in with valid values beforehand.
|
71
|
+
#
|
72
|
+
# (The exact error message will differ depending on which database you're
|
73
|
+
# using, but you get the idea.)
|
64
74
|
#
|
65
75
|
# This happens because `validate_uniqueness_of` tries to create a new post
|
66
76
|
# but cannot do so because of the `content` attribute: though unrelated to
|
67
|
-
# this test, it nevertheless needs to be filled in.
|
68
|
-
#
|
77
|
+
# this test, it nevertheless needs to be filled in. As indicated at the
|
78
|
+
# end of the error message, the solution is to build a custom Post object
|
79
|
+
# ahead of time with `content` filled in:
|
69
80
|
#
|
70
|
-
# describe Post do
|
81
|
+
# RSpec.describe Post, type: :model do
|
71
82
|
# describe "validations" do
|
72
|
-
# subject { Post.new(content:
|
83
|
+
# subject { Post.new(content: "Here is the content") }
|
73
84
|
# it { should validate_uniqueness_of(:title) }
|
74
85
|
# end
|
75
86
|
# end
|
76
87
|
#
|
77
88
|
# Or, if you're using
|
78
|
-
# [
|
89
|
+
# [FactoryBot](https://github.com/thoughtbot/factory_bot) and you have a
|
79
90
|
# `post` factory defined which automatically fills in `content`, you can
|
80
91
|
# say:
|
81
92
|
#
|
82
|
-
# describe Post do
|
93
|
+
# RSpec.describe Post, type: :model do
|
83
94
|
# describe "validations" do
|
84
|
-
# subject {
|
95
|
+
# subject { FactoryBot.build(:post) }
|
85
96
|
# it { should validate_uniqueness_of(:title) }
|
86
97
|
# end
|
87
98
|
# end
|
@@ -91,11 +102,11 @@ module Shoulda
|
|
91
102
|
# Use `on` if your validation applies only under a certain context.
|
92
103
|
#
|
93
104
|
# class Post < ActiveRecord::Base
|
94
|
-
#
|
105
|
+
# validates :title, uniqueness: true, on: :create
|
95
106
|
# end
|
96
107
|
#
|
97
108
|
# # RSpec
|
98
|
-
# describe Post do
|
109
|
+
# RSpec.describe Post, type: :model do
|
99
110
|
# it { should validate_uniqueness_of(:title).on(:create) }
|
100
111
|
# end
|
101
112
|
#
|
@@ -109,11 +120,11 @@ module Shoulda
|
|
109
120
|
# Use `with_message` if you are using a custom validation message.
|
110
121
|
#
|
111
122
|
# class Post < ActiveRecord::Base
|
112
|
-
#
|
123
|
+
# validates :title, uniqueness: true, message: 'Please choose another title'
|
113
124
|
# end
|
114
125
|
#
|
115
126
|
# # RSpec
|
116
|
-
# describe Post do
|
127
|
+
# RSpec.describe Post, type: :model do
|
117
128
|
# it do
|
118
129
|
# should validate_uniqueness_of(:title).
|
119
130
|
# with_message('Please choose another title')
|
@@ -133,11 +144,11 @@ module Shoulda
|
|
133
144
|
# unique, but the scoped attributes are not unique either.
|
134
145
|
#
|
135
146
|
# class Post < ActiveRecord::Base
|
136
|
-
#
|
147
|
+
# validates :slug, uniqueness: { scope: :journal_id }
|
137
148
|
# end
|
138
149
|
#
|
139
150
|
# # RSpec
|
140
|
-
# describe Post do
|
151
|
+
# RSpec.describe Post, type: :model do
|
141
152
|
# it { should validate_uniqueness_of(:slug).scoped_to(:journal_id) }
|
142
153
|
# end
|
143
154
|
#
|
@@ -146,6 +157,12 @@ module Shoulda
|
|
146
157
|
# should validate_uniqueness_of(:slug).scoped_to(:journal_id)
|
147
158
|
# end
|
148
159
|
#
|
160
|
+
# NOTE: Support for testing uniqueness validation scoped to an array of
|
161
|
+
# associations is not available.
|
162
|
+
#
|
163
|
+
# For more information, please refer to
|
164
|
+
# https://github.com/thoughtbot/shoulda-matchers/issues/814
|
165
|
+
#
|
149
166
|
# ##### case_insensitive
|
150
167
|
#
|
151
168
|
# Use `case_insensitive` to test usage of the `:case_sensitive` option
|
@@ -154,11 +171,11 @@ module Shoulda
|
|
154
171
|
# attributes in the pre-existing record.
|
155
172
|
#
|
156
173
|
# class Post < ActiveRecord::Base
|
157
|
-
#
|
174
|
+
# validates :key, uniqueness: { case_sensitive: false }
|
158
175
|
# end
|
159
176
|
#
|
160
177
|
# # RSpec
|
161
|
-
# describe Post do
|
178
|
+
# RSpec.describe Post, type: :model do
|
162
179
|
# it { should validate_uniqueness_of(:key).case_insensitive }
|
163
180
|
# end
|
164
181
|
#
|
@@ -182,7 +199,7 @@ module Shoulda
|
|
182
199
|
# attribute.
|
183
200
|
#
|
184
201
|
# class User < ActiveRecord::Base
|
185
|
-
#
|
202
|
+
# validates :email, uniqueness: true
|
186
203
|
#
|
187
204
|
# def email=(value)
|
188
205
|
# super(value.downcase)
|
@@ -190,7 +207,7 @@ module Shoulda
|
|
190
207
|
# end
|
191
208
|
#
|
192
209
|
# # RSpec
|
193
|
-
# describe Post do
|
210
|
+
# RSpec.describe Post, type: :model do
|
194
211
|
# it do
|
195
212
|
# should validate_uniqueness_of(:email).ignoring_case_sensitivity
|
196
213
|
# end
|
@@ -206,11 +223,11 @@ module Shoulda
|
|
206
223
|
# Use `allow_nil` to assert that the attribute allows nil.
|
207
224
|
#
|
208
225
|
# class Post < ActiveRecord::Base
|
209
|
-
#
|
226
|
+
# validates :author_id, uniqueness: true, allow_nil: true
|
210
227
|
# end
|
211
228
|
#
|
212
229
|
# # RSpec
|
213
|
-
# describe Post do
|
230
|
+
# RSpec.describe Post, type: :model do
|
214
231
|
# it { should validate_uniqueness_of(:author_id).allow_nil }
|
215
232
|
# end
|
216
233
|
#
|
@@ -226,11 +243,11 @@ module Shoulda
|
|
226
243
|
# Use `allow_blank` to assert that the attribute allows a blank value.
|
227
244
|
#
|
228
245
|
# class Post < ActiveRecord::Base
|
229
|
-
#
|
246
|
+
# validates :author_id, uniqueness: true, allow_blank: true
|
230
247
|
# end
|
231
248
|
#
|
232
249
|
# # RSpec
|
233
|
-
# describe Post do
|
250
|
+
# RSpec.describe Post, type: :model do
|
234
251
|
# it { should validate_uniqueness_of(:author_id).allow_blank }
|
235
252
|
# end
|
236
253
|
#
|
@@ -253,19 +270,19 @@ module Shoulda
|
|
253
270
|
super(attribute)
|
254
271
|
@expected_message = :taken
|
255
272
|
@options = {
|
256
|
-
case_sensitivity_strategy: :sensitive
|
273
|
+
case_sensitivity_strategy: :sensitive,
|
257
274
|
}
|
258
275
|
@existing_record_created = false
|
259
276
|
@failure_reason = nil
|
260
277
|
@failure_reason_when_negated = nil
|
261
278
|
@attribute_setters = {
|
262
|
-
existing_record:
|
263
|
-
new_record:
|
279
|
+
existing_record: AttributeSetters.new,
|
280
|
+
new_record: AttributeSetters.new,
|
264
281
|
}
|
265
282
|
end
|
266
283
|
|
267
284
|
def scoped_to(*scopes)
|
268
|
-
@options[:scopes] = [*scopes].flatten
|
285
|
+
@options[:scopes] = [*scopes].flatten.map(&:to_sym)
|
269
286
|
self
|
270
287
|
end
|
271
288
|
|
@@ -285,7 +302,7 @@ module Shoulda
|
|
285
302
|
end
|
286
303
|
|
287
304
|
def expects_to_allow_nil?
|
288
|
-
@options[:allow_nil]
|
305
|
+
@options[:allow_nil] == true
|
289
306
|
end
|
290
307
|
|
291
308
|
def allow_blank
|
@@ -294,7 +311,7 @@ module Shoulda
|
|
294
311
|
end
|
295
312
|
|
296
313
|
def expects_to_allow_blank?
|
297
|
-
@options[:allow_blank]
|
314
|
+
@options[:allow_blank] == true
|
298
315
|
end
|
299
316
|
|
300
317
|
def simple_description
|
@@ -313,15 +330,29 @@ module Shoulda
|
|
313
330
|
@given_record = given_record
|
314
331
|
@all_records = model.all
|
315
332
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
333
|
+
matches_presence_of_attribute? &&
|
334
|
+
matches_presence_of_scopes? &&
|
335
|
+
matches_scopes_configuration? &&
|
336
|
+
matches_uniqueness_without_scopes? &&
|
337
|
+
matches_uniqueness_with_case_sensitivity_strategy? &&
|
338
|
+
matches_uniqueness_with_scopes? &&
|
339
|
+
matches_allow_nil? &&
|
340
|
+
matches_allow_blank?
|
341
|
+
ensure
|
342
|
+
Uniqueness::TestModels.remove_all
|
343
|
+
end
|
344
|
+
|
345
|
+
def does_not_match?(given_record)
|
346
|
+
@given_record = given_record
|
347
|
+
@all_records = model.all
|
348
|
+
|
349
|
+
does_not_match_presence_of_scopes? ||
|
350
|
+
does_not_match_scopes_configuration? ||
|
351
|
+
does_not_match_uniqueness_without_scopes? ||
|
352
|
+
does_not_match_uniqueness_with_case_sensitivity_strategy? ||
|
353
|
+
does_not_match_uniqueness_with_scopes? ||
|
354
|
+
does_not_match_allow_nil? ||
|
355
|
+
does_not_match_allow_blank?
|
325
356
|
ensure
|
326
357
|
Uniqueness::TestModels.remove_all
|
327
358
|
end
|
@@ -370,83 +401,121 @@ module Shoulda
|
|
370
401
|
end
|
371
402
|
end
|
372
403
|
|
373
|
-
def
|
374
|
-
model.
|
404
|
+
def validations
|
405
|
+
model.validators_on(@attribute).select do |validator|
|
375
406
|
validator.is_a?(::ActiveRecord::Validations::UniquenessValidator)
|
376
407
|
end
|
377
408
|
end
|
378
409
|
|
379
|
-
def
|
380
|
-
if
|
410
|
+
def matches_scopes_configuration?
|
411
|
+
if scopes_match?
|
381
412
|
true
|
382
413
|
else
|
383
|
-
@failure_reason = 'Expected the validation'
|
414
|
+
@failure_reason = 'Expected the validation '
|
384
415
|
|
385
|
-
|
386
|
-
|
416
|
+
@failure_reason <<
|
417
|
+
if expected_scopes.empty?
|
418
|
+
'not to be scoped to anything, '
|
419
|
+
else
|
420
|
+
"to be scoped to #{inspected_expected_scopes}, "
|
421
|
+
end
|
422
|
+
|
423
|
+
if actual_sets_of_scopes.any?
|
424
|
+
@failure_reason << 'but it was scoped to '
|
425
|
+
@failure_reason << "#{inspected_actual_scopes} instead."
|
387
426
|
else
|
388
|
-
@failure_reason <<
|
427
|
+
@failure_reason << 'but it was not scoped to anything.'
|
389
428
|
end
|
390
429
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
430
|
+
false
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
def does_not_match_scopes_configuration?
|
435
|
+
if scopes_match?
|
436
|
+
@failure_reason = 'Expected the validation '
|
437
|
+
|
438
|
+
if expected_scopes.empty?
|
439
|
+
@failure_reason << 'to be scoped to nothing, '
|
440
|
+
@failure_reason << 'but it was scoped to '
|
395
441
|
@failure_reason << "#{inspected_actual_scopes} instead."
|
442
|
+
else
|
443
|
+
@failure_reason << 'not to be scoped to '
|
444
|
+
@failure_reason << inspected_expected_scopes
|
396
445
|
end
|
397
446
|
|
398
447
|
false
|
448
|
+
else
|
449
|
+
true
|
399
450
|
end
|
400
451
|
end
|
401
452
|
|
402
|
-
def
|
403
|
-
|
453
|
+
def scopes_match?
|
454
|
+
actual_sets_of_scopes.empty? && expected_scopes.empty? ||
|
455
|
+
actual_sets_of_scopes.any? { |scopes| scopes == expected_scopes }
|
404
456
|
end
|
405
457
|
|
406
458
|
def inspected_expected_scopes
|
407
459
|
expected_scopes.map(&:inspect).to_sentence
|
408
460
|
end
|
409
461
|
|
410
|
-
def
|
411
|
-
|
412
|
-
|
462
|
+
def inspected_actual_scopes
|
463
|
+
inspected_actual_sets_of_scopes.to_sentence(
|
464
|
+
words_connector: ' and ',
|
465
|
+
last_word_connector: ', and',
|
466
|
+
)
|
467
|
+
end
|
468
|
+
|
469
|
+
def inspected_actual_sets_of_scopes
|
470
|
+
inspected_sets_of_scopes = actual_sets_of_scopes.map do |scopes|
|
471
|
+
scopes.map(&:inspect)
|
472
|
+
end
|
473
|
+
|
474
|
+
if inspected_sets_of_scopes.many?
|
475
|
+
inspected_sets_of_scopes.map { |x| "(#{x.to_sentence})" }
|
413
476
|
else
|
414
|
-
|
477
|
+
inspected_sets_of_scopes.map(&:to_sentence)
|
415
478
|
end
|
416
479
|
end
|
417
480
|
|
418
|
-
def
|
419
|
-
|
481
|
+
def expected_scopes
|
482
|
+
Array.wrap(@options[:scopes])
|
420
483
|
end
|
421
484
|
|
422
|
-
def
|
423
|
-
|
424
|
-
|
485
|
+
def actual_sets_of_scopes
|
486
|
+
validations.map do |validation|
|
487
|
+
Array.wrap(validation.options[:scope]).map(&:to_sym)
|
488
|
+
end.reject(&:empty?)
|
489
|
+
end
|
490
|
+
|
491
|
+
def matches_allow_nil?
|
492
|
+
!expects_to_allow_nil? || (
|
493
|
+
update_existing_record!(nil) &&
|
425
494
|
allows_value_of(nil, @expected_message)
|
426
|
-
|
427
|
-
|
428
|
-
|
495
|
+
)
|
496
|
+
end
|
497
|
+
|
498
|
+
def does_not_match_allow_nil?
|
499
|
+
expects_to_allow_nil? && (
|
500
|
+
update_existing_record!(nil) &&
|
501
|
+
(@failure_reason = nil ||
|
502
|
+
disallows_value_of(nil, @expected_message)
|
503
|
+
)
|
504
|
+
)
|
429
505
|
end
|
430
506
|
|
431
|
-
def
|
432
|
-
|
433
|
-
update_existing_record!('')
|
507
|
+
def matches_allow_blank?
|
508
|
+
!expects_to_allow_blank? || (
|
509
|
+
update_existing_record!('') &&
|
434
510
|
allows_value_of('', @expected_message)
|
435
|
-
|
436
|
-
true
|
437
|
-
end
|
511
|
+
)
|
438
512
|
end
|
439
513
|
|
440
|
-
def
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
"The record you provided could not be created, " +
|
446
|
-
"as it failed with the following validation errors:\n\n" +
|
447
|
-
format_validation_errors(existing_record.errors)
|
448
|
-
false
|
449
|
-
end
|
514
|
+
def does_not_match_allow_blank?
|
515
|
+
expects_to_allow_blank? && (
|
516
|
+
update_existing_record!('') &&
|
517
|
+
(@failure_reason = nil || disallows_value_of('', @expected_message))
|
518
|
+
)
|
450
519
|
end
|
451
520
|
|
452
521
|
def existing_record
|
@@ -467,46 +536,34 @@ module Shoulda
|
|
467
536
|
end
|
468
537
|
|
469
538
|
def find_existing_record
|
470
|
-
|
471
|
-
|
472
|
-
if record.present?
|
473
|
-
record
|
474
|
-
else
|
475
|
-
nil
|
476
|
-
end
|
539
|
+
model.first.presence
|
477
540
|
end
|
478
541
|
|
479
542
|
def create_existing_record
|
480
543
|
@given_record.tap do |existing_record|
|
481
|
-
|
482
|
-
existing_record.save
|
544
|
+
existing_record.save(validate: false)
|
483
545
|
end
|
546
|
+
rescue ::ActiveRecord::StatementInvalid => e
|
547
|
+
raise ExistingRecordInvalid.create(underlying_exception: e)
|
484
548
|
end
|
485
549
|
|
486
550
|
def update_existing_record!(value)
|
487
551
|
if existing_value_read != value
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
value
|
493
|
-
)
|
494
|
-
existing_record.save!
|
552
|
+
set_attribute_on_existing_record!(@attribute, value)
|
553
|
+
# It would be nice if we could ensure that the record was valid,
|
554
|
+
# but that would break users' existing tests
|
555
|
+
existing_record.save(validate: false)
|
495
556
|
end
|
496
|
-
end
|
497
557
|
|
498
|
-
|
499
|
-
if has_secure_password?
|
500
|
-
instance.password = "password"
|
501
|
-
instance.password_confirmation = "password"
|
502
|
-
end
|
558
|
+
true
|
503
559
|
end
|
504
560
|
|
505
561
|
def arbitrary_non_blank_value
|
562
|
+
non_blank_value = dummy_value_for(@attribute)
|
506
563
|
limit = column_limit_for(@attribute)
|
507
|
-
non_blank_value = 'an arbitrary value'
|
508
564
|
|
509
|
-
|
565
|
+
is_string_value = non_blank_value.is_a?(String)
|
566
|
+
if is_string_value && limit && limit < non_blank_value.length
|
510
567
|
'x' * limit
|
511
568
|
else
|
512
569
|
non_blank_value
|
@@ -514,9 +571,7 @@ module Shoulda
|
|
514
571
|
end
|
515
572
|
|
516
573
|
def has_secure_password?
|
517
|
-
|
518
|
-
'ActiveModel::SecurePassword::InstanceMethodsOnActivation'
|
519
|
-
)
|
574
|
+
Shoulda::Matchers::RailsShim.has_secure_password?(subject, @attribute)
|
520
575
|
end
|
521
576
|
|
522
577
|
def build_new_record
|
@@ -525,15 +580,15 @@ module Shoulda
|
|
525
580
|
attribute_names_under_test.each do |attribute_name|
|
526
581
|
set_attribute_on_new_record!(
|
527
582
|
attribute_name,
|
528
|
-
existing_record.public_send(attribute_name)
|
583
|
+
existing_record.public_send(attribute_name),
|
529
584
|
)
|
530
585
|
end
|
531
586
|
|
532
587
|
@new_record
|
533
588
|
end
|
534
589
|
|
535
|
-
def
|
536
|
-
if
|
590
|
+
def matches_presence_of_attribute?
|
591
|
+
if attribute_present_on_model?
|
537
592
|
true
|
538
593
|
else
|
539
594
|
@failure_reason =
|
@@ -542,19 +597,37 @@ module Shoulda
|
|
542
597
|
end
|
543
598
|
end
|
544
599
|
|
545
|
-
def
|
546
|
-
if
|
600
|
+
def does_not_match_presence_of_attribute?
|
601
|
+
if attribute_present_on_model?
|
602
|
+
@failure_reason =
|
603
|
+
":#{attribute} seems to be an attribute on #{model.name}."
|
604
|
+
false
|
605
|
+
else
|
606
|
+
true
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
def attribute_present_on_model?
|
611
|
+
model.method_defined?("#{attribute}=") ||
|
612
|
+
model.columns_hash.key?(attribute.to_s)
|
613
|
+
end
|
614
|
+
|
615
|
+
def matches_presence_of_scopes?
|
616
|
+
if scopes_missing_on_model.none?
|
547
617
|
true
|
548
618
|
else
|
619
|
+
inspected_scopes = scopes_missing_on_model.map(&:inspect)
|
620
|
+
|
549
621
|
reason = ''
|
550
622
|
|
551
|
-
reason <<
|
623
|
+
reason << inspected_scopes.to_sentence
|
552
624
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
625
|
+
reason <<
|
626
|
+
if inspected_scopes.many?
|
627
|
+
' do not seem to be attributes'
|
628
|
+
else
|
629
|
+
' does not seem to be an attribute'
|
630
|
+
end
|
558
631
|
|
559
632
|
reason << " on #{model.name}."
|
560
633
|
|
@@ -564,21 +637,44 @@ module Shoulda
|
|
564
637
|
end
|
565
638
|
end
|
566
639
|
|
567
|
-
def
|
568
|
-
scopes_missing_on_model.
|
640
|
+
def does_not_match_presence_of_scopes?
|
641
|
+
if scopes_missing_on_model.any?
|
642
|
+
true
|
643
|
+
else
|
644
|
+
inspected_scopes = scopes_present_on_model.map(&:inspect)
|
645
|
+
|
646
|
+
reason = ''
|
647
|
+
|
648
|
+
reason << inspected_scopes.to_sentence
|
649
|
+
|
650
|
+
reason <<
|
651
|
+
if inspected_scopes.many?
|
652
|
+
' seem to be attributes'
|
653
|
+
else
|
654
|
+
' seems to be an attribute'
|
655
|
+
end
|
656
|
+
|
657
|
+
reason << " on #{model.name}."
|
658
|
+
|
659
|
+
@failure_reason = reason
|
660
|
+
|
661
|
+
false
|
662
|
+
end
|
569
663
|
end
|
570
664
|
|
571
|
-
def
|
572
|
-
@
|
573
|
-
|
665
|
+
def scopes_present_on_model
|
666
|
+
@_scopes_present_on_model ||= expected_scopes.select do |scope|
|
667
|
+
model.method_defined?("#{scope}=")
|
574
668
|
end
|
575
669
|
end
|
576
670
|
|
577
|
-
def
|
578
|
-
|
671
|
+
def scopes_missing_on_model
|
672
|
+
@_scopes_missing_on_model ||= expected_scopes.reject do |scope|
|
673
|
+
model.method_defined?("#{scope}=")
|
674
|
+
end
|
579
675
|
end
|
580
676
|
|
581
|
-
def
|
677
|
+
def matches_uniqueness_without_scopes?
|
582
678
|
if existing_value_read.blank?
|
583
679
|
update_existing_record!(arbitrary_non_blank_value)
|
584
680
|
end
|
@@ -586,8 +682,18 @@ module Shoulda
|
|
586
682
|
disallows_value_of(existing_value_read, @expected_message)
|
587
683
|
end
|
588
684
|
|
589
|
-
def
|
590
|
-
|
685
|
+
def does_not_match_uniqueness_without_scopes?
|
686
|
+
@failure_reason = nil
|
687
|
+
|
688
|
+
if existing_value_read.blank?
|
689
|
+
update_existing_record!(arbitrary_non_blank_value)
|
690
|
+
end
|
691
|
+
|
692
|
+
allows_value_of(existing_value_read, @expected_message)
|
693
|
+
end
|
694
|
+
|
695
|
+
def matches_uniqueness_with_case_sensitivity_strategy?
|
696
|
+
if should_test_case_sensitivity?
|
591
697
|
value = existing_value_read
|
592
698
|
swapcased_value = value.swapcase
|
593
699
|
|
@@ -596,7 +702,7 @@ module Shoulda
|
|
596
702
|
raise NonCaseSwappableValueError.create(
|
597
703
|
model: model,
|
598
704
|
attribute: @attribute,
|
599
|
-
value: value
|
705
|
+
value: value,
|
600
706
|
)
|
601
707
|
end
|
602
708
|
|
@@ -609,7 +715,32 @@ module Shoulda
|
|
609
715
|
end
|
610
716
|
end
|
611
717
|
|
612
|
-
def
|
718
|
+
def does_not_match_uniqueness_with_case_sensitivity_strategy?
|
719
|
+
if should_test_case_sensitivity?
|
720
|
+
@failure_reason = nil
|
721
|
+
|
722
|
+
value = existing_value_read
|
723
|
+
swapcased_value = value.swapcase
|
724
|
+
|
725
|
+
if case_sensitivity_strategy == :sensitive
|
726
|
+
disallows_value_of(swapcased_value, @expected_message)
|
727
|
+
else
|
728
|
+
if value == swapcased_value
|
729
|
+
raise NonCaseSwappableValueError.create(
|
730
|
+
model: model,
|
731
|
+
attribute: @attribute,
|
732
|
+
value: value,
|
733
|
+
)
|
734
|
+
end
|
735
|
+
|
736
|
+
allows_value_of(swapcased_value, @expected_message)
|
737
|
+
end
|
738
|
+
else
|
739
|
+
true
|
740
|
+
end
|
741
|
+
end
|
742
|
+
|
743
|
+
def should_test_case_sensitivity?
|
613
744
|
case_sensitivity_strategy != :ignore &&
|
614
745
|
existing_value_read.respond_to?(:swapcase) &&
|
615
746
|
!existing_value_read.empty?
|
@@ -621,37 +752,49 @@ module Shoulda
|
|
621
752
|
false
|
622
753
|
end
|
623
754
|
|
624
|
-
def
|
625
|
-
|
626
|
-
|
627
|
-
else
|
755
|
+
def matches_uniqueness_with_scopes?
|
756
|
+
expected_scopes.none? ||
|
757
|
+
all_scopes_are_booleans? ||
|
628
758
|
expected_scopes.all? do |scope|
|
629
|
-
|
759
|
+
setting_next_value_for(scope) do
|
760
|
+
allows_value_of(existing_value_read, @expected_message)
|
761
|
+
end
|
762
|
+
end
|
763
|
+
end
|
630
764
|
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
765
|
+
def does_not_match_uniqueness_with_scopes?
|
766
|
+
expected_scopes.any? &&
|
767
|
+
!all_scopes_are_booleans? &&
|
768
|
+
expected_scopes.any? do |scope|
|
769
|
+
setting_next_value_for(scope) do
|
770
|
+
@failure_reason = nil
|
771
|
+
disallows_value_of(existing_value_read, @expected_message)
|
772
|
+
end
|
773
|
+
end
|
774
|
+
end
|
637
775
|
|
638
|
-
|
776
|
+
def setting_next_value_for(scope)
|
777
|
+
previous_value = @all_records.map(&scope).compact.max
|
639
778
|
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
end
|
779
|
+
next_value =
|
780
|
+
if previous_value.blank?
|
781
|
+
dummy_value_for(scope)
|
782
|
+
else
|
783
|
+
next_value_for(scope, previous_value)
|
646
784
|
end
|
647
|
-
|
785
|
+
|
786
|
+
set_attribute_on_new_record!(scope, next_value)
|
787
|
+
|
788
|
+
yield
|
789
|
+
ensure
|
790
|
+
set_attribute_on_new_record!(scope, previous_value)
|
648
791
|
end
|
649
792
|
|
650
793
|
def dummy_value_for(scope)
|
651
794
|
column = column_for(scope)
|
652
795
|
|
653
796
|
if column.respond_to?(:array) && column.array
|
654
|
-
[
|
797
|
+
[dummy_scalar_value_for(column)]
|
655
798
|
else
|
656
799
|
dummy_scalar_value_for(column)
|
657
800
|
end
|
@@ -663,7 +806,7 @@ module Shoulda
|
|
663
806
|
|
664
807
|
def next_value_for(scope, previous_value)
|
665
808
|
if previous_value.is_a?(Array)
|
666
|
-
[
|
809
|
+
[next_scalar_value_for(scope, previous_value[0])]
|
667
810
|
else
|
668
811
|
next_scalar_value_for(scope, previous_value)
|
669
812
|
end
|
@@ -682,7 +825,7 @@ module Shoulda
|
|
682
825
|
elsif previous_value.respond_to?(:next)
|
683
826
|
previous_value.next
|
684
827
|
elsif previous_value.respond_to?(:to_datetime)
|
685
|
-
previous_value.to_datetime.next
|
828
|
+
previous_value.to_datetime.in(60).next
|
686
829
|
elsif boolean_value?(previous_value)
|
687
830
|
!previous_value
|
688
831
|
else
|
@@ -697,7 +840,7 @@ module Shoulda
|
|
697
840
|
end
|
698
841
|
|
699
842
|
def boolean_value?(value)
|
700
|
-
|
843
|
+
[true, false].include?(value)
|
701
844
|
end
|
702
845
|
|
703
846
|
def defined_as_enum?(scope)
|
@@ -719,7 +862,7 @@ module Shoulda
|
|
719
862
|
attribute_setter = build_attribute_setter(
|
720
863
|
record,
|
721
864
|
attribute_name,
|
722
|
-
value
|
865
|
+
value,
|
723
866
|
)
|
724
867
|
attribute_setter.set!
|
725
868
|
|
@@ -731,7 +874,7 @@ module Shoulda
|
|
731
874
|
:existing_record,
|
732
875
|
existing_record,
|
733
876
|
attribute_name,
|
734
|
-
value
|
877
|
+
value,
|
735
878
|
)
|
736
879
|
end
|
737
880
|
|
@@ -740,7 +883,7 @@ module Shoulda
|
|
740
883
|
:new_record,
|
741
884
|
new_record,
|
742
885
|
attribute_name,
|
743
|
-
value
|
886
|
+
value,
|
744
887
|
)
|
745
888
|
end
|
746
889
|
|
@@ -748,18 +891,24 @@ module Shoulda
|
|
748
891
|
@attribute_setters[:existing_record].last
|
749
892
|
end
|
750
893
|
|
894
|
+
def attribute_setters_for_new_record
|
895
|
+
@attribute_setters[:new_record] +
|
896
|
+
[last_attribute_setter_used_on_new_record]
|
897
|
+
end
|
898
|
+
|
751
899
|
def attribute_names_under_test
|
752
900
|
[@attribute] + expected_scopes
|
753
901
|
end
|
754
902
|
|
755
903
|
def build_attribute_setter(record, attribute_name, value)
|
756
|
-
Shoulda::Matchers::ActiveModel::AllowValueMatcher::AttributeSetter.
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
904
|
+
Shoulda::Matchers::ActiveModel::AllowValueMatcher::AttributeSetter.
|
905
|
+
new(
|
906
|
+
matcher_name: :validate_uniqueness_of,
|
907
|
+
object: record,
|
908
|
+
attribute_name: attribute_name,
|
909
|
+
value: value,
|
910
|
+
ignore_interference_by_writer: ignore_interference_by_writer,
|
911
|
+
)
|
763
912
|
end
|
764
913
|
|
765
914
|
def existing_value_read
|
@@ -786,47 +935,42 @@ module Shoulda
|
|
786
935
|
@given_record.class
|
787
936
|
end
|
788
937
|
|
789
|
-
def failure_message_preface
|
938
|
+
def failure_message_preface # rubocop:disable Metrics/MethodLength
|
790
939
|
prefix = ''
|
791
940
|
|
792
941
|
if @existing_record_created
|
793
942
|
prefix << "After taking the given #{model.name}"
|
794
943
|
|
795
944
|
if attribute_setter_for_existing_record
|
796
|
-
prefix << ', setting
|
945
|
+
prefix << ', setting '
|
797
946
|
prefix << description_for_attribute_setter(
|
798
|
-
attribute_setter_for_existing_record
|
947
|
+
attribute_setter_for_existing_record,
|
799
948
|
)
|
800
949
|
else
|
801
950
|
prefix << ", whose :#{attribute} is "
|
802
951
|
prefix << "‹#{existing_value_read.inspect}›"
|
803
952
|
end
|
804
953
|
|
805
|
-
prefix <<
|
954
|
+
prefix << ', and saving it as the existing record, then'
|
955
|
+
elsif attribute_setter_for_existing_record
|
956
|
+
prefix << "Given an existing #{model.name},"
|
957
|
+
prefix << ' after setting '
|
958
|
+
prefix << description_for_attribute_setter(
|
959
|
+
attribute_setter_for_existing_record,
|
960
|
+
)
|
961
|
+
prefix << ', then'
|
806
962
|
else
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
prefix << ', then'
|
814
|
-
else
|
815
|
-
prefix << "Given an existing #{model.name} whose :#{attribute}"
|
816
|
-
prefix << ' is '
|
817
|
-
prefix << Shoulda::Matchers::Util.inspect_value(
|
818
|
-
existing_value_read
|
819
|
-
)
|
820
|
-
prefix << ', after'
|
821
|
-
end
|
963
|
+
prefix << "Given an existing #{model.name} whose :#{attribute}"
|
964
|
+
prefix << ' is '
|
965
|
+
prefix << Shoulda::Matchers::Util.inspect_value(
|
966
|
+
existing_value_read,
|
967
|
+
)
|
968
|
+
prefix << ', after'
|
822
969
|
end
|
823
970
|
|
824
|
-
prefix << " making a new #{model.name} and setting
|
971
|
+
prefix << " making a new #{model.name} and setting "
|
825
972
|
|
826
|
-
prefix <<
|
827
|
-
last_attribute_setter_used_on_new_record,
|
828
|
-
same_as_existing: existing_and_new_values_are_same?
|
829
|
-
)
|
973
|
+
prefix << descriptions_for_attribute_setters_for_new_record
|
830
974
|
|
831
975
|
prefix << ", the matcher expected the new #{model.name} to be"
|
832
976
|
|
@@ -848,21 +992,24 @@ different altogether.
|
|
848
992
|
MESSAGE
|
849
993
|
end
|
850
994
|
|
851
|
-
def description_for_attribute_setter(
|
852
|
-
|
995
|
+
def description_for_attribute_setter(
|
996
|
+
attribute_setter,
|
997
|
+
same_as_existing: nil
|
998
|
+
)
|
999
|
+
description = "its :#{attribute_setter.attribute_name} to "
|
853
1000
|
|
854
1001
|
if same_as_existing == false
|
855
1002
|
description << 'a different value, '
|
856
1003
|
end
|
857
1004
|
|
858
1005
|
description << Shoulda::Matchers::Util.inspect_value(
|
859
|
-
attribute_setter.value_written
|
1006
|
+
attribute_setter.value_written,
|
860
1007
|
)
|
861
1008
|
|
862
1009
|
if attribute_setter.attribute_changed_value?
|
863
1010
|
description << ' (read back as '
|
864
1011
|
description << Shoulda::Matchers::Util.inspect_value(
|
865
|
-
attribute_setter.value_read
|
1012
|
+
attribute_setter.value_read,
|
866
1013
|
)
|
867
1014
|
description << ')'
|
868
1015
|
end
|
@@ -874,6 +1021,23 @@ different altogether.
|
|
874
1021
|
description
|
875
1022
|
end
|
876
1023
|
|
1024
|
+
def descriptions_for_attribute_setters_for_new_record
|
1025
|
+
attribute_setter_descriptions_for_new_record.to_sentence
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
def attribute_setter_descriptions_for_new_record
|
1029
|
+
attribute_setters_for_new_record.map do |attribute_setter|
|
1030
|
+
same_as_existing = (
|
1031
|
+
attribute_setter.value_written ==
|
1032
|
+
existing_value_written
|
1033
|
+
)
|
1034
|
+
description_for_attribute_setter(
|
1035
|
+
attribute_setter,
|
1036
|
+
same_as_existing: same_as_existing,
|
1037
|
+
)
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
877
1041
|
def existing_and_new_values_are_same?
|
878
1042
|
last_value_set_on_new_record == existing_value_written
|
879
1043
|
end
|
@@ -886,6 +1050,50 @@ different altogether.
|
|
886
1050
|
last_submatcher_run.last_value_set
|
887
1051
|
end
|
888
1052
|
|
1053
|
+
# @private
|
1054
|
+
class AttributeSetters
|
1055
|
+
include Enumerable
|
1056
|
+
|
1057
|
+
def initialize
|
1058
|
+
@attribute_setters = []
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
def <<(given_attribute_setter)
|
1062
|
+
index = find_index_of(given_attribute_setter)
|
1063
|
+
|
1064
|
+
if index
|
1065
|
+
@attribute_setters[index] = given_attribute_setter
|
1066
|
+
else
|
1067
|
+
@attribute_setters << given_attribute_setter
|
1068
|
+
end
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
def +(other_attribute_setters)
|
1072
|
+
dup.tap do |attribute_setters|
|
1073
|
+
other_attribute_setters.each do |attribute_setter|
|
1074
|
+
attribute_setters << attribute_setter
|
1075
|
+
end
|
1076
|
+
end
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
def each(&block)
|
1080
|
+
@attribute_setters.each(&block)
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
def last
|
1084
|
+
@attribute_setters.last
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
private
|
1088
|
+
|
1089
|
+
def find_index_of(given_attribute_setter)
|
1090
|
+
@attribute_setters.find_index do |attribute_setter|
|
1091
|
+
attribute_setter.attribute_name ==
|
1092
|
+
given_attribute_setter.attribute_name
|
1093
|
+
end
|
1094
|
+
end
|
1095
|
+
end
|
1096
|
+
|
889
1097
|
# @private
|
890
1098
|
class NonCaseSwappableValueError < Shoulda::Matchers::Error
|
891
1099
|
attr_accessor :model, :attribute, :value
|
@@ -895,7 +1103,7 @@ different altogether.
|
|
895
1103
|
Your #{model.name} model has a uniqueness validation on :#{attribute} which is
|
896
1104
|
declared to be case-sensitive, but the value the uniqueness matcher used,
|
897
1105
|
#{value.inspect}, doesn't contain any alpha characters, so using it to
|
898
|
-
|
1106
|
+
test the case-sensitivity part of the validation is ineffective. There are
|
899
1107
|
two possible solutions for this depending on what you're trying to do here:
|
900
1108
|
|
901
1109
|
a) If you meant for the validation to be case-sensitive, then you need to give
|
@@ -908,7 +1116,29 @@ b) If you meant for the validation to be case-insensitive, then you need to
|
|
908
1116
|
|
909
1117
|
For more information, please see:
|
910
1118
|
|
911
|
-
|
1119
|
+
https://matchers.shoulda.io/docs/v#{Shoulda::Matchers::VERSION}/file.NonCaseSwappableValueError.html
|
1120
|
+
MESSAGE
|
1121
|
+
end
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
# @private
|
1125
|
+
class ExistingRecordInvalid < Shoulda::Matchers::Error
|
1126
|
+
include Shoulda::Matchers::ActiveModel::Helpers
|
1127
|
+
|
1128
|
+
attr_accessor :underlying_exception
|
1129
|
+
|
1130
|
+
def message
|
1131
|
+
<<-MESSAGE.strip
|
1132
|
+
validate_uniqueness_of works by matching a new record against an
|
1133
|
+
existing record. If there is no existing record, it will create one
|
1134
|
+
using the record you provide.
|
1135
|
+
|
1136
|
+
While doing this, the following error was raised:
|
1137
|
+
|
1138
|
+
#{Shoulda::Matchers::Util.indent(underlying_exception.message, 2)}
|
1139
|
+
|
1140
|
+
The best way to fix this is to provide the matcher with a record where
|
1141
|
+
any required attributes are filled in with valid values beforehand.
|
912
1142
|
MESSAGE
|
913
1143
|
end
|
914
1144
|
end
|