class Mongoid::Association::Referenced::HasMany::Proxy

Transparent proxy for has_many associations. An instance of this class is returned when calling the association getter method on the subject document. This class inherits from Mongoid::Association::Proxy and forwards most of its methods to the target of the association, i.e. the array of documents on the opposite-side collection which must be loaded.

Public Class Methods

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

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

@example Create the new association.

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

@param [ Document ] base The document this association hangs off of. @param [ Array<Document> ] target The target of the association. @param [ Mongoid::Association::Relatable ] association The association metadata.

Calls superclass method Mongoid::Association::Proxy::new
# File lib/mongoid/association/referenced/has_many/proxy.rb, line 51
def initialize(base, target, association)
  enum = HasMany::Enumerable.new(target, base, association)
  super(base, enum, association) do
    raise_mixed if klass.embedded? && !klass.cyclic?
  end
end

Public Instance Methods

<<(*args) click to toggle source

Appends a document or array of documents to the association. 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… ] *args Any number of documents.

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

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 73
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 association without saving.

@example Build a new document on the association.

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

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

@return [ Document ] The new document.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 118
def build(attributes = {}, type = nil)
  Factory.execute_build(type || klass, attributes, execute_callbacks: false).tap do |doc|
    append(doc)
    doc.apply_post_processed_defaults
    yield doc if block_given?
    doc.run_pending_callbacks
    doc.run_callbacks(:build) { doc }
  end
end
Also aliased as: new
clear()
Alias for: purge
concat(documents) click to toggle source

Appends an array of documents to the association. 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.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 95
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 association. This will set the foreign key on the document to nil. If the dependent options on the association are :delete_all 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.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 140
def delete(document)
  execute_callbacks_around(:remove, document) do
    result = _target.delete(document) do |doc|
      if doc
        unbind_one(doc)
        cascade!(doc) unless _assigning?
      end
    end

    result.tap { reset_unloaded }
  end
end
Also aliased as: delete_one
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 association.

person.posts.delete_all

@example Conditonally delete all documents in the association.

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

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

@return [ Integer ] The number of documents deleted.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 169
def delete_all(conditions = nil)
  remove_all(conditions, :delete_all)
end
delete_one(document)

Mongoid::Extensions::Array defines Array#delete_one, so we need to make sure that method behaves reasonably on proxies, too.

Alias for: delete
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 association.

person.posts.destroy_all

@example Conditionally destroy all documents in the association.

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

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

@return [ Integer ] The number of documents destroyed.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 185
def destroy_all(conditions = nil)
  remove_all(conditions, :destroy_all)
end
each(&block) click to toggle source

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

@note This will load the entire association into memory.

@example Iterate over the documents.

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

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

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 200
def each(&block)
  if block
    _target.each(&block)
  else
    to_enum
  end
end
exists?(id_or_conditions = :none) click to toggle source

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

If the association contains documents but all of the documents exist only in the application, i.e. have not been persisted to the database, this method returns false.

This method queries the database on each invocation even if the association is already loaded into memory.

@example Are there persisted documents?

person.posts.exists?

@param [ :none | nil | false | Hash | Object ] id_or_conditions

When :none (the default), returns true if any persisted
documents exist in the association. When nil or false, this
will always return false. When a Hash is given, this queries
the documents in the association for those that match the given
conditions, and returns true if any match. Any other argument is
interpreted as an id, and queries for the existence of documents
in the association with a matching _id.

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

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 230
def exists?(id_or_conditions = :none)
  criteria.exists?(id_or_conditions)
end
find(*args, &block) click to toggle source

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

This method delegates to +Mongoid::Criteria#find+. If this method is not given a block, it returns one or many documents for the provided _id values.

If this method is given a block, it returns the first document of those found by the current Criteria object for which the block returns a truthy value.

@note Each argument can be an individual id, an array of ids or

a nested array. Each array will be flattened.

@example Find by an id.

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

@example Find by multiple ids.

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

@example Finds the first matching document using a block.

person.addresses.find { |addr| addr.state == 'CA' }

@note This will keep matching documents in memory for iteration

later.

@param [ [ Object | Array<Object> ]… ] *args The ids. @param &block Optional block to pass. @yield [ Object ] Yields each enumerable element to the block.

@return [ Document | Array<Document> | nil ] A document or matching documents.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 265
def find(*args, &block)
  matching = criteria.find(*args, &block)
  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 association.

