class Mongoid::Relations::Referenced::Many

This class defines the behaviour for all relations that are a one-to-many between documents in different collections.

Public Class Methods

new(base, target, metadata) click to toggle source

Instantiate a new references_many relation. Will set the foreign key and the base on the inverse object.

@example Create the new relation.

Referenced::Many.new(base, target, metadata)

@param [ Document ] base The document this relation hangs off of. @param [ Array<Document> ] target The target of the relation. @param [ Metadata ] metadata The relation’s metadata.

@since 2.0.0.beta.1

# File lib/mongoid/relations/referenced/many.rb, line 215
def initialize(base, target, metadata)
  init(base, Targets::Enumerable.new(target), metadata) do
    raise_mixed if klass.embedded? && !klass.cyclic?
  end
end

Private Class Methods

builder(base, meta, object) click to toggle source

Return the builder that is responsible for generating the documents that will be used by this relation.

@example Get the builder.

Referenced::Many.builder(meta, object)

@param [ Document ] base The base document. @param [ Metadata ] meta The metadata of the relation. @param [ Document, Hash ] object A document or attributes to build

with.

@return [ Builder ] A new builder object.

@since 2.0.0.rc.1

# File lib/mongoid/relations/referenced/many.rb, line 543
def builder(base, meta, object)
  Builders::Referenced::Many.new(base, meta, object || [])
end
criteria(metadata, object, type = nil) click to toggle source

Get the standard criteria used for querying this relation.

@example Get the criteria.

Proxy.criteria(meta, id, Model)

@param [ Metadata ] metadata The metadata. @param [ Object ] object The value of the foreign key. @param [ Class ] type The optional type.

@return [ Criteria ] The criteria.

@since 2.1.0

# File lib/mongoid/relations/referenced/many.rb, line 559
def criteria(metadata, object, type = nil)
  apply_ordering(
    with_inverse_field_criterion(
      with_polymorphic_criterion(
        metadata.klass.where(metadata.foreign_key => object),
        metadata,
        type
      ),
      metadata
    ), metadata
  )
end
eager_load(metadata, ids) click to toggle source

Eager load the relation based on the criteria.

@example Eager load the criteria.

Proxy.eager_load(metadata, criteria)

@param [ Metadata ] metadata The relation metadata. @param [ Array<Object> ] ids The ids of the base docs.

@return [ Criteria ] The criteria to eager load the relation.

@since 2.2.0

# File lib/mongoid/relations/referenced/many.rb, line 583
def eager_load(metadata, ids)
  eager_load_ids(metadata, ids) { |doc, key| IdentityMap.set_many(doc, key) }
end
embedded?() click to toggle source

Returns true if the relation is an embedded one. In this case always false.

@example Is this relation embedded?

Referenced::Many.embedded?

@return [ false ] Always false.

@since 2.0.0.rc.1

# File lib/mongoid/relations/referenced/many.rb, line 596
def embedded?
  false
end
foreign_key(name) click to toggle source

Get the foreign key for the provided name.

@example Get the foreign key.

Referenced::Many.foreign_key(:person)

@param [ Symbol ] name The name.

@return [ String ] The foreign key.

@since 3.0.0

# File lib/mongoid/relations/referenced/many.rb, line 610
def foreign_key(name)
  "#{name}#{foreign_key_suffix}"
end
foreign_key_default() click to toggle source

Get the default value for the foreign key.

@example Get the default.

Referenced::Many.foreign_key_default

@return [ nil ] Always nil.

@since 2.0.0.rc.1

# File lib/mongoid/relations/referenced/many.rb, line 622
def foreign_key_default
  nil
end
foreign_key_suffix() click to toggle source

Returns the suffix of the foreign key field, either “_id” or “_ids”.

@example Get the suffix for the foreign key.

Referenced::Many.foreign_key_suffix

@return [ String ] “_id”

@since 2.0.0.rc.1

# File lib/mongoid/relations/referenced/many.rb, line 634
def foreign_key_suffix
  "_id"
end
macro() click to toggle source

Returns the macro for this relation. Used mostly as a helper in reflection.

@example Get the macro.

Referenced::Many.macro

@return [ Symbol ] :has_many

# File lib/mongoid/relations/referenced/many.rb, line 645
def macro
  :has_many
end
nested_builder(metadata, attributes, options) click to toggle source

Return the nested builder that is responsible for generating the documents that will be used by this relation.

@example Get the nested builder.

