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

Inherits:
Many
  • Object
show all
Extended by:
Forwardable, ClassMethods
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.

Defined Under Namespace

Modules: ClassMethods

Constant Summary

Constants inherited from Proxy

Proxy::KEEPER_METHODS

Instance Attribute Summary

Attributes inherited from Proxy

#_association, #_base, #_target

Instance Method Summary collapse

Methods included from ClassMethods

eager_loader, embedded?

Methods inherited from Many

#blank?, #create, #create!, #find_or_create_by, #find_or_create_by!, #find_or_initialize_by, #nil?, #respond_to?, #scoped, #serializable_hash

Methods inherited from Proxy

apply_ordering, #extend_proxies, #klass, #reset_unloaded, #substitutable

Methods included from Marshalable

#marshal_dump, #marshal_load

Constructor Details

#initialize(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:



48
49
50
51
52
53
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 48

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.

rubocop:disable Style/MissingRespondToMissing

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.



465
466
467
468
469
470
471
472
473
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 465

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

  • (Array<Document>)

    The loaded docs.



70
71
72
73
74
75
76
77
78
79
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 70

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



115
116
117
118
119
120
121
122
123
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 115

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

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

  • documents (Array<Document>)

    The docs to add.

Returns:



92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 92

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



137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 137

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.



166
167
168
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 166

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

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



182
183
184
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 182

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:

  • (Array<Document>)

    The loaded docs.



197
198
199
200
201
202
203
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 197

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.



227
228
229
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 227

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



262
263
264
265
266
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 262

def find(*args, &block)
  matching = criteria.find(*args, &block)
  Array(matching).each { |doc| _target.push(doc) }
  matching
end

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


274
275
276
277
278
279
280
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 274

def nullify
  criteria.update_all(foreign_key => nil)
  _target.clear do |doc|
    unbind_one(doc)
    doc.changed_attributes.delete(foreign_key)
  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.



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 291

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

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

  • replacement (Array<Document>)

    The replacement target.

Returns:

  • (Many)

    The association.



324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 324

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:



346
347
348
# File 'lib/mongoid/association/referenced/has_many/proxy.rb', line 346

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