class Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher
@private
Public Class Methods
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 269 def initialize(attribute) super(attribute) @expected_message = :taken @options = { case_sensitivity_strategy: :sensitive, } @existing_record_created = false @failure_reason = nil @failure_reason_when_negated = nil @attribute_setters = { existing_record: AttributeSetters.new, new_record: AttributeSetters.new, } end
Calls superclass method
Shoulda::Matchers::ActiveModel::ValidationMatcher::new
Public Instance Methods
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 308 def allow_blank @options[:allow_blank] = true self end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 299 def allow_nil @options[:allow_nil] = true self end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 289 def case_insensitive @options[:case_sensitivity_strategy] = :insensitive self end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 345 def does_not_match?(given_record) @given_record = given_record @all_records = model.all does_not_match_presence_of_scopes? || does_not_match_scopes_configuration? || does_not_match_uniqueness_without_scopes? || does_not_match_uniqueness_with_case_sensitivity_strategy? || does_not_match_uniqueness_with_scopes? || does_not_match_allow_nil? || does_not_match_allow_blank? ensure Uniqueness::TestModels.remove_all end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 313 def expects_to_allow_blank? @options[:allow_blank] == true end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 304 def expects_to_allow_nil? @options[:allow_nil] == true end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 294 def ignoring_case_sensitivity @options[:case_sensitivity_strategy] = :ignore self end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 329 def matches?(given_record) @given_record = given_record @all_records = model.all matches_presence_of_attribute? && matches_presence_of_scopes? && matches_scopes_configuration? && matches_uniqueness_without_scopes? && matches_uniqueness_with_case_sensitivity_strategy? && matches_uniqueness_with_scopes? && matches_allow_nil? && matches_allow_blank? ensure Uniqueness::TestModels.remove_all end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 284 def scoped_to(*scopes) @options[:scopes] = [*scopes].flatten.map(&:to_sym) self end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 317 def simple_description description = "validate that :#{@attribute} is" description << description_for_case_sensitive_qualifier description << ' unique' if @options[:scopes].present? description << " within the scope of #{inspected_expected_scopes}" end description end
Protected Instance Methods
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 370 def build_allow_or_disallow_value_matcher(args) super.tap do |matcher| matcher.failure_message_preface = method(:failure_message_preface) matcher.attribute_changed_value_message = method(:attribute_changed_value_message) end end
Calls superclass method
Shoulda::Matchers::ActiveModel::ValidationMatcher#build_allow_or_disallow_value_matcher
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 362 def failure_reason @failure_reason || super end
Calls superclass method
Shoulda::Matchers::ActiveModel::ValidationMatcher#failure_reason
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 366 def failure_reason_when_negated @failure_reason_when_negated || super end
Calls superclass method
Shoulda::Matchers::ActiveModel::ValidationMatcher#failure_reason_when_negated
Private Instance Methods
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 485 def actual_sets_of_scopes validations.map do |validation| Array.wrap(validation.options[:scope]).map(&:to_sym) end.reject(&:empty?) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 836 def all_scopes_are_booleans? @options[:scopes].all? do |scope| @all_records.map(&scope).all? { |s| boolean_value?(s) } end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 561 def arbitrary_non_blank_value non_blank_value = dummy_value_for(@attribute) limit = column_limit_for(@attribute) is_string_value = non_blank_value.is_a?(String) if is_string_value && limit && limit < non_blank_value.length 'x' * limit else non_blank_value end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 980 def attribute_changed_value_message <<-MESSAGE.strip As indicated in the message above, :#{last_attribute_setter_used_on_new_record.attribute_name} seems to be changing certain values as they are set, and this could have something to do with why this test is failing. If you or something else has overridden the writer method for this attribute to normalize values by changing their case in any way (for instance, ensuring that the attribute is always downcased), then try adding `ignoring_case_sensitivity` onto the end of the uniqueness matcher. Otherwise, you may need to write the test yourself, or do something different altogether. MESSAGE end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 899 def attribute_names_under_test [@attribute] + expected_scopes end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 610 def attribute_present_on_model? model.method_defined?("#{attribute}=") || model.columns_hash.key?(attribute.to_s) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 1028 def attribute_setter_descriptions_for_new_record attribute_setters_for_new_record.map do |attribute_setter| same_as_existing = ( attribute_setter.value_written == existing_value_written ) description_for_attribute_setter( attribute_setter, same_as_existing: same_as_existing, ) end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 890 def attribute_setter_for_existing_record @attribute_setters[:existing_record].last end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 894 def attribute_setters_for_new_record @attribute_setters[:new_record] + [last_attribute_setter_used_on_new_record] end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 855 def available_enum_values_for(scope, previous_value) new_record.defined_enums[scope.to_s].reject do |key, _| key == previous_value end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 842 def boolean_value?(value) [true, false].include?(value) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 903 def build_attribute_setter(record, attribute_name, value) Shoulda::Matchers::ActiveModel::AllowValueMatcher::AttributeSetter. new( matcher_name: :validate_uniqueness_of, object: record, attribute_name: attribute_name, value: value, ignore_interference_by_writer: ignore_interference_by_writer, ) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 577 def build_new_record @new_record = existing_record.dup attribute_names_under_test.each do |attribute_name| set_attribute_on_new_record!( attribute_name, existing_record.public_send(attribute_name), ) end @new_record end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 380 def case_sensitivity_strategy @options[:case_sensitivity_strategy] end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 926 def column_for(scope) model.columns_hash[scope.to_s] end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 930 def column_limit_for(attribute) column_for(attribute).try(:limit) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 542 def create_existing_record @given_record.tap do |existing_record| existing_record.save(validate: false) end rescue ::ActiveRecord::StatementInvalid => e raise ExistingRecordInvalid.create(underlying_exception: e) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 846 def defined_as_enum?(scope) model.respond_to?(:defined_enums) && new_record.defined_enums[scope.to_s] end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 995 def description_for_attribute_setter( attribute_setter, same_as_existing: nil ) description = "its :#{attribute_setter.attribute_name} to " if same_as_existing == false description << 'a different value, ' end description << Shoulda::Matchers::Util.inspect_value( attribute_setter.value_written, ) if attribute_setter.attribute_changed_value? description << ' (read back as ' description << Shoulda::Matchers::Util.inspect_value( attribute_setter.value_read, ) description << ')' end if same_as_existing == true description << ' as well' end description end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 393 def description_for_case_sensitive_qualifier case case_sensitivity_strategy when :sensitive ' case-sensitively' when :insensitive ' case-insensitively' else '' end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 1024 def descriptions_for_attribute_setters_for_new_record attribute_setter_descriptions_for_new_record.to_sentence end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 514 def does_not_match_allow_blank? expects_to_allow_blank? && ( update_existing_record!('') && (@failure_reason = nil || disallows_value_of('', @expected_message)) ) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 498 def does_not_match_allow_nil? expects_to_allow_nil? && ( update_existing_record!(nil) && (@failure_reason = nil || disallows_value_of(nil, @expected_message) ) ) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 600 def does_not_match_presence_of_attribute? if attribute_present_on_model? @failure_reason = ":#{attribute} seems to be an attribute on #{model.name}." false else true end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 640 def does_not_match_presence_of_scopes? if scopes_missing_on_model.any? true else inspected_scopes = scopes_present_on_model.map(&:inspect) reason = '' reason << inspected_scopes.to_sentence reason << if inspected_scopes.many? ' seem to be attributes' else ' seems to be an attribute' end reason << " on #{model.name}." @failure_reason = reason false end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 434 def does_not_match_scopes_configuration? if scopes_match? @failure_reason = 'Expected the validation ' if expected_scopes.empty? @failure_reason << 'to be scoped to nothing, ' @failure_reason << 'but it was scoped to ' @failure_reason << "#{inspected_actual_scopes} instead." else @failure_reason << 'not to be scoped to ' @failure_reason << inspected_expected_scopes end false else true end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 718 def does_not_match_uniqueness_with_case_sensitivity_strategy? if should_test_case_sensitivity? @failure_reason = nil value = existing_value_read swapcased_value = value.swapcase if case_sensitivity_strategy == :sensitive disallows_value_of(swapcased_value, @expected_message) else if value == swapcased_value raise NonCaseSwappableValueError.create( model: model, attribute: @attribute, value: value, ) end allows_value_of(swapcased_value, @expected_message) end else true end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 765 def does_not_match_uniqueness_with_scopes? expected_scopes.any? && !all_scopes_are_booleans? && expected_scopes.any? do |scope| setting_next_value_for(scope) do @failure_reason = nil disallows_value_of(existing_value_read, @expected_message) end end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 685 def does_not_match_uniqueness_without_scopes? @failure_reason = nil if existing_value_read.blank? update_existing_record!(arbitrary_non_blank_value) end allows_value_of(existing_value_read, @expected_message) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 803 def dummy_scalar_value_for(column) Shoulda::Matchers::Util.dummy_value_for(column.type) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 793 def dummy_value_for(scope) column = column_for(scope) if column.respond_to?(:array) && column.array [dummy_scalar_value_for(column)] else dummy_scalar_value_for(column) end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 1041 def existing_and_new_values_are_same? last_value_set_on_new_record == existing_value_written end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 521 def existing_record unless defined?(@existing_record) find_or_create_existing_record end @existing_record end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 914 def existing_value_read existing_record.public_send(@attribute) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 918 def existing_value_written if attribute_setter_for_existing_record attribute_setter_for_existing_record.value_written else existing_value_read end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 481 def expected_scopes Array.wrap(@options[:scopes]) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 938 def failure_message_preface # rubocop:disable Metrics/MethodLength prefix = '' if @existing_record_created prefix << "After taking the given #{model.name}" if attribute_setter_for_existing_record prefix << ', setting ' prefix << description_for_attribute_setter( attribute_setter_for_existing_record, ) else prefix << ", whose :#{attribute} is " prefix << "‹#{existing_value_read.inspect}›" end prefix << ', and saving it as the existing record, then' elsif attribute_setter_for_existing_record prefix << "Given an existing #{model.name}," prefix << ' after setting ' prefix << description_for_attribute_setter( attribute_setter_for_existing_record, ) prefix << ', then' else prefix << "Given an existing #{model.name} whose :#{attribute}" prefix << ' is ' prefix << Shoulda::Matchers::Util.inspect_value( existing_value_read, ) prefix << ', after' end prefix << " making a new #{model.name} and setting " prefix << descriptions_for_attribute_setters_for_new_record prefix << ", the matcher expected the new #{model.name} to be" prefix end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 538 def find_existing_record model.first.presence end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 529 def find_or_create_existing_record @existing_record = find_existing_record unless @existing_record @existing_record = create_existing_record @existing_record_created = true end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 573 def has_secure_password? Shoulda::Matchers::RailsShim.has_secure_password?(subject, @attribute) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 462 def inspected_actual_scopes inspected_actual_sets_of_scopes.to_sentence( words_connector: ' and ', last_word_connector: ', and', ) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 469 def inspected_actual_sets_of_scopes inspected_sets_of_scopes = actual_sets_of_scopes.map do |scopes| scopes.map(&:inspect) end if inspected_sets_of_scopes.many? inspected_sets_of_scopes.map { |x| "(#{x.to_sentence})" } else inspected_sets_of_scopes.map(&:to_sentence) end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 458 def inspected_expected_scopes expected_scopes.map(&:inspect).to_sentence end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 1045 def last_attribute_setter_used_on_new_record last_submatcher_run.last_attribute_setter_used end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 1049 def last_value_set_on_new_record last_submatcher_run.last_value_set end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 507 def matches_allow_blank? !expects_to_allow_blank? || ( update_existing_record!('') && allows_value_of('', @expected_message) ) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 491 def matches_allow_nil? !expects_to_allow_nil? || ( update_existing_record!(nil) && allows_value_of(nil, @expected_message) ) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 590 def matches_presence_of_attribute? if attribute_present_on_model? true else @failure_reason = ":#{attribute} does not seem to be an attribute on #{model.name}." false end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 615 def matches_presence_of_scopes? if scopes_missing_on_model.none? true else inspected_scopes = scopes_missing_on_model.map(&:inspect) reason = '' reason << inspected_scopes.to_sentence reason << if inspected_scopes.many? ' do not seem to be attributes' else ' does not seem to be an attribute' end reason << " on #{model.name}." @failure_reason = reason false end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 410 def matches_scopes_configuration? if scopes_match? true else @failure_reason = 'Expected the validation ' @failure_reason << if expected_scopes.empty? 'not to be scoped to anything, ' else "to be scoped to #{inspected_expected_scopes}, " end if actual_sets_of_scopes.any? @failure_reason << 'but it was scoped to ' @failure_reason << "#{inspected_actual_scopes} instead." else @failure_reason << 'but it was not scoped to anything.' end false end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 695 def matches_uniqueness_with_case_sensitivity_strategy? if should_test_case_sensitivity? value = existing_value_read swapcased_value = value.swapcase if case_sensitivity_strategy == :sensitive if value == swapcased_value raise NonCaseSwappableValueError.create( model: model, attribute: @attribute, value: value, ) end allows_value_of(swapcased_value, @expected_message) else disallows_value_of(swapcased_value, @expected_message) end else true end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 755 def matches_uniqueness_with_scopes? expected_scopes.none? || all_scopes_are_booleans? || expected_scopes.all? do |scope| setting_next_value_for(scope) do allows_value_of(existing_value_read, @expected_message) end end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 677 def matches_uniqueness_without_scopes? if existing_value_read.blank? update_existing_record!(arbitrary_non_blank_value) end disallows_value_of(existing_value_read, @expected_message) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 934 def model @given_record.class end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 749 def model_class?(model_name) model_name.constantize.ancestors.include?(::ActiveRecord::Base) rescue NameError false end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 384 def new_record unless defined?(@new_record) build_new_record end @new_record end
Also aliased as: subject
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 815 def next_scalar_value_for(scope, previous_value) column = column_for(scope) if column.type == :uuid SecureRandom.uuid elsif defined_as_enum?(scope) available_values = available_enum_values_for(scope, previous_value) available_values.keys.last elsif polymorphic_type_attribute?(scope, previous_value) Uniqueness::TestModels.create(previous_value).to_s elsif previous_value.respond_to?(:next) previous_value.next elsif previous_value.respond_to?(:to_datetime) previous_value.to_datetime.in(60).next elsif boolean_value?(previous_value) !previous_value else previous_value.to_s.next end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 807 def next_value_for(scope, previous_value) if previous_value.is_a?(Array) [next_scalar_value_for(scope, previous_value[0])] else next_scalar_value_for(scope, previous_value) end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 851 def polymorphic_type_attribute?(scope, previous_value) scope.to_s =~ /_type$/ && model_class?(previous_value) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 453 def scopes_match? actual_sets_of_scopes.empty? && expected_scopes.empty? || actual_sets_of_scopes.any? { |scopes| scopes == expected_scopes } end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 671 def scopes_missing_on_model @_scopes_missing_on_model ||= expected_scopes.reject do |scope| model.method_defined?("#{scope}=") end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 665 def scopes_present_on_model @_scopes_present_on_model ||= expected_scopes.select do |scope| model.method_defined?("#{scope}=") end end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 861 def set_attribute_on!(record_type, record, attribute_name, value) attribute_setter = build_attribute_setter( record, attribute_name, value, ) attribute_setter.set! @attribute_setters[record_type] << attribute_setter end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 872 def set_attribute_on_existing_record!(attribute_name, value) set_attribute_on!( :existing_record, existing_record, attribute_name, value, ) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 881 def set_attribute_on_new_record!(attribute_name, value) set_attribute_on!( :new_record, new_record, attribute_name, value, ) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 776 def setting_next_value_for(scope) previous_value = @all_records.map(&scope).compact.max next_value = if previous_value.blank? dummy_value_for(scope) else next_value_for(scope, previous_value) end set_attribute_on_new_record!(scope, next_value) yield ensure set_attribute_on_new_record!(scope, previous_value) end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 743 def should_test_case_sensitivity? case_sensitivity_strategy != :ignore && existing_value_read.respond_to?(:swapcase) && !existing_value_read.empty? end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 550 def update_existing_record!(value) if existing_value_read != value set_attribute_on_existing_record!(@attribute, value) # It would be nice if we could ensure that the record was valid, # but that would break users' existing tests existing_record.save(validate: false) end true end
Source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 404 def validations model.validators_on(@attribute).select do |validator| validator.is_a?(::ActiveRecord::Validations::UniquenessValidator) end end