person.posts.nullify
# File lib/mongoid/association/referenced/has_many/proxy.rb, line 277
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 association. Will delete the documents from the db if they are already persisted.

@example Clear the association.

person.posts.clear

@return [ Many ] The association emptied.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 294
def purge
  return nullify unless _association.destructive?

  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 StandardError => e
      after_remove_error = e
    end
  end

  raise after_remove_error if after_remove_error

  many
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 association. If the new target is nil, perform the necessary deletion.

@example Replace the association.

person.posts.substitute([ new_post ])

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

@return [ Many ] The association.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 327
def substitute(replacement)
  if replacement
    new_docs, docs = replacement.compact, []
    new_ids = new_docs.map(&:_id)
    remove_not_in(new_ids)
    new_docs.each do |doc|
      docs.push(doc) if doc.send(foreign_key) != _base.send(_association.primary_key)
    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.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 349
def unscoped
  klass.unscoped.where(foreign_key => _base.send(_association.primary_key))
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 association.

relation.append(document)

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

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 362
def append(document)
  with_add_callbacks(document, already_related?(document)) do
    _target.push(document)
    characterize_one(document)
    bind_one(document)
  end
end
binding() click to toggle source

Instantiate the binding associated with this association.

@example Get the binding.

relation.binding([ address ])

@return [ Binding ] The binding.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 407
def binding
  HasMany::Binding.new(_base, _target, _association)
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 association is destructive.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 441
def cascade!(document)
  return unless persistable?

  case _association.dependent
  when :delete_all
    document.delete
  when :destroy
    document.destroy
  else
    document.save
  end
end
collection() click to toggle source

Get the collection of the association in question.

@example Get the collection of the association.

relation.collection

@return [ Collection ] The collection of the association.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 417
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 association.

relation.criteria

@return [ Criteria ] A new criteria.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 428
def criteria
  @criteria ||= _association.criteria(_base)
end
method_missing(name, *args, &block) click to toggle source
# File lib/mongoid/association/referenced/has_many/proxy.rb, line 468
               def method_missing(name, *args, &block)
  if _target.respond_to?(name)
    _target.send(name, *args, &block)
  else
    klass.send(:with_scope, criteria) do
      criteria.public_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.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 488
def persist_delayed(docs, inserts)
  return if docs.empty?

  collection.insert_many(inserts, session: _session)
  docs.each do |doc|
    doc.new_record = false
    doc.run_after_callbacks(:create, :save) unless _association.autosave?
    doc.post_persist
  end
end
persistable?() click to toggle source

Are we able to persist this association?

@example Can we persist the association?

relation.persistable?

@return [ true | false ] If the association is persistable.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 505
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 association.

person.posts.delete_all

@example Conditonally delete all documents in the association.

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

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

@return [ Integer ] The number of documents deleted.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 522
def remove_all(conditions = nil, method = :delete_all)
  selector = conditions || {}
  removed = klass.send(method, selector.merge!(criteria.selector))
  _target.delete_if do |doc|
    doc._matches?(selector).tap do |b|
      unbind_one(doc) if b
    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.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 540
def remove_not_in(ids)
  removed = criteria.not_in(_id: ids)

  update_or_delete_all(removed)

  in_memory.each do |doc|
    next if ids.include?(doc._id)

    unbind_one(doc)
    _target.delete(doc)
    doc.destroyed = true if _association.destructive?
  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.

# File lib/mongoid/association/referenced/has_many/proxy.rb, line 577
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.send(:as_attributes))
  else
    doc.save
  end
end
update_or_delete_all(removed) click to toggle source

If the association is destructive, the matching documents will be removed. Otherwise, their foreign keys will be set to nil.

@param [ Criteria ] removed The criteria for the documents to

remove.
# File lib/mongoid/association/referenced/has_many/proxy.rb, line 559
def update_or_delete_all(removed)
  if _association.destructive?
    removed.delete_all
  else
    removed.update_all(foreign_key => nil)
  end
end
with_add_callbacks(document, already_related) { || ... } click to toggle source

Execute before/after add callbacks around the block unless the objects already have a persisted association.

@example Execute before/after add callbacks around the block.

relation.with_add_callbacks(document, false)

@param [ Document ] document The document to append to the target. @param [ true | false ] already_related Whether the document is already related

to the target.
# File lib/mongoid/association/referenced/has_many/proxy.rb, line 379
def with_add_callbacks(document, already_related)
  execute_callback :before_add, document unless already_related
  yield
  execute_callback :after_add, document unless already_related
end