Referenced::Many.builder(attributes, options)

@param [ Metadata ] metadata The relation metadata. @param [ Hash ] attributes The attributes to build with. @param [ Hash ] options The options for the builder.

@option options [ true, false ] :allow_destroy Can documents be

deleted?

@option options [ Integer ] :limit Max number of documents to

create at once.

@option options [ Proc, Symbol ] :reject_if If documents match this

option then they are ignored.

@option options [ true, false ] :update_only Only existing documents

can be modified.

@return [ NestedBuilder ] A newly instantiated nested builder object.

@since 2.0.0.rc.1

# File lib/mongoid/relations/referenced/many.rb, line 671
def nested_builder(metadata, attributes, options)
  Builders::NestedAttributes::Many.new(metadata, attributes, options)
end
path(document) click to toggle source

Get the path calculator for the supplied document.

@example Get the path calculator.

Proxy.path(document)

@param [ Document ] document The document to calculate on.

@return [ Root ] The root atomic path calculator.

@since 2.1.0

# File lib/mongoid/relations/referenced/many.rb, line 685
def path(document)
  Mongoid::Atomic::Paths::Root.new(document)
end
stores_foreign_key?() click to toggle source

Tells the caller if this relation is one that stores the foreign key on its own objects.

@example Does this relation store a foreign key?

Referenced::Many.stores_foreign_key?

@return [ false ] Always false.

@since 2.0.0.rc.1

# File lib/mongoid/relations/referenced/many.rb, line 698
def stores_foreign_key?
  false
end
valid_options() click to toggle source

Get the valid options allowed with this relation.

@example Get the valid options.

Relation.valid_options

@return [ Array<Symbol> ] The valid options.

@since 2.1.0

# File lib/mongoid/relations/referenced/many.rb, line 710
def valid_options
  [
    :after_add,
    :after_remove,
    :as,
    :autosave,
    :before_add,
    :before_remove,
    :dependent,
    :foreign_key,
    :order,
    :primary_key
  ]
end
validation_default() click to toggle source

Get the default validation setting for the relation. Determines if by default a validates associated will occur.

@example Get the validation default.

Proxy.validation_default

@return [ true, false ] The validation default.

@since 2.1.9

# File lib/mongoid/relations/referenced/many.rb, line 734
def validation_default
  true
end
with_inverse_field_criterion(criteria, metadata) click to toggle source

Decorate the criteria with inverse field criteria, if applicable.

@api private

@example Get the criteria with polymorphic criterion.

Proxy.with_inverse_field_criterion(criteria, metadata)

@param [ Criteria ] criteria The criteria to decorate. @param [ Metadata ] metadata The metadata.

@return [ Criteria ] The criteria.

@since 3.0.0

# File lib/mongoid/relations/referenced/many.rb, line 775
def with_inverse_field_criterion(criteria, metadata)
  inverse_metadata = metadata.inverse_metadata(metadata.klass)
  if inverse_metadata.try(:inverse_of_field)
    criteria.any_in(inverse_metadata.inverse_of_field => [ metadata.name, nil ])
  else
    criteria
  end
end
with_polymorphic_criterion(criteria, metadata, type = nil) click to toggle source

Decorate the criteria with polymorphic criteria, if applicable.

@api private

@example Get the criteria with polymorphic criterion.

Proxy.with_polymorphic_criterion(criteria, metadata)

@param [ Criteria ] criteria The criteria to decorate. @param [ Metadata ] metadata The metadata. @param [ Class ] type The optional type.

@return [ Criteria ] The criteria.

@since 3.0.0

# File lib/mongoid/relations/referenced/many.rb, line 754
def with_polymorphic_criterion(criteria, metadata, type = nil)
  if metadata.polymorphic?
    criteria.where(metadata.type => type.name)
  else
    criteria
  end
end

Public Instance Methods

<<(*args) click to toggle source

Appends a document or array of documents to the relation. Will set the parent and update the index in the process.

@example Append a document.

person.posts << post

@example Push a document.

person.posts.push(post)

@example Concat with other documents.

person.posts.concat([ post_one, post_two ])

@param [ Document, Array<Document> ] *args Any number of documents.

@return [ Array<Document> ] The loaded docs.

@since 2.0.0.beta.1

# File lib/mongoid/relations/referenced/many.rb, line 30
def <<(*args)
  docs = args.flatten
  return concat(docs) if docs.size > 1
  if doc = docs.first
    append(doc)
    doc.save if persistable? && !_assigning? && !doc.validated?
  end
  self
