class Mongoid::Association::Referenced::HasAndBelongsToMany::Proxy
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.
Public Instance Methods
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.
rubocop:disable Metrics/AbcSize
# 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
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_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
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_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
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.
Mongoid::Association::Referenced::HasMany::Proxy#delete
# 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
Mongoid::Extensions::Array
defines Array#delete_one, so we need to make sure that method behaves reasonably on proxies, too.
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.preferences.nullify
@param [ Array<Document> ] replacement The replacement documents.
# 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
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.preferences.substitute([ new_post ])
@param [ Array<Document> ] replacement The replacement target.
@return [ Many
] The association.
# 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
Get a criteria for the documents without the default scoping applied.
@example Get the unscoped criteria.
person.preferences.unscoped
@return [ Criteria
] The unscoped criteria.
# 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
Private Instance Methods
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_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
Processes a single document as part of a “concat“ command.
@param [ Mongoid::Document
] doc the document to append @param [ Hash ] ids the mapping of primary keys that have been
visited
@param [ Array ] docs the list of new docs to be inserted later,
in bulk
@param [ Array ] inserts the list of Hashes of attributes that will
be inserted (corresponding to the ``docs`` list)
rubocop:disable Metrics/AbcSize
# 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
Instantiate the binding associated with this association.
@example Get the binding.
relation.binding([ address ])
@return [ Binding
] The binding.
# File lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb, line 258 def binding HasAndBelongsToMany::Binding.new(_base, _target, _association) end
Determine if the child document should be persisted.
@api private
@example Is the child persistable?
relation.child_persistable?(doc)
@param [ Document
] doc The document.
@return [ true | false ] If the document can be persisted.
# 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
Does the cleanup for the inverse of the association when replacing the relation with another list of documents.
@param [ Array<Document> | nil ] replacement the list of documents
that will replace the current list.
# 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
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.
@api private
# 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
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.
@return [ Array<Document> ] the replacement documents
# 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
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_and_belongs_to_many/proxy.rb, line 284 def criteria(id_list = nil) _association.criteria(_base, id_list) end
The inverse primary key
@return [ Object
] the inverse primary key
# 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
Reset the value in the changed_attributes hash for the foreign key to its value before executing the given block.
@api private
# 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
Flag the base as unsynced with respect to the foreign key.
@api private
@example Flag as unsynced.
relation.unsynced(doc, :preference_ids)
@param [ Document
] doc The document to flag. @param [ Symbol ] key The key to flag on the document.
@return [ true ] true.
# File lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb, line 299 def unsynced(doc, key) doc._synced[key] = false true end