123456789_123456789_123456789_123456789_123456789_

Class: Mongoid::Association::Referenced::HasAndBelongsToMany::Proxy

Overview

Transparent proxy for has_and_belongs_to_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.

::Mongoid::Association::Referenced::HasMany::Proxy::ClassMethods - Extended

embedded?

Returns true if the association is an embedded one.

Class Method Summary

ClassMethods - Extended

eager_loader

Get the Eager object for this type of association.

::Mongoid::Association::Referenced::HasMany::Proxy - Inherited

.new

Instantiate a new references_many association.

::Mongoid::Association::Referenced::HasMany::Proxy::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::Referenced::HasMany::Proxy - Inherited

#persistable?

Are we able to persist this association?

::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::Referenced::HasMany::Proxy - Inherited

#<<

Appends a document or array of documents to the association.

#build

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

#clear
#concat

Appends an array of documents to the association.

#delete

Delete the document from the association.

#delete_all

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

#delete_one
#destroy_all

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

#each

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

#exists?

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

#find

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

#new
#nullify

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.

#nullify_all
#purge

Clear the association.

#push
#substitute

Substitutes the supplied target documents for the existing documents in the association.

#unscoped

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

#already_related?

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

#append

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

#binding

Instantiate the binding associated with this association.

#cascade!

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

#collection

Get the collection of the association in question.

#criteria

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

#method_missing

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.

#persist_delayed

Persist all the delayed batch inserts.

#remove_all

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

#remove_not_in

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

#save_or_delay

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

#update_or_delete_all

If the association is destructive, the matching documents will be removed.

#with_add_callbacks

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

::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

This class inherits a constructor from Mongoid::Association::Referenced::HasMany::Proxy

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Mongoid::Association::Referenced::HasMany::Proxy

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_and_belongs_to_many/proxy.rb', line 58

def <<(*args)
  docs = args.flatten
  return concat(docs) if docs.size > 1

  if (doc = docs.first)
    append(doc) do
      # We ignore the changes to the value for the foreign key in the
      # changed_attributes hash in this block of code for two reasons:
      #
      # 1) The add_to_set method deletes the value for the foreign
      #    key in the changed_attributes hash, but if we enter this
      #    method with a value for the foreign key in the
      #    changed_attributes hash, then we want it to exist outside
      #    this method as well. It's used later on in the Syncable
      #    module to set the inverse foreign keys.
      # 2) The reset_unloaded method accesses the value for the foreign
      #    key on _base, which causes it to get added to the
      #    changed_attributes hash. This happens because when reading
      #    a "resizable" attribute, it is automatically added to the
      #    changed_attributes hash. This is true only for the foreign
      #    key value for HABTM associations as the other associations
      #    use strings for their foreign key values. For consistency
      #    with the other associations, we ignore this addition to
      #    the changed_attributes hash.
      #    See MONGOID-4843 for a longer discussion about this.
      reset_foreign_key_changes do
        _base.add_to_set(foreign_key => doc.public_send(_association.primary_key))
        doc.save if child_persistable?(doc)
        reset_unloaded
      end
    end
  end
  unsynced(_base, foreign_key) and self
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_and_belongs_to_many/proxy.rb', line 243

def append(document)
  execute_callbacks_around(:add, document) do
    _target.push(document)
    characterize_one(document)
    bind_one(document)
    yield if block_given?
  end
end

#append_document(doc, ids, docs, inserts) (private)

Processes a single document as part of a “concat“ command.

Parameters:

  • doc (Mongoid::Document)

    the document to append

  • ids (Hash)

    the mapping of primary keys that have been visited

  • docs (Array)

    the list of new docs to be inserted later, in bulk

  • inserts (Array)

    the list of Hashes of attributes that will be inserted (corresponding to the “docs“ list)

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 365

def append_document(doc, ids, docs, inserts)
  return unless doc

  append(doc)

  pk = doc.public_send(_association.primary_key)
  if persistable? || _creating?
    ids[pk] = true
    save_or_delay(doc, docs, inserts)
  else
    existing = _base.public_send(foreign_key)
    return if existing.include?(pk)

    existing.push(pk)
    unsynced(_base, foreign_key)
  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_and_belongs_to_many/proxy.rb', line 258

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

#build(attributes = {}, type = nil) {|doc| ... } ⇒ 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.

Yields:

  • (doc)

Returns:

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 123

def build(attributes = {}, type = nil)
  doc = Factory.execute_build(type || klass, attributes, execute_callbacks: false)
  append(doc)
  doc.apply_post_processed_defaults
  _base.public_send(foreign_key).push(doc.public_send(_association.primary_key))
  unsynced(doc, inverse_foreign_key)
  yield(doc) if block_given?
  doc.run_pending_callbacks
  doc
end

#child_persistable?(doc) ⇒ true | false (private)

This method is for internal use only.

Determine if the child document should be persisted.

Examples:

Is the child persistable?

relation.child_persistable?(doc)

Parameters:

Returns:

  • (true | false)

    If the document can be persisted.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 272

def child_persistable?(doc)
  (persistable? || _creating?) &&
    !(doc.persisted? && _association.forced_nil_inverse?)
