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
@@ -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
|