class Mongoid::Validatable::AssociatedValidator

Validates whether or not an association is valid or not. Will correctly handle has one and has many associations.

@example Set up the association validations.

class Person
  include Mongoid::Document
  embeds_one :name
  embeds_many :addresses

  validates_associated :name, :addresses
end

Public Instance Methods

attributes() click to toggle source

Required by ‘validates_with` so that the validator gets added to the correct attributes.

# File lib/mongoid/validatable/associated.rb, line 22
def attributes
  options[:attributes]
end
validate(document) click to toggle source

Checks that the named associations of the given record (‘attributes`) are valid. This does NOT load the associations from the database, and will only validate records that are dirty or unpersisted.

If anything is not valid, appropriate errors will be added to the ‘document` parameter.

@param [ Mongoid::Document ] document the document with the

associations to validate.
# File lib/mongoid/validatable/associated.rb, line 36
def validate(document)
  options[:attributes].each do |attr_name|
    validate_association(document, attr_name)
  end
end

Private Instance Methods

get_target_documents(target) click to toggle source

Examine the given target object and return an array of documents (possibly empty) that the target represents.

@param [ Array | Mongoid::Document | Mongoid::Association::Proxy | HasMany::Enumerable ] target

the target object to examine.

@return [ Array<Mongoid::Document> ] the list of documents

# File lib/mongoid/validatable/associated.rb, line 97
def get_target_documents(target)
  if target.respond_to?(:_loaded?)
    get_target_documents_for_has_many(target)
  else
    get_target_documents_for_other(target)
  end
end
get_target_documents_for_has_many(target) click to toggle source

Returns the list of all currently in-memory values held by the target. The target will not be loaded.

@param [ HasMany::Enumerable ] target the target that will

be examined for in-memory documents.

@return [ Array<Mongoid::Document> ] the in-memory documents

held by the target.
# File lib/mongoid/validatable/associated.rb, line 113
def get_target_documents_for_has_many(target)
  [ *target._loaded.values, *target._added.values ]
end
get_target_documents_for_other(target) click to toggle source

Returns the target as an array. If the target represents a single value, it is wrapped in an array.

@param [ Array | Mongoid::Document | Mongoid::Association::Proxy ] target

the target to return.

@return [ Array<Mongoid::Document> ] the target, as an array.

# File lib/mongoid/validatable/associated.rb, line 124
def get_target_documents_for_other(target)
  Array.wrap(target)
end
validate_association(document, attribute) click to toggle source

Validates that the given association provided is either nil, persisted and unchanged, or invalid. Otherwise, the appropriate errors will be added to the parent document.

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

# File lib/mongoid/validatable/associated.rb, line 50
def validate_association(document, attribute)
  # grab the proxy from the instance variable directly; we don't want
  # any loading logic to run; we just want to see if it's already
  # been loaded.
  proxy = document.ivar(attribute)
  return unless proxy

  # if the variable exists, now we see if it is a proxy, or an actual
  # document. It might be a literal document instead of a proxy if this
  # document was created with a Document instance as a provided attribute,
  # e.g. "Post.new(message: Message.new)".
  target = proxy.respond_to?(:_target) ? proxy._target : proxy

  # Now, fetch the list of documents from the target. Target may be a
  # single value, or a list of values, and in the case of HasMany,
  # might be a rather complex collection. We need to do this without
  # triggering a load, so it's a bit of a delicate dance.
  list = get_target_documents(target)

  valid = document.validating do
    # Now, treating the target as an array, look at each element
    # and see if it is valid, but only if it has already been
    # persisted, or changed, and hasn't been flagged for destroy.
    #
    # use map.all? instead of just all?, because all? will do short-circuit
    # evaluation and terminate on the first failed validation.
    list.map do |value|
      if value && !value.flagged_for_destroy? && (!value.persisted? || value.changed?)
        value.validated? ? true : value.valid?
      else
        true
      end
    end.all?
  end

  document.errors.add(attribute, :invalid) unless valid
end