end
Also aliased as: push
build(attributes = {}, type = nil) { |doc| ... } click to toggle source

Build a new document from the attributes and append it to this relation without saving.

@example Build a new document on the relation.

person.posts.build(:title => "A new post")

@overload build(attributes = {}, options = {}, type = nil)

@param [ Hash ] attributes The attributes of the new document.
@param [ Hash ] options The scoped assignment options.
@param [ Class ] type The optional subclass to build.

@overload build(attributes = {}, type = nil)

@param [ Hash ] attributes The attributes of the new document.
@param [ Class ] type The optional subclass to build.

@return [ Document ] The new document.

@since 2.0.0.beta.1

# File lib/mongoid/relations/referenced/many.rb, line 81
def build(attributes = {}, type = nil)
  doc = Factory.build(type || klass, attributes)
  append(doc)
  doc.apply_post_processed_defaults
  yield(doc) if block_given?
  doc.run_callbacks(:build) { doc }
  doc
end
Also aliased as: new
clear()
Alias for: purge
concat(documents) click to toggle source

Appends an array of documents to the relation. Performs a batch insert of the documents instead of persisting one at a time.

@example Concat with other documents.

person.posts.concat([ post_one, post_two ])

@param [ Array<Document> ] documents The docs to add.

@return [ Array<Document> ] The documents.

@since 2.4.0

# File lib/mongoid/relations/referenced/many.rb, line 52
def concat(documents)
  docs, inserts = [], []
  documents.each do |doc|
    next unless doc
    append(doc)
    save_or_delay(doc, docs, inserts) if persistable?
  end
  persist_delayed(docs, inserts)
  self
end
delete(document) click to toggle source

Delete the document from the relation. This will set the foreign key on the document to nil. If the dependent options on the relation are :delete or :destroy the appropriate removal will occur.

@example Delete the document.

person.posts.delete(post)

@param [ Document ] document The document to remove.

@return [ Document ] The matching document.

@since 2.1.0

# File lib/mongoid/relations/referenced/many.rb, line 103
def delete(document)
  execute_callback :before_remove, document
  target.delete(document) do |doc|
    if doc
      unbind_one(doc)
      cascade!(doc) if !_assigning?
    end
    execute_callback :after_remove, doc
  end
end
delete_all(conditions = nil) click to toggle source

Deletes all related documents from the database given the supplied conditions.

@example Delete all documents in the relation.

person.posts.delete_all

@example Conditonally delete all documents in the relation.

person.posts.delete_all({ :title => "Testing" })

@param [ Hash ] conditions Optional conditions to delete with.

@return [ Integer ] The number of documents deleted.

@since 2.0.0.beta.1

# File lib/mongoid/relations/referenced/many.rb, line 128
def delete_all(conditions = nil)
  remove_all(conditions, :delete_all)
end
destroy_all(conditions = nil) click to toggle source

Destroys all related documents from the database given the supplied conditions.

@example Destroy all documents in the relation.

person.posts.destroy_all

@example Conditonally destroy all documents in the relation.

person.posts.destroy_all({ :title => "Testing" })

@param [ Hash ] conditions Optional conditions to destroy with.

@return [ Integer ] The number of documents destroyd.

@since 2.0.0.beta.1

# File lib/mongoid/relations/referenced/many.rb, line 146
def destroy_all(conditions = nil)
  remove_all(conditions, :destroy_all)
end
each() { |doc| ... } click to toggle source

Iterate over each document in the relation and yield to the provided block.

@note This will load the entire relation into memory.

@example Iterate over the documents.

person.posts.each do |post|
  post.save
end

@return [ Array<Document> ] The loaded docs.

@since 2.1.0

# File lib/mongoid/relations/referenced/many.rb, line 163
def each
  if block_given?
    target.each { |doc| yield(doc) }
  else
    to_enum
  end
end
exists?() click to toggle source

Determine if any documents in this relation exist in the database.

@example Are there persisted documents?

person.posts.exists?

@return [ true, false ] True is persisted documents exist, false if not.

# File lib/mongoid/relations/referenced/many.rb, line 177
def exists?
  criteria.exists?
end
find(*args) click to toggle source

Find the matchind document on the association, either based on id or conditions.

@example Find by an id.

person.posts.find(Moped::BSON::ObjectId.new)

@example Find by multiple ids.

