class Mongoid::Validatable::UniquenessValidator

Validates whether or not a field is unique against the documents in the database.

@example Define the uniqueness validator.

class Person
  include Mongoid::Document
  field :title

  validates_uniqueness_of :title
end

It is also possible to limit the uniqueness constraint to a set of records matching certain conditions:

class Person
  include Mongoid::Document
  field :title
  field :active, type: Boolean

  validates_uniqueness_of :title, conditions: -> {where(active: true)}
end

Public Instance Methods

validate_each(document, attribute, value) click to toggle source

Validate the document for uniqueness violations.

@example Validate the document.

validate_each(person, :title, "Sir")

@param [ Document ] document The document to validate. @param [ Symbol ] attribute The field to validate on. @param [ Object ] value The value of the field.

@return [ Errors ] The errors.

@since 1.0.0

# File lib/mongoid/validatable/uniqueness.rb, line 43
def validate_each(document, attribute, value)
  with_query(document) do
    attrib, val = to_validate(document, attribute, value)
    return unless validation_required?(document, attrib)
    if document.embedded?
      validate_embedded(document, attrib, val)
    else
      validate_root(document, attrib, val)
    end
  end
end

Private Instance Methods

add_error(document, attribute, value) click to toggle source

Add the error to the document.

@api private

@example Add the error.

validator.add_error(doc, :name, "test")

@param [ Document ] document The document to validate. @param [ Symbol ] attribute The name of the attribute. @param [ Object ] value The value of the object.

@since 2.4.10

# File lib/mongoid/validatable/uniqueness.rb, line 69
def add_error(document, attribute, value)
  document.errors.add(
    attribute, :taken, **options.except(:case_sensitive, :scope).merge(value: value)
  )
end
case_sensitive?() click to toggle source

Should the uniqueness validation be case sensitive?

@api private

@example Is the validation case sensitive?

validator.case_sensitive?

@return [ true, false ] If the validation is case sensitive.

@since 2.3.0

# File lib/mongoid/validatable/uniqueness.rb, line 85
def case_sensitive?
  !(options[:case_sensitive] == false)
end
create_criteria(base, document, attribute, value) click to toggle source

Create the validation criteria.

@api private

@example Create the criteria.

validator.create_criteria(User, user, :name, "syd")

@param [ Class, Proxy ] base The base to execute the criteria from. @param [ Document ] document The document to validate. @param [ Symbol ] attribute The name of the attribute. @param [ Object ] value The value of the object.

@return [ Criteria ] The criteria.

@since 2.4.10

# File lib/mongoid/validatable/uniqueness.rb, line 104
def create_criteria(base, document, attribute, value)
  criteria = scope(base.unscoped, document, attribute)
  criteria.selector.update(criterion(document, attribute, value.mongoize))
  criteria
end
criterion(document, attribute, value) click to toggle source

Get the default criteria for checking uniqueness.

@api private

@example Get the criteria.

validator.criterion(person, :title, "Sir")

@param [ Document ] document The document to validate. @param [ Symbol ] attribute The name of the attribute. @param [ Object ] value The value of the object.

@return [ Criteria ] The uniqueness criteria.

@since 2.3.0

# File lib/mongoid/validatable/uniqueness.rb, line 124
def criterion(document, attribute, value)
  field = document.database_field_name(attribute)

  if value && localized?(document, field)
    conditions = (value || {}).inject([]) { |acc, (k,v)| acc << { "#{field}.#{k}" => filter(v) }}
    selector = { "$or" => conditions }
  else
    selector = { field => filter(value) }
  end

  if document.persisted? && !document.embedded?
    selector.merge!(_id: { "$ne" => document._id })
  end
  selector
end
filter(value) click to toggle source

Filter the value based on whether the check is case sensitive or not.

@api private

@example Filter the value.

validator.filter("testing")

@param [ Object ] value The value to filter.

@return [ Object, Regexp ] The value, filtered or not.

@since 2.3.0

# File lib/mongoid/validatable/uniqueness.rb, line 152
def filter(value)
  !case_sensitive? && value ? /\A#{Regexp.escape(value.to_s)}\z/i : value
end
localized?(document, attribute) click to toggle source

Is the attribute localized?

@api private

@example Is the attribute localized?

validator.localized?(doc, :field)

@param [ Document ] document The document getting validated. @param [ Symbol ] attribute The attribute to validate.

@return [ true, false ] If the attribute is localized.

