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
@@ -1,1418 +0,0 @@
|
|
1
|
-
require 'unit_spec_helper'
|
2
|
-
|
3
|
-
describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :model do
|
4
|
-
shared_context 'it supports scoped attributes of a certain type' do |options = {}|
|
5
|
-
column_type = options.fetch(:column_type)
|
6
|
-
value_type = options.fetch(:value_type, column_type)
|
7
|
-
array = options.fetch(:array, false)
|
8
|
-
|
9
|
-
context 'when the correct scope is specified' do
|
10
|
-
context 'when the subject is a new record' do
|
11
|
-
it 'accepts' do
|
12
|
-
record = build_record_validating_uniqueness(
|
13
|
-
scopes: [
|
14
|
-
build_attribute(name: :scope1),
|
15
|
-
{ name: :scope2 }
|
16
|
-
]
|
17
|
-
)
|
18
|
-
expect(record).to validate_uniqueness.scoped_to(:scope1, :scope2)
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'still accepts if the scope is unset beforehand' do
|
22
|
-
record = build_record_validating_uniqueness(
|
23
|
-
scopes: [ build_attribute(name: :scope, value: nil) ]
|
24
|
-
)
|
25
|
-
|
26
|
-
expect(record).to validate_uniqueness.scoped_to(:scope)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
context 'when the subject is an existing record' do
|
31
|
-
it 'accepts' do
|
32
|
-
record = create_record_validating_uniqueness(
|
33
|
-
scopes: [
|
34
|
-
build_attribute(name: :scope1),
|
35
|
-
{ name: :scope2 }
|
36
|
-
]
|
37
|
-
)
|
38
|
-
|
39
|
-
expect(record).to validate_uniqueness.scoped_to(:scope1, :scope2)
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'still accepts if the scope is unset beforehand' do
|
43
|
-
record = create_record_validating_uniqueness(
|
44
|
-
scopes: [ build_attribute(name: :scope, value: nil) ]
|
45
|
-
)
|
46
|
-
|
47
|
-
expect(record).to validate_uniqueness.scoped_to(:scope)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
context "when more than one record exists that has the next version of the attribute's value" do
|
53
|
-
it 'accepts' do
|
54
|
-
value1 = dummy_value_for(value_type, array: array)
|
55
|
-
value2 = next_version_of(value1, value_type)
|
56
|
-
value3 = next_version_of(value2, value_type)
|
57
|
-
model = define_model_validating_uniqueness(
|
58
|
-
scopes: [ build_attribute(name: :scope) ]
|
59
|
-
)
|
60
|
-
create_record_from(model, scope: value2)
|
61
|
-
create_record_from(model, scope: value3)
|
62
|
-
record = build_record_from(model, scope: value1)
|
63
|
-
|
64
|
-
expect(record).to validate_uniqueness.scoped_to(:scope)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
context 'when too narrow of a scope is specified' do
|
69
|
-
it 'rejects with an appropriate failure message' do
|
70
|
-
record = build_record_validating_uniqueness(
|
71
|
-
scopes: [
|
72
|
-
build_attribute(name: :scope1),
|
73
|
-
build_attribute(name: :scope2)
|
74
|
-
],
|
75
|
-
additional_attributes: [:other]
|
76
|
-
)
|
77
|
-
|
78
|
-
assertion = lambda do
|
79
|
-
expect(record).
|
80
|
-
to validate_uniqueness.
|
81
|
-
scoped_to(:scope1, :scope2, :other)
|
82
|
-
end
|
83
|
-
|
84
|
-
message = <<-MESSAGE
|
85
|
-
Example did not properly validate that :attr is case-sensitively unique
|
86
|
-
within the scope of :scope1, :scope2, and :other.
|
87
|
-
Expected the validation to be scoped to :scope1, :scope2, and :other,
|
88
|
-
but it was scoped to :scope1 and :scope2 instead.
|
89
|
-
MESSAGE
|
90
|
-
|
91
|
-
expect(&assertion).to fail_with_message(message)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
context 'when too broad of a scope is specified' do
|
96
|
-
it 'rejects with an appropriate failure message' do
|
97
|
-
record = build_record_validating_uniqueness(
|
98
|
-
scopes: [
|
99
|
-
build_attribute(name: :scope1),
|
100
|
-
build_attribute(name: :scope2)
|
101
|
-
],
|
102
|
-
)
|
103
|
-
|
104
|
-
assertion = lambda do
|
105
|
-
expect(record).
|
106
|
-
to validate_uniqueness.
|
107
|
-
scoped_to(:scope1)
|
108
|
-
end
|
109
|
-
|
110
|
-
message = <<-MESSAGE
|
111
|
-
Example did not properly validate that :attr is case-sensitively unique
|
112
|
-
within the scope of :scope1.
|
113
|
-
Expected the validation to be scoped to :scope1, but it was scoped to
|
114
|
-
:scope1 and :scope2 instead.
|
115
|
-
MESSAGE
|
116
|
-
|
117
|
-
expect(&assertion).to fail_with_message(message)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
context 'when a different scope is specified' do
|
122
|
-
it 'rejects with an appropriate failure message' do
|
123
|
-
record = build_record_validating_uniqueness(
|
124
|
-
scopes: [ build_attribute(name: :other) ],
|
125
|
-
additional_attributes: [:scope]
|
126
|
-
)
|
127
|
-
assertion = lambda do
|
128
|
-
expect(record).
|
129
|
-
to validate_uniqueness.
|
130
|
-
scoped_to(:scope)
|
131
|
-
end
|
132
|
-
|
133
|
-
message = <<-MESSAGE
|
134
|
-
Example did not properly validate that :attr is case-sensitively unique
|
135
|
-
within the scope of :scope.
|
136
|
-
Expected the validation to be scoped to :scope, but it was scoped to
|
137
|
-
:other instead.
|
138
|
-
MESSAGE
|
139
|
-
|
140
|
-
expect(&assertion).to fail_with_message(message)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
context 'when no scope is specified' do
|
145
|
-
it 'rejects with an appropriate failure message' do
|
146
|
-
record = build_record_validating_uniqueness(
|
147
|
-
scopes: [ build_attribute(name: :scope) ]
|
148
|
-
)
|
149
|
-
|
150
|
-
assertion = lambda do
|
151
|
-
expect(record).to validate_uniqueness
|
152
|
-
end
|
153
|
-
|
154
|
-
message = <<-MESSAGE
|
155
|
-
Example did not properly validate that :attr is case-sensitively unique.
|
156
|
-
Expected the validation not to be scoped to anything, but it was
|
157
|
-
scoped to :scope instead.
|
158
|
-
MESSAGE
|
159
|
-
|
160
|
-
expect(&assertion).to fail_with_message(message)
|
161
|
-
end
|
162
|
-
|
163
|
-
context 'if the scope attribute is unset in the record given to the matcher' do
|
164
|
-
it 'rejects with an appropriate failure message' do
|
165
|
-
record = build_record_validating_uniqueness(
|
166
|
-
scopes: [ build_attribute(name: :scope, value: nil) ]
|
167
|
-
)
|
168
|
-
|
169
|
-
assertion = lambda do
|
170
|
-
expect(record).to validate_uniqueness
|
171
|
-
end
|
172
|
-
|
173
|
-
message = <<-MESSAGE
|
174
|
-
Example did not properly validate that :attr is case-sensitively unique.
|
175
|
-
Expected the validation not to be scoped to anything, but it was
|
176
|
-
scoped to :scope instead.
|
177
|
-
MESSAGE
|
178
|
-
|
179
|
-
expect(&assertion).to fail_with_message(message)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
context 'when a non-existent attribute is specified as a scope' do
|
185
|
-
context 'when there is more than one scope' do
|
186
|
-
it 'rejects with an appropriate failure message (and does not raise an error)' do
|
187
|
-
record = build_record_validating_uniqueness(
|
188
|
-
scopes: [ build_attribute(name: :scope) ]
|
189
|
-
)
|
190
|
-
|
191
|
-
assertion = lambda do
|
192
|
-
expect(record).to validate_uniqueness.scoped_to(:non_existent)
|
193
|
-
end
|
194
|
-
|
195
|
-
message = <<-MESSAGE.strip
|
196
|
-
Example did not properly validate that :attr is case-sensitively unique
|
197
|
-
within the scope of :non_existent.
|
198
|
-
:non_existent does not seem to be an attribute on Example.
|
199
|
-
MESSAGE
|
200
|
-
|
201
|
-
expect(&assertion).to fail_with_message(message)
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
context 'when there is more than one scope' do
|
206
|
-
it 'rejects with an appropriate failure message (and does not raise an error)' do
|
207
|
-
record = build_record_validating_uniqueness(
|
208
|
-
scopes: [ build_attribute(name: :scope) ]
|
209
|
-
)
|
210
|
-
|
211
|
-
assertion = lambda do
|
212
|
-
expect(record).to validate_uniqueness.scoped_to(
|
213
|
-
:non_existent1,
|
214
|
-
:non_existent2
|
215
|
-
)
|
216
|
-
end
|
217
|
-
|
218
|
-
message = <<-MESSAGE.strip
|
219
|
-
Example did not properly validate that :attr is case-sensitively unique
|
220
|
-
within the scope of :non_existent1 and :non_existent2.
|
221
|
-
:non_existent1 and :non_existent2 do not seem to be attributes on
|
222
|
-
Example.
|
223
|
-
MESSAGE
|
224
|
-
|
225
|
-
expect(&assertion).to fail_with_message(message)
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
define_method(:build_attribute) do |attribute_options|
|
231
|
-
attribute_options.deep_merge(
|
232
|
-
column_type: column_type,
|
233
|
-
value_type: value_type,
|
234
|
-
options: { array: array }
|
235
|
-
)
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
context 'when the model does not have a uniqueness validation' do
|
240
|
-
it 'rejects with an appropriate failure message' do
|
241
|
-
model = define_model_without_validation
|
242
|
-
model.create!(attribute_name => 'value')
|
243
|
-
|
244
|
-
assertion = lambda do
|
245
|
-
expect(model.new).to validate_uniqueness_of(attribute_name)
|
246
|
-
end
|
247
|
-
|
248
|
-
message = <<-MESSAGE
|
249
|
-
Example did not properly validate that :attr is case-sensitively unique.
|
250
|
-
Given an existing Example whose :attr is ‹"value"›, after making a new
|
251
|
-
Example and setting its :attr to ‹"value"› as well, the matcher
|
252
|
-
expected the new Example to be invalid, but it was valid instead.
|
253
|
-
MESSAGE
|
254
|
-
|
255
|
-
expect(&assertion).to fail_with_message(message)
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
context 'when the model has a uniqueness validation' do
|
260
|
-
context 'when the attribute has a character limit' do
|
261
|
-
it 'accepts' do
|
262
|
-
record = build_record_validating_uniqueness(
|
263
|
-
attribute_type: :string,
|
264
|
-
attribute_options: { limit: 1 }
|
265
|
-
)
|
266
|
-
|
267
|
-
expect(record).to validate_uniqueness
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
context 'when the record is created beforehand' do
|
272
|
-
context 'when the subject is a new record' do
|
273
|
-
it 'accepts' do
|
274
|
-
create_record_validating_uniqueness
|
275
|
-
expect(new_record_validating_uniqueness).
|
276
|
-
to validate_uniqueness
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
context 'when the subject is an existing record' do
|
281
|
-
it 'accepts' do
|
282
|
-
expect(existing_record_validating_uniqueness).to validate_uniqueness
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
context 'when the validation has no scope and a scope is specified' do
|
287
|
-
it 'rejects with an appropriate failure message' do
|
288
|
-
model = define_model_validating_uniqueness(
|
289
|
-
additional_attributes: [:other]
|
290
|
-
)
|
291
|
-
create_record_from(model)
|
292
|
-
record = build_record_from(model)
|
293
|
-
|
294
|
-
assertion = lambda do
|
295
|
-
expect(record).to validate_uniqueness.scoped_to(:other)
|
296
|
-
end
|
297
|
-
|
298
|
-
message = <<-MESSAGE
|
299
|
-
Example did not properly validate that :attr is case-sensitively unique
|
300
|
-
within the scope of :other.
|
301
|
-
Expected the validation to be scoped to :other, but it was not scoped
|
302
|
-
to anything.
|
303
|
-
MESSAGE
|
304
|
-
|
305
|
-
expect(&assertion).to fail_with_message(message)
|
306
|
-
end
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
context 'when the record is not created beforehand' do
|
311
|
-
it 'creates the record automatically' do
|
312
|
-
model = define_model_validating_uniqueness
|
313
|
-
assertion = -> {
|
314
|
-
record = build_record_from(model)
|
315
|
-
expect(record).to validate_uniqueness
|
316
|
-
}
|
317
|
-
expect(&assertion).to change(model, :count).from(0).to(1)
|
318
|
-
end
|
319
|
-
|
320
|
-
context 'and the table has required attributes other than the attribute being validated, set beforehand' do
|
321
|
-
it 'does not require the record to be persisted' do
|
322
|
-
options = {
|
323
|
-
additional_attributes: [
|
324
|
-
{ name: :required_attribute, options: { null: false } }
|
325
|
-
]
|
326
|
-
}
|
327
|
-
model = define_model_validating_uniqueness(options) do |m|
|
328
|
-
m.validates_presence_of :required_attribute
|
329
|
-
end
|
330
|
-
|
331
|
-
record = build_record_from(model, required_attribute: 'something')
|
332
|
-
expect(record).to validate_uniqueness
|
333
|
-
end
|
334
|
-
end
|
335
|
-
end
|
336
|
-
|
337
|
-
context 'and the validation has a custom message' do
|
338
|
-
context 'when no message is specified' do
|
339
|
-
it 'rejects with an appropriate failure message' do
|
340
|
-
record = build_record_validating_uniqueness(
|
341
|
-
attribute_value: 'some value',
|
342
|
-
validation_options: { message: 'bad value' }
|
343
|
-
)
|
344
|
-
|
345
|
-
assertion = lambda do
|
346
|
-
expect(record).to validate_uniqueness
|
347
|
-
end
|
348
|
-
|
349
|
-
message = <<-MESSAGE
|
350
|
-
Example did not properly validate that :attr is case-sensitively unique.
|
351
|
-
After taking the given Example, whose :attr is ‹"some value"›, and
|
352
|
-
saving it as the existing record, then making a new Example and
|
353
|
-
setting its :attr to ‹"some value"› as well, the matcher expected the
|
354
|
-
new Example to be invalid and to produce the validation error "has
|
355
|
-
already been taken" on :attr. The record was indeed invalid, but it
|
356
|
-
produced these validation errors instead:
|
357
|
-
|
358
|
-
* attr: ["bad value"]
|
359
|
-
MESSAGE
|
360
|
-
|
361
|
-
expect(&assertion).to fail_with_message(message)
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
context 'given a string' do
|
366
|
-
context 'when the given and actual messages do not match' do
|
367
|
-
it 'rejects with an appropriate failure message' do
|
368
|
-
record = build_record_validating_uniqueness(
|
369
|
-
attribute_value: 'some value',
|
370
|
-
validation_options: { message: 'something else entirely' }
|
371
|
-
)
|
372
|
-
|
373
|
-
assertion = lambda do
|
374
|
-
expect(record).
|
375
|
-
to validate_uniqueness.
|
376
|
-
with_message('some message')
|
377
|
-
end
|
378
|
-
|
379
|
-
message = <<-MESSAGE
|
380
|
-
Example did not properly validate that :attr is case-sensitively unique,
|
381
|
-
producing a custom validation error on failure.
|
382
|
-
After taking the given Example, whose :attr is ‹"some value"›, and
|
383
|
-
saving it as the existing record, then making a new Example and
|
384
|
-
setting its :attr to ‹"some value"› as well, the matcher expected the
|
385
|
-
new Example to be invalid and to produce the validation error "some
|
386
|
-
message" on :attr. The record was indeed invalid, but it produced
|
387
|
-
these validation errors instead:
|
388
|
-
|
389
|
-
* attr: ["something else entirely"]
|
390
|
-
MESSAGE
|
391
|
-
|
392
|
-
expect(&assertion).to fail_with_message(message)
|
393
|
-
end
|
394
|
-
end
|
395
|
-
|
396
|
-
context 'when the given and actual messages match' do
|
397
|
-
it 'accepts' do
|
398
|
-
record = build_record_validating_uniqueness(
|
399
|
-
validation_options: { message: 'bad value' }
|
400
|
-
)
|
401
|
-
expect(record).
|
402
|
-
to validate_uniqueness.
|
403
|
-
with_message('bad value')
|
404
|
-
end
|
405
|
-
end
|
406
|
-
end
|
407
|
-
|
408
|
-
context 'given a regex' do
|
409
|
-
context 'when the given and actual messages do not match' do
|
410
|
-
it 'rejects with an appropriate failure message' do
|
411
|
-
record = build_record_validating_uniqueness(
|
412
|
-
attribute_value: 'some value',
|
413
|
-
validation_options: { message: 'something else entirely' }
|
414
|
-
)
|
415
|
-
|
416
|
-
assertion = lambda do
|
417
|
-
expect(record).
|
418
|
-
to validate_uniqueness.
|
419
|
-
with_message(/some message/)
|
420
|
-
end
|
421
|
-
|
422
|
-
message = <<-MESSAGE
|
423
|
-
Example did not properly validate that :attr is case-sensitively unique,
|
424
|
-
producing a custom validation error on failure.
|
425
|
-
After taking the given Example, whose :attr is ‹"some value"›, and
|
426
|
-
saving it as the existing record, then making a new Example and
|
427
|
-
setting its :attr to ‹"some value"› as well, the matcher expected the
|
428
|
-
new Example to be invalid and to produce a validation error matching
|
429
|
-
‹/some message/› on :attr. The record was indeed invalid, but it
|
430
|
-
produced these validation errors instead:
|
431
|
-
|
432
|
-
* attr: ["something else entirely"]
|
433
|
-
MESSAGE
|
434
|
-
|
435
|
-
expect(&assertion).to fail_with_message(message)
|
436
|
-
end
|
437
|
-
end
|
438
|
-
|
439
|
-
context 'when the given and actual messages match' do
|
440
|
-
it 'accepts' do
|
441
|
-
record = build_record_validating_uniqueness(
|
442
|
-
validation_options: { message: 'bad value' }
|
443
|
-
)
|
444
|
-
expect(record).
|
445
|
-
to validate_uniqueness.
|
446
|
-
with_message(/bad/)
|
447
|
-
end
|
448
|
-
end
|
449
|
-
end
|
450
|
-
end
|
451
|
-
|
452
|
-
it_supports(
|
453
|
-
'ignoring_interference_by_writer',
|
454
|
-
tests: {
|
455
|
-
reject_if_qualified_but_changing_value_interferes: {
|
456
|
-
model_name: 'Example',
|
457
|
-
attribute_name: :attr,
|
458
|
-
default_value: 'some value',
|
459
|
-
changing_values_with: :next_value,
|
460
|
-
expected_message: <<-MESSAGE.strip
|
461
|
-
Example did not properly validate that :attr is case-sensitively unique.
|
462
|
-
After taking the given Example, whose :attr is ‹"some valuf"›, and
|
463
|
-
saving it as the existing record, then making a new Example and
|
464
|
-
setting its :attr to ‹"some valuf"› (read back as ‹"some valug"›) as
|
465
|
-
well, the matcher expected the new Example to be invalid, but it was
|
466
|
-
valid instead.
|
467
|
-
|
468
|
-
As indicated in the message above, :attr seems to be changing certain
|
469
|
-
values as they are set, and this could have something to do with why
|
470
|
-
this test is failing. If you or something else has overridden the
|
471
|
-
writer method for this attribute to normalize values by changing their
|
472
|
-
case in any way (for instance, ensuring that the attribute is always
|
473
|
-
downcased), then try adding `ignoring_case_sensitivity` onto the end
|
474
|
-
of the uniqueness matcher. Otherwise, you may need to write the test
|
475
|
-
yourself, or do something different altogether.
|
476
|
-
|
477
|
-
MESSAGE
|
478
|
-
}
|
479
|
-
}
|
480
|
-
)
|
481
|
-
end
|
482
|
-
|
483
|
-
context 'when the model has a scoped uniqueness validation' do
|
484
|
-
context 'when one of the scoped attributes is a string column' do
|
485
|
-
include_context 'it supports scoped attributes of a certain type',
|
486
|
-
column_type: :string
|
487
|
-
end
|
488
|
-
|
489
|
-
context 'when one of the scoped attributes is a boolean column' do
|
490
|
-
include_context 'it supports scoped attributes of a certain type',
|
491
|
-
column_type: :boolean
|
492
|
-
end
|
493
|
-
|
494
|
-
context 'when there is more than one scoped attribute and all are boolean columns' do
|
495
|
-
it 'accepts when all of the scoped attributes are true' do
|
496
|
-
record = build_record_validating_uniqueness(
|
497
|
-
scopes: [
|
498
|
-
{ type: :boolean, name: :scope1, value: true },
|
499
|
-
{ type: :boolean, name: :scope2, value: true }
|
500
|
-
]
|
501
|
-
)
|
502
|
-
expect(record).to validate_uniqueness.scoped_to(:scope1, :scope2)
|
503
|
-
end
|
504
|
-
|
505
|
-
it 'accepts when all the scoped attributes are false' do
|
506
|
-
record = build_record_validating_uniqueness(
|
507
|
-
scopes: [
|
508
|
-
{ type: :boolean, name: :scope1, value: false },
|
509
|
-
{ type: :boolean, name: :scope2, value: false }
|
510
|
-
]
|
511
|
-
)
|
512
|
-
expect(record).to validate_uniqueness.scoped_to(:scope1, :scope2)
|
513
|
-
end
|
514
|
-
|
515
|
-
it 'accepts when one of the scoped attributes is true and the other is false' do
|
516
|
-
record = build_record_validating_uniqueness(
|
517
|
-
scopes: [
|
518
|
-
{ type: :boolean, name: :scope1, value: true },
|
519
|
-
{ type: :boolean, name: :scope2, value: false }
|
520
|
-
]
|
521
|
-
)
|
522
|
-
expect(record).to validate_uniqueness.scoped_to(:scope1, :scope2)
|
523
|
-
end
|
524
|
-
end
|
525
|
-
|
526
|
-
context 'when one of the scoped attributes is an integer column' do
|
527
|
-
include_context 'it supports scoped attributes of a certain type',
|
528
|
-
column_type: :integer
|
529
|
-
|
530
|
-
if active_record_supports_enum?
|
531
|
-
context 'when one of the scoped attributes is an enum' do
|
532
|
-
it 'accepts' do
|
533
|
-
record = build_record_validating_scoped_uniqueness_with_enum(
|
534
|
-
enum_scope: :scope
|
535
|
-
)
|
536
|
-
expect(record).to validate_uniqueness.scoped_to(:scope)
|
537
|
-
end
|
538
|
-
|
539
|
-
context 'when too narrow of a scope is specified' do
|
540
|
-
it 'rejects with an appropriate failure message' do
|
541
|
-
record = build_record_validating_scoped_uniqueness_with_enum(
|
542
|
-
enum_scope: :scope1,
|
543
|
-
additional_scopes: [:scope2],
|
544
|
-
additional_attributes: [:other]
|
545
|
-
)
|
546
|
-
|
547
|
-
assertion = lambda do
|
548
|
-
expect(record).
|
549
|
-
to validate_uniqueness.
|
550
|
-
scoped_to(:scope1, :scope2, :other)
|
551
|
-
end
|
552
|
-
|
553
|
-
message = <<-MESSAGE
|
554
|
-
Example did not properly validate that :attr is case-sensitively unique
|
555
|
-
within the scope of :scope1, :scope2, and :other.
|
556
|
-
Expected the validation to be scoped to :scope1, :scope2, and :other,
|
557
|
-
but it was scoped to :scope1 and :scope2 instead.
|
558
|
-
MESSAGE
|
559
|
-
|
560
|
-
expect(&assertion).to fail_with_message(message)
|
561
|
-
end
|
562
|
-
end
|
563
|
-
|
564
|
-
context 'when too broad of a scope is specified' do
|
565
|
-
it 'rejects with an appropriate failure message' do
|
566
|
-
record = build_record_validating_scoped_uniqueness_with_enum(
|
567
|
-
enum_scope: :scope1,
|
568
|
-
additional_scopes: [:scope2]
|
569
|
-
)
|
570
|
-
|
571
|
-
assertion = lambda do
|
572
|
-
expect(record).to validate_uniqueness.scoped_to(:scope1)
|
573
|
-
end
|
574
|
-
|
575
|
-
message = <<-MESSAGE
|
576
|
-
Example did not properly validate that :attr is case-sensitively unique
|
577
|
-
within the scope of :scope1.
|
578
|
-
Expected the validation to be scoped to :scope1, but it was scoped to
|
579
|
-
:scope1 and :scope2 instead.
|
580
|
-
MESSAGE
|
581
|
-
|
582
|
-
expect(&assertion).to fail_with_message(message)
|
583
|
-
end
|
584
|
-
end
|
585
|
-
end
|
586
|
-
end
|
587
|
-
end
|
588
|
-
|
589
|
-
context 'when one of the scoped attributes is a date column' do
|
590
|
-
include_context 'it supports scoped attributes of a certain type',
|
591
|
-
column_type: :date
|
592
|
-
end
|
593
|
-
|
594
|
-
context 'when one of the scoped attributes is a datetime column (using DateTime)' do
|
595
|
-
include_context 'it supports scoped attributes of a certain type',
|
596
|
-
column_type: :datetime
|
597
|
-
end
|
598
|
-
|
599
|
-
context 'when one of the scoped attributes is a datetime column (using Time)' do
|
600
|
-
include_context 'it supports scoped attributes of a certain type',
|
601
|
-
column_type: :datetime,
|
602
|
-
value_type: :time
|
603
|
-
end
|
604
|
-
|
605
|
-
context 'when one of the scoped attributes is a text column' do
|
606
|
-
include_context 'it supports scoped attributes of a certain type',
|
607
|
-
column_type: :text
|
608
|
-
end
|
609
|
-
|
610
|
-
if database_supports_uuid_columns?
|
611
|
-
context 'when one of the scoped attributes is a UUID column' do
|
612
|
-
include_context 'it supports scoped attributes of a certain type',
|
613
|
-
column_type: :uuid
|
614
|
-
end
|
615
|
-
end
|
616
|
-
|
617
|
-
if database_supports_array_columns? && active_record_supports_array_columns?
|
618
|
-
context 'when one of the scoped attributes is a array-of-string column' do
|
619
|
-
include_examples 'it supports scoped attributes of a certain type',
|
620
|
-
column_type: :string,
|
621
|
-
array: true
|
622
|
-
end
|
623
|
-
|
624
|
-
context 'when one of the scoped attributes is an array-of-integer column' do
|
625
|
-
include_examples 'it supports scoped attributes of a certain type',
|
626
|
-
column_type: :integer,
|
627
|
-
array: true
|
628
|
-
end
|
629
|
-
|
630
|
-
context 'when one of the scoped attributes is an array-of-date column' do
|
631
|
-
include_examples 'it supports scoped attributes of a certain type',
|
632
|
-
column_type: :date,
|
633
|
-
array: true
|
634
|
-
end
|
635
|
-
|
636
|
-
context 'when one of the scoped attributes is an array-of-datetime column (using DateTime)' do
|
637
|
-
include_examples 'it supports scoped attributes of a certain type',
|
638
|
-
column_type: :datetime,
|
639
|
-
array: true
|
640
|
-
end
|
641
|
-
|
642
|
-
context 'when one of the scoped attributes is an array-of-datetime column (using Time)' do
|
643
|
-
include_examples 'it supports scoped attributes of a certain type',
|
644
|
-
column_type: :datetime,
|
645
|
-
value_type: :time,
|
646
|
-
array: true
|
647
|
-
end
|
648
|
-
|
649
|
-
context 'when one of the scoped attributes is an array-of-text column' do
|
650
|
-
include_examples 'it supports scoped attributes of a certain type',
|
651
|
-
column_type: :text,
|
652
|
-
array: true
|
653
|
-
end
|
654
|
-
end
|
655
|
-
|
656
|
-
context "when an existing record that is not the first has a nil value for the scoped attribute" do
|
657
|
-
it 'still works' do
|
658
|
-
model = define_model_validating_uniqueness(scopes: [:scope])
|
659
|
-
create_record_from(model, scope: 'some value')
|
660
|
-
create_record_from(model, scope: nil)
|
661
|
-
record = build_record_from(model, scope: 'a different value')
|
662
|
-
|
663
|
-
expect(record).to validate_uniqueness.scoped_to(:scope)
|
664
|
-
end
|
665
|
-
end
|
666
|
-
end
|
667
|
-
|
668
|
-
context 'when the model has a case-sensitive validation' do
|
669
|
-
context 'when the matcher is not qualified with case_insensitive' do
|
670
|
-
it 'accepts' do
|
671
|
-
record = build_record_validating_uniqueness(
|
672
|
-
attribute_type: :string,
|
673
|
-
validation_options: { case_sensitive: true }
|
674
|
-
)
|
675
|
-
|
676
|
-
expect(record).to validate_uniqueness
|
677
|
-
end
|
678
|
-
|
679
|
-
context 'given an existing record where the value of the attribute under test is not case-swappable' do
|
680
|
-
it 'raises a NonCaseSwappableValueError' do
|
681
|
-
model = define_model_validating_uniqueness(
|
682
|
-
attribute_type: :string,
|
683
|
-
validation_options: { case_sensitive: true },
|
684
|
-
)
|
685
|
-
record = create_record_from(model, attribute_name => '123')
|
686
|
-
running_matcher = -> { validate_uniqueness.matches?(record) }
|
687
|
-
|
688
|
-
expect(&running_matcher).
|
689
|
-
to raise_error(described_class::NonCaseSwappableValueError)
|
690
|
-
end
|
691
|
-
end
|
692
|
-
end
|
693
|
-
|
694
|
-
context 'when the matcher is qualified with case_insensitive' do
|
695
|
-
it 'rejects with an appropriate failure message' do
|
696
|
-
record = build_record_validating_uniqueness(
|
697
|
-
attribute_type: :string,
|
698
|
-
attribute_value: 'some value',
|
699
|
-
validation_options: { case_sensitive: true }
|
700
|
-
)
|
701
|
-
|
702
|
-
assertion = lambda do
|
703
|
-
expect(record).to validate_uniqueness.case_insensitive
|
704
|
-
end
|
705
|
-
|
706
|
-
message = <<-MESSAGE
|
707
|
-
Example did not properly validate that :attr is case-insensitively
|
708
|
-
unique.
|
709
|
-
After taking the given Example, whose :attr is ‹"some value"›, and
|
710
|
-
saving it as the existing record, then making a new Example and
|
711
|
-
setting its :attr to a different value, ‹"SOME VALUE"›, the matcher
|
712
|
-
expected the new Example to be invalid, but it was valid instead.
|
713
|
-
MESSAGE
|
714
|
-
|
715
|
-
expect(&assertion).to fail_with_message(message)
|
716
|
-
end
|
717
|
-
end
|
718
|
-
end
|
719
|
-
|
720
|
-
context 'when the model has a case-insensitive validation' do
|
721
|
-
context 'when case_insensitive is not specified' do
|
722
|
-
it 'rejects with an appropriate failure message' do
|
723
|
-
record = build_record_validating_uniqueness(
|
724
|
-
attribute_type: :string,
|
725
|
-
validation_options: { case_sensitive: false }
|
726
|
-
)
|
727
|
-
|
728
|
-
assertion = lambda do
|
729
|
-
expect(record).to validate_uniqueness
|
730
|
-
end
|
731
|
-
|
732
|
-
message = <<-MESSAGE
|
733
|
-
Example did not properly validate that :attr is case-sensitively unique.
|
734
|
-
After taking the given Example, setting its :attr to ‹"an arbitrary
|
735
|
-
value"›, and saving it as the existing record, then making a new
|
736
|
-
Example and setting its :attr to a different value, ‹"AN ARBITRARY
|
737
|
-
VALUE"›, the matcher expected the new Example to be valid, but it was
|
738
|
-
invalid instead, producing these validation errors:
|
739
|
-
|
740
|
-
* attr: ["has already been taken"]
|
741
|
-
MESSAGE
|
742
|
-
|
743
|
-
expect(&assertion).to fail_with_message(message)
|
744
|
-
end
|
745
|
-
end
|
746
|
-
|
747
|
-
context 'when case_insensitive is specified' do
|
748
|
-
it 'accepts' do
|
749
|
-
record = build_record_validating_uniqueness(
|
750
|
-
attribute_type: :string,
|
751
|
-
validation_options: { case_sensitive: false }
|
752
|
-
)
|
753
|
-
|
754
|
-
expect(record).to validate_uniqueness.case_insensitive
|
755
|
-
end
|
756
|
-
|
757
|
-
it_supports(
|
758
|
-
'ignoring_interference_by_writer',
|
759
|
-
tests: {
|
760
|
-
reject_if_qualified_but_changing_value_interferes: {
|
761
|
-
model_name: 'Example',
|
762
|
-
attribute_name: :attr,
|
763
|
-
default_value: 'some value',
|
764
|
-
changing_values_with: :next_value,
|
765
|
-
expected_message: <<-MESSAGE.strip
|
766
|
-
Example did not properly validate that :attr is case-insensitively
|
767
|
-
unique.
|
768
|
-
After taking the given Example, whose :attr is ‹"some valuf"›, and
|
769
|
-
saving it as the existing record, then making a new Example and
|
770
|
-
setting its :attr to ‹"some valuf"› (read back as ‹"some valug"›) as
|
771
|
-
well, the matcher expected the new Example to be invalid, but it was
|
772
|
-
valid instead.
|
773
|
-
|
774
|
-
As indicated in the message above, :attr seems to be changing certain
|
775
|
-
values as they are set, and this could have something to do with why
|
776
|
-
this test is failing. If you or something else has overridden the
|
777
|
-
writer method for this attribute to normalize values by changing their
|
778
|
-
case in any way (for instance, ensuring that the attribute is always
|
779
|
-
downcased), then try adding `ignoring_case_sensitivity` onto the end
|
780
|
-
of the uniqueness matcher. Otherwise, you may need to write the test
|
781
|
-
yourself, or do something different altogether.
|
782
|
-
MESSAGE
|
783
|
-
}
|
784
|
-
}
|
785
|
-
)
|
786
|
-
|
787
|
-
def validation_matcher_scenario_args
|
788
|
-
super.deep_merge(validation_options: { case_sensitive: false })
|
789
|
-
end
|
790
|
-
|
791
|
-
def configure_validation_matcher(matcher)
|
792
|
-
super(matcher).case_insensitive
|
793
|
-
end
|
794
|
-
end
|
795
|
-
end
|
796
|
-
|
797
|
-
context 'when the validation is declared with allow_nil' do
|
798
|
-
context 'given a new record whose attribute is nil' do
|
799
|
-
it 'accepts' do
|
800
|
-
model = define_model_validating_uniqueness(
|
801
|
-
validation_options: { allow_nil: true }
|
802
|
-
)
|
803
|
-
record = build_record_from(model, attribute_name => nil)
|
804
|
-
expect(record).to validate_uniqueness.allow_nil
|
805
|
-
end
|
806
|
-
end
|
807
|
-
|
808
|
-
context 'given an existing record whose attribute is nil' do
|
809
|
-
it 'accepts' do
|
810
|
-
model = define_model_validating_uniqueness(
|
811
|
-
validation_options: { allow_nil: true }
|
812
|
-
)
|
813
|
-
record = create_record_from(model, attribute_name => nil)
|
814
|
-
expect(record).to validate_uniqueness.allow_nil
|
815
|
-
end
|
816
|
-
end
|
817
|
-
|
818
|
-
if active_record_supports_has_secure_password?
|
819
|
-
context 'when the model is declared with has_secure_password' do
|
820
|
-
it 'accepts' do
|
821
|
-
model = define_model_validating_uniqueness(
|
822
|
-
validation_options: { allow_nil: true },
|
823
|
-
additional_attributes: [{ name: :password_digest, type: :string }]
|
824
|
-
) do |m|
|
825
|
-
m.has_secure_password
|
826
|
-
end
|
827
|
-
|
828
|
-
record = build_record_from(model, attribute_name => nil)
|
829
|
-
|
830
|
-
expect(record).to validate_uniqueness.allow_nil
|
831
|
-
end
|
832
|
-
end
|
833
|
-
end
|
834
|
-
end
|
835
|
-
|
836
|
-
context 'when the validation is not declared with allow_nil' do
|
837
|
-
context 'given a new record whose attribute is nil' do
|
838
|
-
it 'rejects with an appropriate failure message' do
|
839
|
-
model = define_model_validating_uniqueness
|
840
|
-
record = build_record_from(model, attribute_name => nil)
|
841
|
-
|
842
|
-
assertion = lambda do
|
843
|
-
expect(record).to validate_uniqueness.allow_nil
|
844
|
-
end
|
845
|
-
|
846
|
-
message = <<-MESSAGE
|
847
|
-
Example did not properly validate that :attr is case-sensitively unique,
|
848
|
-
but only if it is not nil.
|
849
|
-
After taking the given Example, setting its :attr to ‹nil›, and saving
|
850
|
-
it as the existing record, then making a new Example and setting its
|
851
|
-
:attr to ‹nil› as well, the matcher expected the new Example to be
|
852
|
-
valid, but it was invalid instead, producing these validation errors:
|
853
|
-
|
854
|
-
* attr: ["has already been taken"]
|
855
|
-
MESSAGE
|
856
|
-
|
857
|
-
expect(&assertion).to fail_with_message(message)
|
858
|
-
end
|
859
|
-
end
|
860
|
-
|
861
|
-
context 'given an existing record whose attribute is nil' do
|
862
|
-
it 'rejects with an appropriate failure message' do
|
863
|
-
model = define_model_validating_uniqueness
|
864
|
-
record = create_record_from(model, attribute_name => nil)
|
865
|
-
|
866
|
-
assertion = lambda do
|
867
|
-
expect(record).to validate_uniqueness.allow_nil
|
868
|
-
end
|
869
|
-
|
870
|
-
message = <<-MESSAGE
|
871
|
-
Example did not properly validate that :attr is case-sensitively unique,
|
872
|
-
but only if it is not nil.
|
873
|
-
Given an existing Example, after setting its :attr to ‹nil›, then
|
874
|
-
making a new Example and setting its :attr to ‹nil› as well, the
|
875
|
-
matcher expected the new Example to be valid, but it was invalid
|
876
|
-
instead, producing these validation errors:
|
877
|
-
|
878
|
-
* attr: ["has already been taken"]
|
879
|
-
MESSAGE
|
880
|
-
|
881
|
-
expect(&assertion).to fail_with_message(message)
|
882
|
-
end
|
883
|
-
end
|
884
|
-
end
|
885
|
-
|
886
|
-
context 'when the validation is declared with allow_blank' do
|
887
|
-
context 'given a new record whose attribute is nil' do
|
888
|
-
it 'accepts' do
|
889
|
-
model = define_model_validating_uniqueness(
|
890
|
-
validation_options: { allow_blank: true }
|
891
|
-
)
|
892
|
-
record = build_record_from(model, attribute_name => nil)
|
893
|
-
expect(record).to validate_uniqueness.allow_blank
|
894
|
-
end
|
895
|
-
end
|
896
|
-
|
897
|
-
context 'given an existing record whose attribute is nil' do
|
898
|
-
it 'accepts' do
|
899
|
-
model = define_model_validating_uniqueness(
|
900
|
-
validation_options: { allow_blank: true }
|
901
|
-
)
|
902
|
-
record = create_record_from(model, attribute_name => nil)
|
903
|
-
expect(record).to validate_uniqueness.allow_blank
|
904
|
-
end
|
905
|
-
end
|
906
|
-
|
907
|
-
context 'given a new record whose attribute is empty' do
|
908
|
-
it 'accepts' do
|
909
|
-
model = define_model_validating_uniqueness(
|
910
|
-
attribute_type: :string,
|
911
|
-
validation_options: { allow_blank: true }
|
912
|
-
)
|
913
|
-
record = build_record_from(model, attribute_name => '')
|
914
|
-
expect(record).to validate_uniqueness.allow_blank
|
915
|
-
end
|
916
|
-
end
|
917
|
-
|
918
|
-
context 'given an existing record whose attribute is empty' do
|
919
|
-
it 'accepts' do
|
920
|
-
model = define_model_validating_uniqueness(
|
921
|
-
attribute_type: :string,
|
922
|
-
validation_options: { allow_blank: true }
|
923
|
-
)
|
924
|
-
record = create_record_from(model, attribute_name => '')
|
925
|
-
expect(record).to validate_uniqueness.allow_blank
|
926
|
-
end
|
927
|
-
end
|
928
|
-
|
929
|
-
if active_record_supports_has_secure_password?
|
930
|
-
context 'when the model is declared with has_secure_password' do
|
931
|
-
context 'given a record whose attribute is nil' do
|
932
|
-
it 'accepts' do
|
933
|
-
model = define_model_validating_uniqueness(
|
934
|
-
validation_options: { allow_blank: true },
|
935
|
-
additional_attributes: [{ name: :password_digest, type: :string }]
|
936
|
-
) do |m|
|
937
|
-
m.has_secure_password
|
938
|
-
end
|
939
|
-
|
940
|
-
record = build_record_from(model, attribute_name => nil)
|
941
|
-
|
942
|
-
expect(record).to validate_uniqueness.allow_blank
|
943
|
-
end
|
944
|
-
end
|
945
|
-
|
946
|
-
context 'given a record whose attribute is empty' do
|
947
|
-
it 'accepts' do
|
948
|
-
model = define_model_validating_uniqueness(
|
949
|
-
attribute_type: :string,
|
950
|
-
validation_options: { allow_blank: true },
|
951
|
-
additional_attributes: [{ name: :password_digest, type: :string }]
|
952
|
-
) do |m|
|
953
|
-
m.has_secure_password
|
954
|
-
end
|
955
|
-
|
956
|
-
record = build_record_from(model, attribute_name => '')
|
957
|
-
|
958
|
-
expect(record).to validate_uniqueness.allow_blank
|
959
|
-
end
|
960
|
-
end
|
961
|
-
end
|
962
|
-
end
|
963
|
-
end
|
964
|
-
|
965
|
-
context 'when the validation is not declared with allow_blank' do
|
966
|
-
context 'given a new record whose attribute is nil' do
|
967
|
-
it 'rejects with an appropriate failure message' do
|
968
|
-
model = define_model_validating_uniqueness
|
969
|
-
record = build_record_from(model, attribute_name => nil)
|
970
|
-
|
971
|
-
assertion = lambda do
|
972
|
-
expect(record).to validate_uniqueness.allow_blank
|
973
|
-
end
|
974
|
-
|
975
|
-
message = <<-MESSAGE
|
976
|
-
Example did not properly validate that :attr is case-sensitively unique,
|
977
|
-
but only if it is not blank.
|
978
|
-
After taking the given Example, setting its :attr to ‹""›, and saving
|
979
|
-
it as the existing record, then making a new Example and setting its
|
980
|
-
:attr to ‹""› as well, the matcher expected the new Example to be
|
981
|
-
valid, but it was invalid instead, producing these validation errors:
|
982
|
-
|
983
|
-
* attr: ["has already been taken"]
|
984
|
-
MESSAGE
|
985
|
-
|
986
|
-
expect(&assertion).to fail_with_message(message)
|
987
|
-
end
|
988
|
-
end
|
989
|
-
|
990
|
-
context 'given an existing record whose attribute is nil' do
|
991
|
-
it 'rejects with an appropriate failure message' do
|
992
|
-
model = define_model_validating_uniqueness
|
993
|
-
record = create_record_from(model, attribute_name => nil)
|
994
|
-
|
995
|
-
assertion = lambda do
|
996
|
-
expect(record).to validate_uniqueness.allow_blank
|
997
|
-
end
|
998
|
-
|
999
|
-
message = <<-MESSAGE
|
1000
|
-
Example did not properly validate that :attr is case-sensitively unique,
|
1001
|
-
but only if it is not blank.
|
1002
|
-
Given an existing Example, after setting its :attr to ‹""›, then
|
1003
|
-
making a new Example and setting its :attr to ‹""› as well, the
|
1004
|
-
matcher expected the new Example to be valid, but it was invalid
|
1005
|
-
instead, producing these validation errors:
|
1006
|
-
|
1007
|
-
* attr: ["has already been taken"]
|
1008
|
-
MESSAGE
|
1009
|
-
|
1010
|
-
expect(&assertion).to fail_with_message(message)
|
1011
|
-
end
|
1012
|
-
end
|
1013
|
-
|
1014
|
-
context 'given a new record whose attribute is empty' do
|
1015
|
-
it 'rejects with an appropriate failure message' do
|
1016
|
-
model = define_model_validating_uniqueness(
|
1017
|
-
attribute_type: :string
|
1018
|
-
)
|
1019
|
-
record = build_record_from(model, attribute_name => '')
|
1020
|
-
|
1021
|
-
assertion = lambda do
|
1022
|
-
expect(record).to validate_uniqueness.allow_blank
|
1023
|
-
end
|
1024
|
-
|
1025
|
-
message = <<-MESSAGE
|
1026
|
-
Example did not properly validate that :attr is case-sensitively unique,
|
1027
|
-
but only if it is not blank.
|
1028
|
-
After taking the given Example, setting its :attr to ‹""›, and saving
|
1029
|
-
it as the existing record, then making a new Example and setting its
|
1030
|
-
:attr to ‹""› as well, the matcher expected the new Example to be
|
1031
|
-
valid, but it was invalid instead, producing these validation errors:
|
1032
|
-
|
1033
|
-
* attr: ["has already been taken"]
|
1034
|
-
MESSAGE
|
1035
|
-
|
1036
|
-
expect(&assertion).to fail_with_message(message)
|
1037
|
-
end
|
1038
|
-
end
|
1039
|
-
|
1040
|
-
context 'given an existing record whose attribute is empty' do
|
1041
|
-
it 'rejects with an appropriate failure message' do
|
1042
|
-
model = define_model_validating_uniqueness(
|
1043
|
-
attribute_type: :string
|
1044
|
-
)
|
1045
|
-
record = create_record_from(model, attribute_name => '')
|
1046
|
-
|
1047
|
-
assertion = lambda do
|
1048
|
-
expect(record).to validate_uniqueness.allow_blank
|
1049
|
-
end
|
1050
|
-
|
1051
|
-
message = <<-MESSAGE
|
1052
|
-
Example did not properly validate that :attr is case-sensitively unique,
|
1053
|
-
but only if it is not blank.
|
1054
|
-
Given an existing Example, after setting its :attr to ‹""›, then
|
1055
|
-
making a new Example and setting its :attr to ‹""› as well, the
|
1056
|
-
matcher expected the new Example to be valid, but it was invalid
|
1057
|
-
instead, producing these validation errors:
|
1058
|
-
|
1059
|
-
* attr: ["has already been taken"]
|
1060
|
-
MESSAGE
|
1061
|
-
|
1062
|
-
expect(&assertion).to fail_with_message(message)
|
1063
|
-
end
|
1064
|
-
end
|
1065
|
-
end
|
1066
|
-
|
1067
|
-
context 'when testing that a polymorphic *_type column is one of the validation scopes' do
|
1068
|
-
it 'sets that column to a meaningful value that works with other validations on the same column' do
|
1069
|
-
user_model = define_model 'User'
|
1070
|
-
favorite_columns = {
|
1071
|
-
favoriteable_id: { type: :integer, options: { null: false } },
|
1072
|
-
favoriteable_type: { type: :string, options: { null: false } }
|
1073
|
-
}
|
1074
|
-
favorite_model = define_model 'Favorite', favorite_columns do
|
1075
|
-
attr_accessible :favoriteable
|
1076
|
-
belongs_to :favoriteable, polymorphic: true
|
1077
|
-
validates :favoriteable, presence: true
|
1078
|
-
validates :favoriteable_id, uniqueness: { scope: :favoriteable_type }
|
1079
|
-
end
|
1080
|
-
|
1081
|
-
user = user_model.create!
|
1082
|
-
favorite_model.create!(favoriteable: user)
|
1083
|
-
new_favorite = favorite_model.new
|
1084
|
-
|
1085
|
-
expect(new_favorite).
|
1086
|
-
to validate_uniqueness_of(:favoriteable_id).
|
1087
|
-
scoped_to(:favoriteable_type)
|
1088
|
-
end
|
1089
|
-
|
1090
|
-
context 'if the model the *_type column refers to is namespaced, and shares the last part of its name with an existing model' do
|
1091
|
-
it 'still works' do
|
1092
|
-
define_class 'User'
|
1093
|
-
define_module 'Models'
|
1094
|
-
user_model = define_model 'Models::User'
|
1095
|
-
favorite_columns = {
|
1096
|
-
favoriteable_id: { type: :integer, options: { null: false } },
|
1097
|
-
favoriteable_type: { type: :string, options: { null: false } }
|
1098
|
-
}
|
1099
|
-
favorite_model = define_model 'Models::Favorite', favorite_columns do
|
1100
|
-
attr_accessible :favoriteable
|
1101
|
-
belongs_to :favoriteable, polymorphic: true
|
1102
|
-
validates :favoriteable, presence: true
|
1103
|
-
validates :favoriteable_id, uniqueness: { scope: :favoriteable_type }
|
1104
|
-
end
|
1105
|
-
|
1106
|
-
user = user_model.create!
|
1107
|
-
favorite_model.create!(favoriteable: user)
|
1108
|
-
new_favorite = favorite_model.new
|
1109
|
-
|
1110
|
-
expect(new_favorite).
|
1111
|
-
to validate_uniqueness_of(:favoriteable_id).
|
1112
|
-
scoped_to(:favoriteable_type)
|
1113
|
-
end
|
1114
|
-
end
|
1115
|
-
end
|
1116
|
-
|
1117
|
-
context 'when the model does not have the attribute being tested' do
|
1118
|
-
it 'fails with an appropriate failure message' do
|
1119
|
-
model = define_model(:example)
|
1120
|
-
|
1121
|
-
assertion = lambda do
|
1122
|
-
expect(model.new).to validate_uniqueness_of(:attr)
|
1123
|
-
end
|
1124
|
-
|
1125
|
-
message = <<-MESSAGE.strip
|
1126
|
-
Example did not properly validate that :attr is case-sensitively unique.
|
1127
|
-
:attr does not seem to be an attribute on Example.
|
1128
|
-
MESSAGE
|
1129
|
-
|
1130
|
-
expect(&assertion).to fail_with_message(message)
|
1131
|
-
end
|
1132
|
-
end
|
1133
|
-
|
1134
|
-
context 'when the writer method for the attribute changes the case of incoming values' do
|
1135
|
-
context 'when the validation is case-sensitive' do
|
1136
|
-
context 'and the matcher is ensuring that the validation is case-sensitive' do
|
1137
|
-
it 'rejects with an appropriate failure message' do
|
1138
|
-
model = define_model_validating_uniqueness(
|
1139
|
-
attribute_name: :name
|
1140
|
-
)
|
1141
|
-
|
1142
|
-
model.class_eval do
|
1143
|
-
def name=(name)
|
1144
|
-
super(name.upcase)
|
1145
|
-
end
|
1146
|
-
end
|
1147
|
-
|
1148
|
-
assertion = lambda do
|
1149
|
-
expect(model.new).to validate_uniqueness_of(:name)
|
1150
|
-
end
|
1151
|
-
|
1152
|
-
message = <<-MESSAGE.strip
|
1153
|
-
Example did not properly validate that :name is case-sensitively unique.
|
1154
|
-
After taking the given Example, setting its :name to ‹"an arbitrary
|
1155
|
-
value"› (read back as ‹"AN ARBITRARY VALUE"›), and saving it as the
|
1156
|
-
existing record, then making a new Example and setting its :name to
|
1157
|
-
‹"an arbitrary value"› (read back as ‹"AN ARBITRARY VALUE"›) as well,
|
1158
|
-
the matcher expected the new Example to be valid, but it was invalid
|
1159
|
-
instead, producing these validation errors:
|
1160
|
-
|
1161
|
-
* name: ["has already been taken"]
|
1162
|
-
|
1163
|
-
As indicated in the message above, :name seems to be changing certain
|
1164
|
-
values as they are set, and this could have something to do with why
|
1165
|
-
this test is failing. If you or something else has overridden the
|
1166
|
-
writer method for this attribute to normalize values by changing their
|
1167
|
-
case in any way (for instance, ensuring that the attribute is always
|
1168
|
-
downcased), then try adding `ignoring_case_sensitivity` onto the end
|
1169
|
-
of the uniqueness matcher. Otherwise, you may need to write the test
|
1170
|
-
yourself, or do something different altogether.
|
1171
|
-
MESSAGE
|
1172
|
-
|
1173
|
-
expect(&assertion).to fail_with_message(message)
|
1174
|
-
end
|
1175
|
-
end
|
1176
|
-
|
1177
|
-
context 'and the matcher is ignoring case sensitivity' do
|
1178
|
-
it 'accepts (and not raise an error)' do
|
1179
|
-
model = define_model_validating_uniqueness(
|
1180
|
-
attribute_name: :name
|
1181
|
-
)
|
1182
|
-
|
1183
|
-
model.class_eval do
|
1184
|
-
def name=(name)
|
1185
|
-
super(name.upcase)
|
1186
|
-
end
|
1187
|
-
end
|
1188
|
-
|
1189
|
-
expect(model.new).
|
1190
|
-
to validate_uniqueness_of(:name).
|
1191
|
-
ignoring_case_sensitivity
|
1192
|
-
end
|
1193
|
-
end
|
1194
|
-
end
|
1195
|
-
|
1196
|
-
context 'when the validation is case-insensitive' do
|
1197
|
-
context 'and the matcher is ensuring that the validation is case-insensitive' do
|
1198
|
-
it 'accepts (and does not raise an error)' do
|
1199
|
-
model = define_model_validating_uniqueness(
|
1200
|
-
attribute_name: :name,
|
1201
|
-
validation_options: { case_sensitive: false },
|
1202
|
-
)
|
1203
|
-
|
1204
|
-
model.class_eval do
|
1205
|
-
def name=(name)
|
1206
|
-
super(name.downcase)
|
1207
|
-
end
|
1208
|
-
end
|
1209
|
-
|
1210
|
-
expect(model.new).
|
1211
|
-
to validate_uniqueness_of(:name).
|
1212
|
-
case_insensitive
|
1213
|
-
end
|
1214
|
-
end
|
1215
|
-
end
|
1216
|
-
end
|
1217
|
-
|
1218
|
-
let(:model_attributes) { {} }
|
1219
|
-
|
1220
|
-
def default_attribute
|
1221
|
-
{
|
1222
|
-
value_type: :string,
|
1223
|
-
column_type: :string,
|
1224
|
-
options: { array: false, null: true }
|
1225
|
-
}
|
1226
|
-
end
|
1227
|
-
|
1228
|
-
def normalize_attribute(attribute)
|
1229
|
-
if attribute.is_a?(Hash)
|
1230
|
-
attribute_copy = attribute.dup
|
1231
|
-
|
1232
|
-
if attribute_copy.key?(:type)
|
1233
|
-
attribute_copy[:value_type] = attribute_copy[:type]
|
1234
|
-
attribute_copy[:column_type] = attribute_copy[:type]
|
1235
|
-
end
|
1236
|
-
|
1237
|
-
default_attribute.deep_merge(attribute_copy)
|
1238
|
-
else
|
1239
|
-
default_attribute.deep_merge(name: attribute)
|
1240
|
-
end
|
1241
|
-
end
|
1242
|
-
|
1243
|
-
def normalize_attributes(attributes)
|
1244
|
-
attributes.map do |attribute|
|
1245
|
-
normalize_attribute(attribute)
|
1246
|
-
end
|
1247
|
-
end
|
1248
|
-
|
1249
|
-
def column_options_from(attributes)
|
1250
|
-
attributes.inject({}) do |options, attribute|
|
1251
|
-
options[attribute[:name]] = {
|
1252
|
-
type: attribute[:column_type],
|
1253
|
-
options: attribute.fetch(:options, {})
|
1254
|
-
}
|
1255
|
-
options
|
1256
|
-
end
|
1257
|
-
end
|
1258
|
-
|
1259
|
-
def attributes_with_values_for(model)
|
1260
|
-
model_attributes[model].each_with_object({}) do |attribute, attrs|
|
1261
|
-
attrs[attribute[:name]] = attribute.fetch(:value) do
|
1262
|
-
if attribute[:options][:null]
|
1263
|
-
nil
|
1264
|
-
else
|
1265
|
-
dummy_value_for(
|
1266
|
-
attribute[:value_type],
|
1267
|
-
array: attribute[:options][:array]
|
1268
|
-
)
|
1269
|
-
end
|
1270
|
-
end
|
1271
|
-
end
|
1272
|
-
end
|
1273
|
-
|
1274
|
-
def dummy_value_for(attribute_type, array: false)
|
1275
|
-
if array
|
1276
|
-
[ dummy_scalar_value_for(attribute_type) ]
|
1277
|
-
else
|
1278
|
-
dummy_scalar_value_for(attribute_type)
|
1279
|
-
end
|
1280
|
-
end
|
1281
|
-
|
1282
|
-
def dummy_scalar_value_for(attribute_type)
|
1283
|
-
case attribute_type
|
1284
|
-
when :string, :text
|
1285
|
-
'dummy value'
|
1286
|
-
when :integer
|
1287
|
-
1
|
1288
|
-
when :date
|
1289
|
-
Date.today
|
1290
|
-
when :datetime
|
1291
|
-
Date.today.to_datetime
|
1292
|
-
when :time
|
1293
|
-
Time.now
|
1294
|
-
when :uuid
|
1295
|
-
SecureRandom.uuid
|
1296
|
-
when :boolean
|
1297
|
-
true
|
1298
|
-
else
|
1299
|
-
raise ArgumentError, "Unknown type '#{attribute_type}'"
|
1300
|
-
end
|
1301
|
-
end
|
1302
|
-
|
1303
|
-
def next_version_of(value, value_type)
|
1304
|
-
if value.is_a?(Array)
|
1305
|
-
[ next_version_of(value[0], value_type) ]
|
1306
|
-
elsif value_type == :uuid
|
1307
|
-
SecureRandom.uuid
|
1308
|
-
elsif value.is_a?(Time)
|
1309
|
-
value + 1
|
1310
|
-
elsif value.in?([true, false])
|
1311
|
-
!value
|
1312
|
-
elsif value.respond_to?(:next)
|
1313
|
-
value.next
|
1314
|
-
end
|
1315
|
-
end
|
1316
|
-
|
1317
|
-
def build_record_from(model, extra_attributes = {})
|
1318
|
-
attributes = attributes_with_values_for(model)
|
1319
|
-
model.new(attributes.merge(extra_attributes))
|
1320
|
-
end
|
1321
|
-
|
1322
|
-
def create_record_from(model, extra_attributes = {})
|
1323
|
-
build_record_from(model, extra_attributes).tap do |record|
|
1324
|
-
record.save!
|
1325
|
-
end
|
1326
|
-
end
|
1327
|
-
|
1328
|
-
def define_model_validating_uniqueness(options = {}, &block)
|
1329
|
-
attribute_name = options.fetch(:attribute_name) { self.attribute_name }
|
1330
|
-
attribute_type = options.fetch(:attribute_type, :string)
|
1331
|
-
attribute_options = options.fetch(:attribute_options, {})
|
1332
|
-
attribute = normalize_attribute(
|
1333
|
-
name: attribute_name,
|
1334
|
-
value_type: attribute_type,
|
1335
|
-
column_type: attribute_type,
|
1336
|
-
options: attribute_options
|
1337
|
-
)
|
1338
|
-
|
1339
|
-
if options.key?(:attribute_value)
|
1340
|
-
attribute[:value] = options[:attribute_value]
|
1341
|
-
end
|
1342
|
-
|
1343
|
-
scope_attributes = normalize_attributes(options.fetch(:scopes, []))
|
1344
|
-
scope_attribute_names = scope_attributes.map { |attr| attr[:name] }
|
1345
|
-
additional_attributes = normalize_attributes(
|
1346
|
-
options.fetch(:additional_attributes, [])
|
1347
|
-
)
|
1348
|
-
attributes = [attribute] + scope_attributes + additional_attributes
|
1349
|
-
validation_options = options.fetch(:validation_options, {})
|
1350
|
-
column_options = column_options_from(attributes)
|
1351
|
-
|
1352
|
-
model = define_model(:example, column_options) do |m|
|
1353
|
-
m.validates_uniqueness_of attribute_name,
|
1354
|
-
validation_options.merge(scope: scope_attribute_names)
|
1355
|
-
|
1356
|
-
attributes.each do |attr|
|
1357
|
-
m.attr_accessible(attr[:name])
|
1358
|
-
end
|
1359
|
-
|
1360
|
-
block.call(m) if block
|
1361
|
-
end
|
1362
|
-
|
1363
|
-
model_attributes[model] = attributes
|
1364
|
-
|
1365
|
-
model
|
1366
|
-
end
|
1367
|
-
|
1368
|
-
def build_record_validating_uniqueness(options = {}, &block)
|
1369
|
-
model = define_model_validating_uniqueness(options, &block)
|
1370
|
-
build_record_from(model)
|
1371
|
-
end
|
1372
|
-
alias_method :new_record_validating_uniqueness,
|
1373
|
-
:build_record_validating_uniqueness
|
1374
|
-
|
1375
|
-
def create_record_validating_uniqueness(options = {}, &block)
|
1376
|
-
build_record_validating_uniqueness(options, &block).tap do |record|
|
1377
|
-
record.save!
|
1378
|
-
end
|
1379
|
-
end
|
1380
|
-
alias_method :existing_record_validating_uniqueness,
|
1381
|
-
:create_record_validating_uniqueness
|
1382
|
-
|
1383
|
-
def build_record_validating_scoped_uniqueness_with_enum(options = {})
|
1384
|
-
options = options.dup
|
1385
|
-
enum_scope_attribute =
|
1386
|
-
normalize_attribute(options.delete(:enum_scope)).
|
1387
|
-
merge(value_type: :integer, column_type: :integer)
|
1388
|
-
additional_scopes = options.delete(:additional_scopes) { [] }
|
1389
|
-
options[:scopes] = [enum_scope_attribute] + additional_scopes
|
1390
|
-
dummy_enum_values = [:foo, :bar]
|
1391
|
-
|
1392
|
-
model = define_model_validating_uniqueness(options)
|
1393
|
-
model.enum(enum_scope_attribute[:name] => dummy_enum_values)
|
1394
|
-
|
1395
|
-
build_record_from(model)
|
1396
|
-
end
|
1397
|
-
|
1398
|
-
def define_model_without_validation
|
1399
|
-
define_model(:example, attribute_name => :string) do |model|
|
1400
|
-
model.attr_accessible(attribute_name)
|
1401
|
-
end
|
1402
|
-
end
|
1403
|
-
|
1404
|
-
def validate_uniqueness
|
1405
|
-
validate_uniqueness_of(attribute_name)
|
1406
|
-
end
|
1407
|
-
|
1408
|
-
def attribute_name
|
1409
|
-
:attr
|
1410
|
-
end
|
1411
|
-
|
1412
|
-
def validation_matcher_scenario_args
|
1413
|
-
super.deep_merge(
|
1414
|
-
matcher_name: :validate_uniqueness_of,
|
1415
|
-
model_creator: :"active_record/uniqueness_matcher"
|
1416
|
-
)
|
1417
|
-
end
|
1418
|
-
end
|