class Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher
@private
Public Class Methods
new(attribute)
click to toggle source
Calls superclass method
Shoulda::Matchers::ActiveModel::ValidationMatcher::new
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 263 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
Public Instance Methods
allow_blank()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 302 def allow_blank @options[:allow_blank] = true self end
allow_nil()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 293 def allow_nil @options[:allow_nil] = true self end
case_insensitive()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 283 def case_insensitive @options[:case_sensitivity_strategy] = :insensitive self end
expects_to_allow_blank?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 307 def expects_to_allow_blank? @options[:allow_blank] end
expects_to_allow_nil?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 298 def expects_to_allow_nil? @options[:allow_nil] end
ignoring_case_sensitivity()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 288 def ignoring_case_sensitivity @options[:case_sensitivity_strategy] = :ignore self end
matches?(given_record)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 323 def matches?(given_record) @given_record = given_record @all_records = model.all validate_attribute_present_on_model? && validate_scopes_present_on_model? && validate_scopes_match? && validate_two_records_with_same_non_blank_value_cannot_coexist? && validate_case_sensitivity? && validate_after_scope_change? && allows_nil? && allows_blank? ensure Uniqueness::TestModels.remove_all end
scoped_to(*scopes)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 278 def scoped_to(*scopes) @options[:scopes] = [*scopes].flatten self end
simple_description()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 311 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
build_allow_or_disallow_value_matcher(args)
click to toggle source
Calls superclass method
Shoulda::Matchers::ActiveModel::ValidationMatcher#build_allow_or_disallow_value_matcher
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 349 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
failure_reason()
click to toggle source
Calls superclass method
Shoulda::Matchers::ActiveModel::ValidationMatcher#failure_reason
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 341 def failure_reason @failure_reason || super end
failure_reason_when_negated()
click to toggle source
Calls superclass method
Shoulda::Matchers::ActiveModel::ValidationMatcher#failure_reason_when_negated
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 345 def failure_reason_when_negated @failure_reason_when_negated || super end
Private Instance Methods
actual_sets_of_scopes()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 444 def actual_sets_of_scopes validations.map do |validation| Array.wrap(validation.options[:scope]) end.reject(&:empty?) end
all_scopes_are_booleans?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 712 def all_scopes_are_booleans? @options[:scopes].all? do |scope| @all_records.map(&scope).all? { |s| boolean_value?(s) } end end
all_scopes_present_on_model?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 586 def all_scopes_present_on_model? scopes_missing_on_model.none? end
allows_blank?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 459 def allows_blank? if expects_to_allow_blank? update_existing_record!('') allows_value_of('', @expected_message) else true end end
allows_nil?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 450 def allows_nil? if expects_to_allow_nil? update_existing_record!(nil) allows_value_of(nil, @expected_message) else true end end
arbitrary_non_blank_value()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 521 def arbitrary_non_blank_value limit = column_limit_for(@attribute) non_blank_value = 'an arbitrary value' if limit && limit < non_blank_value.length 'x' * limit else non_blank_value end end
attribute_changed_value_message()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 857 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
attribute_names_under_test()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 775 def attribute_names_under_test [@attribute] + expected_scopes end
attribute_present_on_model?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 559 def attribute_present_on_model? model.method_defined?("#{attribute}=") || model.columns_hash.key?(attribute.to_s) end
attribute_setter_descriptions_for_new_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 902 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
attribute_setter_for_existing_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 766 def attribute_setter_for_existing_record @attribute_setters[:existing_record].last end
attribute_setters_for_new_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 770 def attribute_setters_for_new_record @attribute_setters[:new_record] + [last_attribute_setter_used_on_new_record] end
available_enum_values_for(scope, previous_value)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 731 def available_enum_values_for(scope, previous_value) new_record.defined_enums[scope.to_s].reject do |key, _| key == previous_value end end
boolean_value?(value)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 718 def boolean_value?(value) [true, false].include?(value) end
build_attribute_setter(record, attribute_name, value)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 779 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
build_new_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 536 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
case_sensitivity_strategy()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 359 def case_sensitivity_strategy @options[:case_sensitivity_strategy] end
column_for(scope)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 801 def column_for(scope) model.columns_hash[scope.to_s] end
column_limit_for(attribute)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 805 def column_limit_for(attribute) column_for(attribute).try(:limit) end
create_existing_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 495 def create_existing_record @given_record.tap do |existing_record| ensure_secure_password_set(existing_record) existing_record.save(validate: false) end rescue ::ActiveRecord::StatementInvalid => error raise ExistingRecordInvalid.create(underlying_exception: error) end
defined_as_enum?(scope)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 722 def defined_as_enum?(scope) model.respond_to?(:defined_enums) && new_record.defined_enums[scope.to_s] end
description_for_attribute_setter(attribute_setter, same_as_existing: nil)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 872 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
description_for_case_sensitive_qualifier()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 372 def description_for_case_sensitive_qualifier case case_sensitivity_strategy when :sensitive ' case-sensitively' when :insensitive ' case-insensitively' else '' end end
descriptions_for_attribute_setters_for_new_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 898 def descriptions_for_attribute_setters_for_new_record attribute_setter_descriptions_for_new_record.to_sentence end
dummy_scalar_value_for(column)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 679 def dummy_scalar_value_for(column) Shoulda::Matchers::Util.dummy_value_for(column.type) end
dummy_value_for(scope)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 669 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
ensure_secure_password_set(instance)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 504 def ensure_secure_password_set(instance) Shoulda::Matchers::RailsShim.digestible_attributes_in(instance). each do |attribute| instance.send("#{attribute}=", 'password') instance.send("#{attribute}_confirmation=", 'password') end end
existing_and_new_values_are_same?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 915 def existing_and_new_values_are_same? last_value_set_on_new_record == existing_value_written end
existing_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 468 def existing_record unless defined?(@existing_record) find_or_create_existing_record end @existing_record end
existing_value_read()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 789 def existing_value_read existing_record.public_send(@attribute) end
existing_value_written()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 793 def existing_value_written if attribute_setter_for_existing_record attribute_setter_for_existing_record.value_written else existing_value_read end end
expected_scopes()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 440 def expected_scopes Array.wrap(@options[:scopes]) end
failure_message_preface()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 813 def failure_message_preface 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" else if 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 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
find_existing_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 485 def find_existing_record record = model.first if record.present? record else nil end end
find_or_create_existing_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 476 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
has_secure_password?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 532 def has_secure_password? Shoulda::Matchers::RailsShim.has_secure_password?(subject, @attribute) end
inspected_actual_scopes()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 421 def inspected_actual_scopes inspected_actual_sets_of_scopes.to_sentence( words_connector: " or ", last_word_connector: ", or" ) end
inspected_actual_sets_of_scopes()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 428 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
inspected_expected_scopes()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 417 def inspected_expected_scopes expected_scopes.map(&:inspect).to_sentence end
inspected_scopes_missing_on_model()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 596 def inspected_scopes_missing_on_model scopes_missing_on_model.map(&:inspect) end
last_attribute_setter_used_on_new_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 919 def last_attribute_setter_used_on_new_record last_submatcher_run.last_attribute_setter_used end
last_value_set_on_new_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 923 def last_value_set_on_new_record last_submatcher_run.last_value_set end
model()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 809 def model @given_record.class end
model_class?(model_name)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 637 def model_class?(model_name) model_name.constantize.ancestors.include?(::ActiveRecord::Base) rescue NameError false end
new_record()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 363 def new_record unless defined?(@new_record) build_new_record end @new_record end
Also aliased as: subject
next_scalar_value_for(scope, previous_value)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 691 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.next elsif boolean_value?(previous_value) !previous_value else previous_value.to_s.next end end
next_value_for(scope, previous_value)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 683 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
polymorphic_type_attribute?(scope, previous_value)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 727 def polymorphic_type_attribute?(scope, previous_value) scope.to_s =~ /_type$/ && model_class?(previous_value) end
scopes_match?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 412 def scopes_match? actual_sets_of_scopes.empty? && expected_scopes.empty? || actual_sets_of_scopes.any? { |scopes| scopes == expected_scopes } end
scopes_missing_on_model()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 590 def scopes_missing_on_model @_missing_scopes ||= expected_scopes.select do |scope| !model.method_defined?("#{scope}=") end end
set_attribute_on!(record_type, record, attribute_name, value)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 737 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
set_attribute_on_existing_record!(attribute_name, value)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 748 def set_attribute_on_existing_record!(attribute_name, value) set_attribute_on!( :existing_record, existing_record, attribute_name, value ) end
set_attribute_on_new_record!(attribute_name, value)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 757 def set_attribute_on_new_record!(attribute_name, value) set_attribute_on!( :new_record, new_record, attribute_name, value ) end
should_validate_case_sensitivity?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 631 def should_validate_case_sensitivity? case_sensitivity_strategy != :ignore && existing_value_read.respond_to?(:swapcase) && !existing_value_read.empty? end
update_existing_record!(value)
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 512 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 end
validate_after_scope_change?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 643 def validate_after_scope_change? if expected_scopes.empty? || all_scopes_are_booleans? true else expected_scopes.all? do |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) if allows_value_of(existing_value_read, @expected_message) set_attribute_on_new_record!(scope, previous_value) true else false end end end end
validate_attribute_present_on_model?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 549 def validate_attribute_present_on_model? if attribute_present_on_model? true else @failure_reason = ":#{attribute} does not seem to be an attribute on #{model.name}." false end end
validate_case_sensitivity?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 608 def validate_case_sensitivity? if should_validate_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
validate_scopes_match?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 389 def validate_scopes_match? if scopes_match? true else @failure_reason = 'Expected the validation' if expected_scopes.empty? @failure_reason << ' not to be scoped to anything' else @failure_reason << " to be scoped to #{inspected_expected_scopes}" end if actual_sets_of_scopes.empty? @failure_reason << ', but it was not scoped to anything.' else @failure_reason << ', but it was scoped to ' @failure_reason << "#{inspected_actual_scopes} instead." end false end end
validate_scopes_present_on_model?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 564 def validate_scopes_present_on_model? if all_scopes_present_on_model? true else reason = '' reason << inspected_scopes_missing_on_model.to_sentence if inspected_scopes_missing_on_model.many? reason << " do not seem to be attributes" else reason << " does not seem to be an attribute" end reason << " on #{model.name}." @failure_reason = reason false end end
validate_two_records_with_same_non_blank_value_cannot_coexist?()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 600 def validate_two_records_with_same_non_blank_value_cannot_coexist? if existing_value_read.blank? update_existing_record!(arbitrary_non_blank_value) end disallows_value_of(existing_value_read, @expected_message) end
validations()
click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 383 def validations model._validators[@attribute].select do |validator| validator.is_a?(::ActiveRecord::Validations::UniquenessValidator) end end