end

#cleanup_inverse_for(replacement) (private)

Does the cleanup for the inverse of the association when replacing the relation with another list of documents.

Parameters:

  • replacement (Array<Document> | nil)

    the list of documents that will replace the current list.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 309

def cleanup_inverse_for(replacement)
  if replacement
    new_ids = replacement.collect { |doc| doc.public_send(_association.primary_key) }
    objects_to_clear = _base.public_send(foreign_key) - new_ids
    criteria(objects_to_clear).pull(inverse_foreign_key => inverse_primary_key)
  else
    criteria.pull(inverse_foreign_key => inverse_primary_key)
  end
end

#clear(replacement = [])

Alias for #nullify.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 176

alias clear nullify

#clear_foreign_key_changes (private)

This method is for internal use only.

Clears the foreign key from the changed_attributes hash.

This is, in general, used to clear the foreign key from the changed_attributes hash for consistency with the other referenced associations.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 220

def clear_foreign_key_changes
  _base.changed_attributes.delete(foreign_key)
end

#clear_target_for_nullifyArray<Document> (private)

Clears the _target list and executes callbacks for each document. If an exception occurs in an after_remove hook, the exception is saved, the processing completes, and then the exception is re-raised.

Returns:

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 336

def clear_target_for_nullify
  after_remove_error = nil
  many_to_many = _target.clear do |doc|
    unbind_one(doc)
    doc.changed_attributes.delete(inverse_foreign_key) unless _association.forced_nil_inverse?

    begin
      execute_callback :after_remove, doc
    rescue StandardError => e
      after_remove_error = e
    end
  end

  raise after_remove_error if after_remove_error

  many_to_many
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_and_belongs_to_many/proxy.rb', line 105

def concat(documents)
  ids, docs, inserts = {}, [], []
  documents.each { |doc| append_document(doc, ids, docs, inserts) }
  _base.push(foreign_key => ids.keys) if persistable? || _creating?
  persist_delayed(docs, inserts)
  self
end

#criteria(id_list = nil) ⇒ Criteria (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_and_belongs_to_many/proxy.rb', line 284

def criteria(id_list = nil)
  _association.criteria(_base, id_list)
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_and_belongs_to_many/proxy.rb', line 146

def delete(document)
  doc = super
  if doc && persistable?
    _base.pull(foreign_key => doc.public_send(_association.primary_key))
    _target._unloaded = criteria
    unsynced(_base, foreign_key)
  end
  doc
end

#delete_one(document)

Alias for #delete.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 158

alias delete_one delete

#inverse_primary_keyObject (private)

The inverse primary key

Returns:

  • (Object)

    the inverse primary key

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 322

def inverse_primary_key
  if (field = _association.options[:inverse_primary_key])
    _base.public_send(field)
  else
    _base._id
  end
end

#new(attributes = {}, type = nil)

Alias for #build.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 134

alias new build

#nullify(replacement = []) Also known as: #nullify_all, #clear, #purge

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.preferences.nullify

Parameters:

  • replacement (Array<Document>) (defaults to: [])

    The replacement documents.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 168

def nullify(replacement = [])
  _target.each { |doc| execute_callback :before_remove, doc }
  cleanup_inverse_for(replacement) unless _association.forced_nil_inverse?
  _base.set(foreign_key => _base.public_send(foreign_key).clear) if persistable?
  clear_target_for_nullify
end

#nullify_all(replacement = [])

Alias for #nullify.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 175

alias nullify_all nullify

#purge(replacement = [])

Alias for #nullify.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 177

alias purge nullify

#push(*args)

Alias for #<<.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 94

alias push <<

#reset_foreign_key_changes (private)

This method is for internal use only.

Reset the value in the changed_attributes hash for the foreign key to its value before executing the given block.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 228

def reset_foreign_key_changes
  prior_fk_change = _base.changed_attributes.key?(foreign_key)
  fk = _base.changed_attributes[foreign_key].dup
  yield if block_given?
  _base.changed_attributes[foreign_key] = fk
  clear_foreign_key_changes unless prior_fk_change
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.

person.preferences.substitute([ new_post ])

Examples:

Replace the association.

Parameters:

Returns:

  • (Many)

    The association.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 189

def substitute(replacement)
  purge(replacement)
  if replacement.blank?
    reset_unloaded
    clear_foreign_key_changes
  else
    push(replacement.compact.uniq)
  end
  self
end

#unscopedCriteria

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

Examples:

Get the unscoped criteria.

person.preferences.unscoped

Returns:

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 207

def unscoped
  klass.unscoped.any_in(_id: _base.public_send(foreign_key))
end

#unsynced(doc, key) ⇒ true (private)

This method is for internal use only.

Flag the base as unsynced with respect to the foreign key.

Examples:

Flag as unsynced.

relation.unsynced(doc, :preference_ids)

Parameters:

  • doc (Document)

    The document to flag.

  • key (Symbol)

    The key to flag on the document.

Returns:

  • (true)

    true.

[ GitHub ]

  
# File 'lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb', line 299

def unsynced(doc, key)
  doc._synced[key] = false
  true
end