person.posts.find([ Moped::BSON::ObjectId.new, Moped::BSON::ObjectId.new ])

@note This will keep matching documents in memory for iteration

later.

@param [ Moped::BSON::ObjectId, Array<Moped::BSON::ObjectId> ] arg The ids.

@return [ Document, Criteria ] The matching document(s).

@since 2.0.0.beta.1

# File lib/mongoid/relations/referenced/many.rb, line 198
def find(*args)
  matching = criteria.find(*args)
  Array(matching).each { |doc| target.push(doc) }
  matching
end
new(attributes = {}, type = nil)
Alias for: build
nullify() click to toggle source

Removes all associations between the base document and the target documents by deleting the foreign keys and the references, orphaning the target documents in the process.

@example Nullify the relation.

person.posts.nullify

@since 2.0.0.rc.1

# File lib/mongoid/relations/referenced/many.rb, line 229
def nullify
  criteria.update_all(foreign_key => nil)
  target.clear do |doc|
    unbind_one(doc)
    doc.changed_attributes.delete(foreign_key)
  end
end
Also aliased as: nullify_all
nullify_all()
Alias for: nullify
purge() click to toggle source

Clear the relation. Will delete the documents from the db if they are already persisted.

@example Clear the relation.

person.posts.clear

@return [ Many ] The relation emptied.

@since 2.0.0.beta.1

# File lib/mongoid/relations/referenced/many.rb, line 247
def purge
  unless metadata.destructive?
    nullify
  else
    after_remove_error = nil
    criteria.delete_all
    many = target.clear do |doc|
      execute_callback :before_remove, doc
      unbind_one(doc)
      doc.destroyed = true
      begin
        execute_callback :after_remove, doc
      rescue => e
        after_remove_error = e
      end
    end
    raise after_remove_error if after_remove_error
    many
  end
end
Also aliased as: clear
push(*args)
Alias for: <<
substitute(replacement) click to toggle source

Substitutes the supplied target documents for the existing documents in the relation. If the new target is nil, perform the necessary deletion.

@example Replace the relation.

person.posts.substitute([ new_post ])

@param [ Array<Document> ] replacement The replacement target.

@return [ Many ] The relation.

@since 2.0.0.rc.1

# File lib/mongoid/relations/referenced/many.rb, line 281
def substitute(replacement)
  if replacement
    new_docs, docs = replacement.compact, []
    new_ids = new_docs.map { |doc| doc.id }
    remove_not_in(new_ids)
    new_docs.each do |doc|
      docs.push(doc) if doc.send(foreign_key) != base.id
    end
    concat(docs)
  else
    purge
  end
  self
end
unscoped() click to toggle source

Get a criteria for the documents without the default scoping applied.

@example Get the unscoped criteria.

person.posts.unscoped

@return [ Criteria ] The unscoped criteria.

@since 2.4.0

# File lib/mongoid/relations/referenced/many.rb, line 305
def unscoped
  klass.unscoped.where(
    foreign_key => Conversions.flag(base.id, metadata)
  )
end

Private Instance Methods

append(document) click to toggle source

Appends the document to the target array, updating the index on the document at the same time.

@example Append the document to the relation.

relation.append(document)

@param [ Document ] document The document to append to the target.

@since 2.0.0.rc.1

# File lib/mongoid/relations/referenced/many.rb, line 322
def append(document)
  document.with(@persistence_options) if @persistence_options

  execute_callback :before_add, document
  target.push(document)
  characterize_one(document)
  bind_one(document)
  execute_callback :after_add, document
end
binding() click to toggle source

Instantiate the binding associated with this relation.

@example Get the binding.

relation.binding([ address ])

@param [ Array<Document> ] new_target The new documents to bind with.

@return [ Binding ] The binding.

@since 2.0.0.rc.1

# File lib/mongoid/relations/referenced/many.rb, line 342
def binding
  Bindings::Referenced::Many.new(base, target, metadata)
end
cascade!(document) click to toggle source

Perform the necessary cascade operations for documents that just got deleted or nullified.

@example Cascade the change.

relation.cascade!(document)

@param [ Document ] document The document to cascade on.

@return [ true, false ] If the metadata is destructive.

@since 2.1.0

# File lib/mongoid/relations/referenced/many.rb, line 386
def cascade!(document)
  if persistable?
    if metadata.destructive?
      document.send(metadata.dependent)
    else
      document.save
    end
  end
end
collection() click to toggle source

Get the collection of the relation in question.

