123456789_123456789_123456789_123456789_123456789_

Class: Mongoid::Association::Referenced::HasMany::Proxy

Relationships & Source Files
Namespace Children
Modules:
Extension / Inclusion / Inheritance Descendants
Subclasses:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ClassMethods, Forwardable, ::Mongoid::Association::Many, Forwardable, ::Mongoid::Association::Proxy, Forwardable
Instance Chain:
Inherits: Mongoid::Association::Many
Defined in: lib/mongoid/association/referenced/has_many/proxy.rb

Overview

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.

Constant Summary

::Mongoid::Association::Proxy - Inherited

KEEPER_METHODS

Class Attribute Summary

ClassMethods - Extended

embedded?

Returns true if the association is an embedded one.

Class Method Summary

ClassMethods - Extended

::Mongoid::Association::Proxy - Inherited

.apply_ordering

Apply ordering to the criteria if it was defined on the association.

.new

Sets the target and the association metadata properties.

Instance Attribute Summary

::Mongoid::Association::Many - Inherited

#blank?

Is the association empty?

#nil?

This proxy can never be nil.

::Mongoid::Association::Proxy - Inherited

#_association,
#_base

Model instance for the base of the association.

#_target

Model instance for one to one associations, or array of model instances for one to many associations, for the target of the association.

::Mongoid::Threaded::Lifecycle - Included

#_assigning

Begin the assignment of attributes.

#_assigning?

Is the current thread in assigning mode?

#_binding

Execute a block in binding mode.

#_binding?

Is the current thread in binding mode?

#_building

Execute a block in building mode.

#_building?

Is the current thread in building mode?

#_creating?

Is the current thread in creating mode?

#_loading

Execute a block in loading mode.

#_loading?

Is the current thread in loading mode?

Instance Method Summary

::Mongoid::Association::Many - Inherited

#create

Creates a new document on the references many association.

#create!

Creates a new document on the references many association.

#find_or_create_by

Find the first document given the conditions, or creates a new document with the conditions that were supplied.

#find_or_create_by!

Find the first document given the conditions, or creates a new document with the conditions that were supplied.

#find_or_initialize_by

Find the first Document given the conditions, or instantiates a new document with the conditions that were supplied.

#respond_to?

Since method_missing is overridden we should override this as well.

#scoped

This is public access to the association’s criteria.

#serializable_hash

Gets the document as a serializable hash, used by ActiveModel’s JSON and XML serializers.

#unscoped

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

#_session,
#find_or

Find the first object given the supplied attributes or create/initialize it.

::Mongoid::Association::Proxy - Inherited

#extend_proxies

Allow extension to be an array and extend each module.

#extend_proxy,
#klass

Get the class from the association, or return nil if no association present.

#reset_unloaded

Resets the criteria inside the association proxy.

#substitutable

The default substitutable object for an association proxy is the clone of the target.

::Mongoid::Association::Marshalable - Included

#marshal_dump

Provides the data needed to Marshal.dump an association proxy.

#marshal_load

Takes the provided data and sets it back on the proxy.

Constructor Details

.new(base, target, association) ⇒ Proxy

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

Examples:

Create the new association.

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

Parameters:

[ GitHub ]

  
# 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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missingCriteria | Object (private)

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.

TODO: make sure we are consistingly using respond_to_missing

anywhere we define method_missing.

Parameters:

  • name (Symbol | String)

    The name of the method.

  • *args (Object...)

    The method args

  • &block

    Optional block to pass.

Returns:

  • (Criteria | Object)

    A Criteria or return value from the target.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 468

ruby2_keywords 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

Instance Attribute Details

#persistable?true | false (readonly, private)

Are we able to persist this association?

Examples:

Can we persist the association?

relation.persistable?

Returns:

  • (true | false)

    If the association is persistable.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 505

def persistable?
  !_binding? && (_creating? || (_base.persisted? && !_building?))
end

Instance Method Details

#<<(*args) ⇒ Array<Document> Also known as: #push

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

Examples:

Append a document.

person.posts << post

Push a document.

person.posts.push(post)

Concat with other documents.

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

Parameters:

  • *args (Document...)

    Any number of documents.

Returns:

[ GitHub ]

  
# 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

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

Whether the document and the base already have a persisted association.

Examples:

Is the document already related to the base.

relation.already_related?(document)

Parameters:

  • document (Document)

    The document to possibly append to the target.

Returns:

  • (true | false)

    Whether the document is already related to the base and the association is persisted.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 394

def already_related?(document)
  document.persisted? &&
    document._association &&
    document.respond_to?(document._association.foreign_key) &&
    document.__send__(document._association.foreign_key) == _base._id
end

#append(document) (private)

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

Examples:

Append the document to the association.

relation.append(document)

Parameters:

  • document (Document)

    The document to append to the target.

[ GitHub ]

  
# 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

#bindingBinding (private)

Instantiate the binding associated with this association.

Examples:

Get the binding.

relation.binding([ address ])

Returns:

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 407

def binding
  HasMany::Binding.new(_base, _target, _association)
end

#build(attributes = {}, type = nil) ⇒ Document Also known as: #new

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

Examples:

Build a new document on the association.

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

Parameters:

  • attributes (Hash) (defaults to: {})

    The attributes of the new document.

  • type (Class) (defaults to: nil)

    The optional subclass to build.

Returns:

[ GitHub ]

  
# 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

