123456789_123456789_123456789_123456789_123456789_

Class: Mongoid::Validatable::UniquenessValidator

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ActiveModel::EachValidator
Instance Chain:
self, Queryable, ActiveModel::EachValidator
Inherits: ActiveModel::EachValidator
  • Object
Defined in: lib/mongoid/validatable/uniqueness.rb

Overview

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

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

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

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

Examples:

Define the uniqueness validator.

class Person
  include Mongoid::Document
  field :title

  validates_uniqueness_of :title
end

Instance Attribute Summary

Instance Method Summary

Queryable - Included

#with_query

Wrap the validation inside the an execution block that alert’s the client not to clear its persistence options.

Instance Attribute Details

#case_sensitive?true | false (readonly, private)

This method is for internal use only.

Should the uniqueness validation be case sensitive?

Examples:

Is the validation case sensitive?

validator.case_sensitive?

Returns:

  • (true | false)

    If the validation is case sensitive.

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 79

def case_sensitive?
  !(options[:case_sensitive] == false)
end

Instance Method Details

#add_error(document, attribute, value) (private)

This method is for internal use only.

Add the error to the document.

Examples:

Add the error.

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

Parameters:

  • document (Document)

    The document to validate.

  • attribute (Symbol)

    The name of the attribute.

  • value (Object)

    The value of the object.

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 65

def add_error(document, attribute, value)
  document.errors.add(
    attribute, :taken, **options.except(:case_sensitive, :scope).merge(value: value)
  )
end

#create_criteria(base, document, attribute, value) ⇒ Criteria (private)

This method is for internal use only.

Create the validation criteria.

Examples:

Create the criteria.

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

Parameters:

  • base (Class | Proxy)

    The base to execute the criteria from.

  • document (Document)

    The document to validate.

  • attribute (Symbol)

    The name of the attribute.

  • value (Object)

    The value of the object.

Returns:

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 96

def create_criteria(base, document, attribute, value)
  criteria = scope(base.unscoped, document, attribute)
  field = document.fields[document.database_field_name(attribute)]

  # In the past, we were calling value.mongoize in all cases, which
  # would call Object's mongoize method. This is a problem for StringifiedSymbol,
  # because a Symbol should mongoize to a String, but calling .mongoize
  # on a Symbol mongoizes it to a Symbol.
  # Therefore, we call the field's mongoize in all cases except when the
  # field is localized, because by the time we arrive at this code, the
  # value is already in the form of { lang => translation } and calling
  # the field's mongoize will nest that further into { lang =>
  # "\{ lang => translation \}"} (assuming the field type is a string).
  # Therefore, we call Object's mongoize method so it returns the hash as
  # it is.
  mongoized = field.try(:localized?) ? value.mongoize : field.mongoize(value)
  criteria.selector.update(criterion(document, attribute, mongoized))
  criteria
end

#criterion(document, attribute, value) ⇒ Criteria (private)

This method is for internal use only.

Get the default criteria for checking uniqueness.

Examples:

Get the criteria.

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

Parameters:

  • document (Document)

    The document to validate.

  • attribute (Symbol)

    The name of the attribute.

  • value (Object)

    The value of the object.

Returns:

  • (Criteria)

    The uniqueness criteria.

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 128

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) ⇒ Object | Regexp (private)

This method is for internal use only.

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

Examples:

Filter the value.

validator.filter("testing")

Parameters:

  • value (Object)

    The value to filter.

Returns:

  • (Object | Regexp)

    The value, filtered or not.

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 154

def filter(value)
  !case_sensitive? && value ? /\A#{Regexp.escape(value.to_s)}\z/i : value
end

#localized?(document, attribute) ⇒ true | false (private)

This method is for internal use only.

Is the attribute localized?

Examples:

Is the attribute localized?

validator.localized?(doc, :field)

Parameters:

  • document (Document)

    The document getting validated.

  • attribute (Symbol)

    The attribute to validate.

Returns:

  • (true | false)

    If the attribute is localized.

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 300

def localized?(document, attribute)
  document.fields[document.database_field_name(attribute)].try(:localized?)
end

#scope(criteria, document, _attribute) ⇒ Criteria (private)

This method is for internal use only.

Scope the criteria to the scope options provided.

Examples:

Scope the criteria.

validator.scope(criteria, document)

Parameters:

  • criteria (Criteria)

    The criteria to scope.

  • document (Document)

    The document being validated.

Returns:

[ GitHub ]

  
# 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) ⇒ true | false (private)

This method is for internal use only.

Scope reference has changed?

Examples:

Has scope reference changed?

validator.scope_value_changed?(doc)

Parameters:

  • document (Document)

    The embedded document.

Returns:

  • (true | false)

    If the scope reference has changed.

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 201

def scope_value_changed?(document)
  Array.wrap(options[:scope]).any? do |item|
    document.send("attribute_changed?", item.to_s)
  end
end

#skip_validation?(document) ⇒ true | false (private)

This method is for internal use only.

Should validation be skipped?

Examples:

Should the validation be skipped?

validator.skip_validation?(doc)

Parameters:

  • document (Document)

    The embedded document.

Returns:

  • (true | false)

    If the validation should be skipped.

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 187

def skip_validation?(document)
  !document._parent || document.embedded_one?
end

#to_validate(document, attribute, value) ⇒ Array<Object, Object> (private)

This method is for internal use only.

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.

Examples:

Get the name and key to validate.

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

Parameters:

  • document (Document)

    The doc getting validated.

  • attribute (Symbol)

    The attribute getting validated.

  • value (Object)

    The value of the attribute.

Returns:

  • (Array<Object, Object>)

    The field and value.

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 222

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_each(document, attribute, value) ⇒ Errors

Validate the document for uniqueness violations.

Examples:

Validate the document.

validate_each(person, :title, "Sir")

Parameters:

  • document (Document)

    The document to validate.

  • attribute (Symbol)

    The field to validate on.

  • value (Object)

    The value of the field.

Returns:

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 41

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

#validate_embedded(document, attribute, value) (private)

This method is for internal use only.

Validate an embedded document.

Examples:

Validate the embedded document.

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

Parameters:

  • document (Document)

    The document.

  • attribute (Symbol)

    The attribute name.

  • value (Object)

    The value.

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 241

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]
  criteria = criteria.read(mode: :primary).limit(2)
  add_error(document, attribute, value) if criteria.count > 1
end

#validate_root(document, attribute, value) (private)

This method is for internal use only.

Validate a root document.

Examples:

Validate the root document.

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

Parameters:

  • document (Document)

    The document.

  • attribute (Symbol)

    The attribute name.

  • value (Object)

    The value.

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 260

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) ⇒ true | false (private)

Are we required to validate the document?

Examples:

Is validation needed?

validator.validation_required?(doc, :field)

Parameters:

  • document (Document)

    The document getting validated.

  • attribute (Symbol)

    The attribute to validate.

Returns:

  • (true | false)

    If we need to validate.

[ GitHub ]

  
# File 'lib/mongoid/validatable/uniqueness.rb', line 283

def validation_required?(document, attribute)
  document.new_record? ||
    document.send("attribute_changed?", attribute.to_s) ||
    scope_value_changed?(document)
end