@example Get the collection of the relation.

relation.collection

@return [ Collection ] The collection of the relation.

@since 2.0.2

# File lib/mongoid/relations/referenced/many.rb, line 354
def collection
  klass.collection
end
criteria() click to toggle source

Returns the criteria object for the target class with its documents set to target.

@example Get a criteria for the relation.

relation.criteria

@return [ Criteria ] A new criteria.

@since 2.0.0.beta.1

# File lib/mongoid/relations/referenced/many.rb, line 367
def criteria
  Many.criteria(
    metadata,
    Conversions.flag(base.send(metadata.primary_key), metadata),
    base.class
  )
end
method_missing(name, *args, &block) click to toggle source

If the target array does not respond to the supplied method then try to find a named scope or criteria on the class and send the call there.

If the method exists on the array, use the default proxy behavior.

@param [ Symbol, String ] name The name of the method. @param [ Array ] args The method args @param [ Proc ] block Optional block to pass.

@return [ Criteria, Object ] A Criteria or return value from the target.

@since 2.0.0.beta.1

# File lib/mongoid/relations/referenced/many.rb, line 408
def method_missing(name, *args, &block)
  if target.respond_to?(name)
    target.send(name, *args, &block)
  else
    klass.send(:with_scope, criteria) do
      criteria.send(name, *args, &block)
    end
  end
end
persist_delayed(docs, inserts) click to toggle source

Persist all the delayed batch inserts.

@api private

@example Persist the delayed batch inserts.

relation.persist_delayed([ doc ])

@param [ Array<Document> ] docs The delayed inserts. @param [ Array<Hash> ] inserts The raw insert document.

@since 3.0.0

# File lib/mongoid/relations/referenced/many.rb, line 429
def persist_delayed(docs, inserts)
  unless docs.empty?
    collection.insert(inserts)
    docs.each do |doc|
      doc.new_record = false
      doc.run_after_callbacks(:create, :save)
      doc.post_persist
    end
  end
end
persistable?() click to toggle source

Are we able to persist this relation?

@example Can we persist the relation?

relation.persistable?

@return [ true, false ] If the relation is persistable.

@since 2.1.0

# File lib/mongoid/relations/referenced/many.rb, line 448
def persistable?
  !_binding? && (_creating? || base.persisted? && !_building?)
end
remove_all(conditions = nil, method = :delete_all) click to toggle source

Deletes all related documents from the database given the supplied conditions.

@example Delete all documents in the relation.

person.posts.delete_all

@example Conditonally delete all documents in the relation.

person.posts.delete_all({ :title => "Testing" })

@param [ Hash ] conditions Optional conditions to delete with. @param [ Symbol ] The deletion method to call.

@return [ Integer ] The number of documents deleted.

@since 2.1.0

# File lib/mongoid/relations/referenced/many.rb, line 467
def remove_all(conditions = nil, method = :delete_all)
  selector = conditions || {}
  removed = klass.send(method, selector.merge!(criteria.selector))
  target.delete_if do |doc|
    if doc.matches?(selector)
      unbind_one(doc) and true
    end
  end
  removed
end
remove_not_in(ids) click to toggle source

Remove all the documents in the proxy that do not have the provided ids.

@example Remove all documents without the ids.

proxy.remove_not_in([ id ])

@param [ Array<Object> ] ids The ids.

@since 2.4.0

# File lib/mongoid/relations/referenced/many.rb, line 487
def remove_not_in(ids)
  removed = criteria.not_in(_id: ids)
  if metadata.destructive?
    removed.delete_all
  else
    removed.update_all(foreign_key => nil)
  end
  in_memory.each do |doc|
    if !ids.include?(doc.id)
      unbind_one(doc)
      target.delete(doc)
      if metadata.destructive?
        doc.destroyed = true
      end
    end
  end
end
save_or_delay(doc, docs, inserts) click to toggle source

Save a persisted document immediately or delay a new document for batch insert.

@api private

@example Save or delay the document.

relation.save_or_delay(doc, [])

@param [ Document ] doc The document. @param [ Array<Document> ] inserts The inserts.

@since 3.0.0

# File lib/mongoid/relations/referenced/many.rb, line 517
def save_or_delay(doc, docs, inserts)
  if doc.new_record? && doc.valid?(:create)
    doc.run_before_callbacks(:save, :create)
    docs.push(doc)
    inserts.push(doc.as_document)
  else
    doc.save
  end
end