#cascade!(document) ⇒ true | false (private)

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

Examples:

Cascade the change.

relation.cascade!(document)

Parameters:

  • document (Document)

    The document to cascade on.

Returns:

  • (true | false)

    If the association is destructive.

[ GitHub ]

  
# 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

#clear

Alias for #purge.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 315

alias clear purge

#collectionCollection (private)

Get the collection of the association in question.

Examples:

Get the collection of the association.

relation.collection

Returns:

  • (Collection)

    The collection of the association.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 417

def collection
  klass.collection
end

#concat(documents) ⇒ Array<Document>

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

Examples:

Concat with other documents.

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

Parameters:

Returns:

[ GitHub ]

  
# 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

#criteriaCriteria (private)

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

Examples:

Get a criteria for the association.

relation.criteria

Returns:

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 428

def criteria
  @criteria ||= _association.criteria(_base)
end

#delete(document) ⇒ Document Also known as: #delete_one

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.

Examples:

Delete the document.

person.posts.delete(post)

Parameters:

  • document (Document)

    The document to remove.

Returns:

[ GitHub ]

  
# 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

#delete_all(conditions = nil) ⇒ Integer

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

Examples:

Delete all documents in the association.

person.posts.delete_all

Conditonally delete all documents in the association.

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

Parameters:

  • conditions (Hash) (defaults to: nil)

    Optional conditions to delete with.

Returns:

  • (Integer)

    The number of documents deleted.

[ GitHub ]

  
# 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)

Alias for #delete.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 155

alias delete_one delete

#destroy_all(conditions = nil) ⇒ Integer

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

Examples:

Destroy all documents in the association.

person.posts.destroy_all

Conditionally destroy all documents in the association.

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

Parameters:

  • conditions (Hash) (defaults to: nil)

    Optional conditions to destroy with.

Returns:

  • (Integer)

    The number of documents destroyed.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 185

def destroy_all(conditions = nil)
  remove_all(conditions, :destroy_all)
end

#each(&block) ⇒ Array<Document>

Note:

This will load the entire association into memory.

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

Examples:

Iterate over the documents.

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

Returns:

[ GitHub ]

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

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.

Examples:

Are there persisted documents?

person.posts.exists?

Parameters:

  • id_or_conditions (:none | nil | false | Hash | Object) (defaults to: :none)

    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.

Returns:

  • (true | false)

    True is persisted documents exist, false if not.

[ GitHub ]

  
# 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) {|Object| ... } ⇒ Document | Array<Document> | nil

Note:

Each argument can be an individual id, an array of ids or a nested array. Each array will be flattened.

Note:

This will keep matching documents in memory for iteration later.

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 ::Mongoid::Criteria object for which the block returns a truthy value.

Examples:

Find by an id.

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

Find by multiple ids.

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

Finds the first matching document using a block.

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

Parameters:

  • *args ([ Object | Array<Object> ]...)

    The ids.

  • &block

    Optional block to pass.

Yields:

  • (Object)

    Yields each enumerable element to the block.

Returns:

[ GitHub ]

  
# 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.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 128

alias new build

#nullify Also known as: #nullify_all

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.

Examples:

Nullify the association.

person.posts.nullify
[ GitHub ]

  
# 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

#nullify_all

Alias for #nullify.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 285

alias nullify_all nullify

#persist_delayed(docs, inserts) (private)

This method is for internal use only.

Persist all the delayed batch inserts.

Examples:

Persist the delayed batch inserts.

relation.persist_delayed([ doc ])

Parameters:

[ GitHub ]

  
# 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

#purgeMany Also known as: #clear

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

Examples:

Clear the association.

person.posts.clear

Returns:

  • (Many)

    The association emptied.

[ GitHub ]

  
# 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

#push(*args)

Alias for #<<.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 84

alias push <<

#remove_all(conditions = nil, method = :delete_all) ⇒ Integer (private)

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

Examples:

Delete all documents in the association.

person.posts.delete_all

Conditonally delete all documents in the association.

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

Parameters:

  • conditions (Hash) (defaults to: nil)

    Optional conditions to delete with.

  • method (Symbol) (defaults to: :delete_all)

    The deletion method to call.

Returns:

  • (Integer)

    The number of documents deleted.

[ GitHub ]

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

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

Examples:

Remove all documents without the ids.

proxy.remove_not_in([ id ])

Parameters:

  • ids (Array<Object>)

    The ids.

[ GitHub ]

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

This method is for internal use only.

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

Examples:

Save or delay the document.

relation.save_or_delay(doc, [])

Parameters:

[ GitHub ]

  
# 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

#substitute(replacement) ⇒ Many

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

Examples:

Replace the association.

person.posts.substitute([ new_post ])

Parameters:

Returns:

  • (Many)

    The association.

[ GitHub ]

  
# 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

#unscopedCriteria

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

Examples:

Get the unscoped criteria.

person.posts.unscoped

Returns:

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 349

def unscoped
  klass.unscoped.where(foreign_key => _base.send(_association.primary_key))
end

#update_or_delete_all(removed) (private)

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

Parameters:

  • removed (Criteria)

    The criteria for the documents to remove.

[ GitHub ]

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

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

Examples:

Execute before/after add callbacks around the block.

relation.with_add_callbacks(document, false)

Parameters:

  • document (Document)

    The document to append to the target.

  • already_related (true | false)

    Whether the document is already related to the target.

[ GitHub ]

  
# 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