class Shoulda::Matchers::ActiveModel::ValidateUniquenessOfMatcher

@private

Public Class Methods

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

Public Instance Methods

allow_nil() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 198
def allow_nil
  @options[:allow_nil] = true
  self
end
case_insensitive() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 193
def case_insensitive
  @options[:case_insensitive] = true
  self
end
description() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 203
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_model/validate_uniqueness_of_matcher.rb, line 211
def matches?(subject)
  @subject = subject.class.new
  @expected_message ||= :taken
  set_scoped_attributes &&
    validate_everything_except_duplicate_nils? &&
    validate_after_scope_change? &&
    allows_nil?
end
scoped_to(*scopes) click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 183
def scoped_to(*scopes)
  @options[:scopes] = [*scopes].flatten
  self
end
with_message(message) click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 188
def with_message(message)
  @expected_message = message
  self
end

Private Instance Methods

allows_nil?() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 222
def allows_nil?
  if @options[:allow_nil]
    ensure_nil_record_in_database
    allows_value_of(nil, @expected_message)
  else
    true
  end
end
class_name() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 352
def class_name
  @subject.class.name
end
correct_type_for_column(column) click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 340
def correct_type_for_column(column)
  if column.type == :string || column.type == :binary
    '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_model/validate_uniqueness_of_matcher.rb, line 249
def create_record_in_database(options = {})
  if options[:nil_value]
    value = nil
  else
    value = 'a'
  end

  @subject.class.new.tap do |instance|
    instance.__send__("#{@attribute}=", value)
    
    other_non_nullable_columns.each do |non_nullable_column|
      instance.__send__("#{non_nullable_column.name}=", correct_type_for_column(non_nullable_column))
    end
    
    if has_secure_password?
      instance.password = 'password'
      instance.password_confirmation = 'password'
    end
    instance.save(validate: false)
  end
end
create_record_without_nil() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 300
def create_record_without_nil
  @existing_record = create_record_in_database
end
ensure_nil_record_in_database() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 239
def ensure_nil_record_in_database
  unless existing_record_is_nil?
    create_record_in_database(nil_value: true)
  end
end
existing_record() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 231
def existing_record
  @existing_record ||= first_instance
end
existing_record_is_nil?() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 245
def existing_record_is_nil?
  @existing_record.present? && existing_value.nil?
end
existing_value() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 356
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_model/validate_uniqueness_of_matcher.rb, line 235
def first_instance
  @subject.class.first || create_record_in_database
end
has_secure_password?() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 271
def has_secure_password?
  @subject.class.ancestors.map(&:to_s).include?('ActiveModel::SecurePassword::InstanceMethodsOnActivation')
end
other_non_nullable_columns() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 364
def other_non_nullable_columns
  @subject.class.columns.select do |column|
    column.name != @attribute && !column.null && !column.primary
  end
end
set_scoped_attributes() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 275
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_model/validate_uniqueness_of_matcher.rb, line 304
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 =
        if 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

      @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?() click to toggle source
# File lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb, line 292
def validate_everything_except_duplicate_nils?
  if @options[:allow_nil] && existing_value.nil?
    create_record_without_nil
  end

  disallows_value_of(existing_value, @expected_message)
end