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
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.
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
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
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
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 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
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
Mongoid::Extensions::Array
defines Array#delete_one, so we need to make sure that method behaves reasonably on proxies, too.
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
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
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 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
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
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
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
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
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
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
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
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
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
# 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 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
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
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 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 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
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
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