class Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher

@private

Public Class Methods

new(attribute) click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 202
def initialize(attribute)
  super(attribute)
  @options = {}
end

Public Instance Methods

allow_blank() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 227
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 222
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 217
def case_insensitive
  @options[:case_insensitive] = true
  self
end
description() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 232
def description
  result = "require "
  result << "case sensitive " unless @options[:case_insensitive]
  result << "unique value for #{@attribute}"
  result << " scoped to #{@options[:scopes].join(', ')}" if @options[:scopes].present?
  result
end
matches?(subject) click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 240
def matches?(subject)
  @original_subject = subject
  @subject = subject.class.new
  @expected_message ||= :taken

  set_scoped_attributes &&
    validate_everything_except_duplicate_nils_or_blanks? &&
    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 207
def scoped_to(*scopes)
  @options[:scopes] = [*scopes].flatten
  self
end
with_message(message) click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 212
def with_message(message)
  @expected_message = message
  self
end

Private Instance Methods

allows_blank?() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 265
def allows_blank?
  if @options[:allow_blank]
    ensure_blank_record_in_database
    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 256
def allows_nil?
  if @options[:allow_nil]
    ensure_nil_record_in_database
    allows_value_of(nil, @expected_message)
  else
    true
  end
end
available_enum_values_for(scope, previous_value) click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 422
def available_enum_values_for(scope, previous_value)
  @subject.defined_enums[scope.to_s].reject do |key, _|
    key == previous_value
  end
end
class_name() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 428
def class_name
  @subject.class.name
end
correct_type_for_column(column) click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 395
def correct_type_for_column(column)
  if column.type == :string
    '0'
  elsif column.type == :datetime
    DateTime.now
  elsif column.type == :uuid
    SecureRandom.uuid
  else
    0
  end
end
create_record_in_database(options = {}) click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 302
def create_record_in_database(options = {})
  @original_subject.tap do |instance|
    instance.__send__("#{@attribute}=", value_for_new_record(options))
    ensure_secure_password_set(instance)
    instance.save(validate: false)
    @created_record = instance
  end
end
create_record_with_value() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 356
def create_record_with_value
  @existing_record = create_record_in_database
end
ensure_blank_record_in_database() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 288
def ensure_blank_record_in_database
  unless existing_record_is_blank?
    create_record_in_database(blank_value: true)
  end
end
ensure_nil_record_in_database() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 282
def ensure_nil_record_in_database
  unless existing_record_is_nil?
    create_record_in_database(nil_value: true)
  end
end
ensure_secure_password_set(instance) click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 311
def ensure_secure_password_set(instance)
  if has_secure_password?
    instance.password = "password"
    instance.password_confirmation = "password"
  end
end
existing_record() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 274
def existing_record
  @existing_record ||= first_instance
end
existing_record_is_blank?() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 298
def existing_record_is_blank?
  @existing_record.present? && existing_value.strip == ''
end
existing_record_is_nil?() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 294
def existing_record_is_nil?
  @existing_record.present? && existing_value.nil?
end
existing_value() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 432
def existing_value
  value = existing_record.__send__(@attribute)
  if @options[:case_insensitive] && value.respond_to?(:swapcase!)
    value.swapcase!
  end
  value
end
first_instance() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 278
def first_instance
  @subject.class.first || create_record_in_database
end
has_secure_password?() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 326
def has_secure_password?
  @subject.class.ancestors.map(&:to_s).include?('ActiveModel::SecurePassword::InstanceMethodsOnActivation')
end
model_class?(model_name) click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 360
def model_class?(model_name)
  model_name.constantize.ancestors.include?(::ActiveRecord::Base)
rescue NameError
  false
end
next_value_for(scope, previous_value) click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 407
def next_value_for(scope, previous_value)
  if @subject.class.respond_to?(:defined_enums) && @subject.defined_enums[scope.to_s]
    available_values = available_enum_values_for(scope, previous_value)
    available_values.keys.last
  elsif scope.to_s =~ /_type$/ && model_class?(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
  else
    previous_value.to_s.next
  end
end
set_scoped_attributes() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 330
def set_scoped_attributes
  if @options[:scopes].present?
    @options[:scopes].all? do |scope|
      setter = :"#{scope}="
      if @subject.respond_to?(setter)
        @subject.__send__(setter, existing_record.__send__(scope))
        true
      else
        @failure_message = "#{class_name} doesn't seem to have a #{scope} attribute."
        false
      end
    end
  else
    true
  end
end
validate_after_scope_change?() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 366
def validate_after_scope_change?
  if @options[:scopes].blank?
    true
  else
    all_records = @subject.class.all
    @options[:scopes].all? do |scope|
      previous_value = all_records.map(&scope).max

      # Assume the scope is a foreign key if the field is nil
      previous_value ||= correct_type_for_column(@subject.class.columns_hash[scope.to_s])

      next_value = next_value_for(scope, previous_value)

      @subject.__send__("#{scope}=", next_value)

      if allows_value_of(existing_value, @expected_message)
        @subject.__send__("#{scope}=", previous_value)

        @failure_message_when_negated <<
          " (with different value of #{scope})"
        true
      else
        @failure_message << " (with different value of #{scope})"
        false
      end
    end
  end
end
validate_everything_except_duplicate_nils_or_blanks?() click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 347
def validate_everything_except_duplicate_nils_or_blanks?
  if (@options[:allow_nil] && existing_value.nil?) ||
     (@options[:allow_blank] && existing_value.blank?)
    create_record_with_value
  end

  disallows_value_of(existing_value, @expected_message)
end
value_for_new_record(options = {}) click to toggle source
# File lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb, line 318
def value_for_new_record(options = {})
  case
    when options[:nil_value] then nil
    when options[:blank_value] then ''
    else 'a'
  end
end