@since 4.0.0

# File lib/mongoid/validatable/uniqueness.rb, line 313
def localized?(document, attribute)
  document.fields[document.database_field_name(attribute)].try(:localized?)
end
scope(criteria, document, _attribute) click to toggle source

Scope the criteria to the scope options provided.

@api private

@example Scope the criteria.

validator.scope(criteria, document)

@param [ Criteria ] criteria The criteria to scope. @param [ Document ] document The document being validated.

@return [ Criteria ] The scoped criteria.

@since 2.3.0

# File lib/mongoid/validatable/uniqueness.rb, line 169
def scope(criteria, document, _attribute)
  Array.wrap(options[:scope]).each do |item|
    name = document.database_field_name(item)
    criteria = criteria.where(item => document.attributes[name])
  end
  criteria
end
scope_value_changed?(document) click to toggle source

Scope reference has changed?

@api private

@example Has scope reference changed?

validator.scope_value_changed?(doc)

@param [ Document ] document The embedded document.

@return [ true, false ] If the scope reference has changed.

@since

# File lib/mongoid/validatable/uniqueness.rb, line 205
def scope_value_changed?(document)
  Array.wrap(options[:scope]).any? do |item|
    document.send("attribute_changed?", item.to_s)
  end
end
skip_validation?(document) click to toggle source

Should validation be skipped?

@api private

@example Should the validation be skipped?

validator.skip_validation?(doc)

@param [ Document ] document The embedded document.

@return [ true, false ] If the validation should be skipped.

@since 2.3.0

# File lib/mongoid/validatable/uniqueness.rb, line 189
def skip_validation?(document)
  !document._parent || document.embedded_one?
end
to_validate(document, attribute, value) click to toggle source

Get the name of the field and the value to validate. This is for the case when we validate an association via the association name and not the key, we need to send the key name and value to the db, not the association object.

@api private

@example Get the name and key to validate.

validator.to_validate(doc, :parent, Parent.new)

@param [ Document ] document The doc getting validated. @param [ Symbol ] attribute The attribute getting validated. @param [ Object ] value The value of the attribute.

@return [ Array<Object, Object> ] The field and value.

@since 2.4.4

# File lib/mongoid/validatable/uniqueness.rb, line 228
def to_validate(document, attribute, value)
  association = document.relations[attribute.to_s]
  if association && association.stores_foreign_key?
    [ association.foreign_key, value && value._id ]
  else
    [ attribute, value ]
  end
end
validate_embedded(document, attribute, value) click to toggle source

Validate an embedded document.

@api private

@example Validate the embedded document.

validator.validate_embedded(doc, :name, "test")

@param [ Document ] document The document. @param [ Symbol ] attribute The attribute name. @param [ Object ] value The value.

@since 2.4.10

# File lib/mongoid/validatable/uniqueness.rb, line 249
def validate_embedded(document, attribute, value)
  return if skip_validation?(document)
  relation = document._parent.send(document.association_name)
  criteria = create_criteria(relation, document, attribute, value)
  criteria = criteria.merge(options[:conditions].call) if options[:conditions]
  add_error(document, attribute, value) if criteria.count > 1
end
validate_root(document, attribute, value) click to toggle source

Validate a root document.

@api private

@example Validate the root document.

validator.validate_root(doc, :name, "test")

@param [ Document ] document The document. @param [ Symbol ] attribute The attribute name. @param [ Object ] value The value.

@since 2.4.10

# File lib/mongoid/validatable/uniqueness.rb, line 269
def validate_root(document, attribute, value)
  klass = document.class

  while klass.superclass.respond_to?(:validators) && klass.superclass.validators.include?(self)
    klass = klass.superclass
  end
  criteria = create_criteria(klass, document, attribute, value)
  criteria = criteria.merge(options[:conditions].call) if options[:conditions]

  if criteria.read(mode: :primary).exists?
    add_error(document, attribute, value)
  end
end
validation_required?(document, attribute) click to toggle source

Are we required to validate the document?

@example Is validation needed?

validator.validation_required?(doc, :field)

@param [ Document ] document The document getting validated. @param [ Symbol ] attribute The attribute to validate.

@return [ true, false ] If we need to validate.

@since 2.4.4

# File lib/mongoid/validatable/uniqueness.rb, line 294
def validation_required?(document, attribute)
  document.new_record? ||
    document.send("attribute_changed?", attribute.to_s) ||
    scope_value_changed?(document)
end