Class: Mongoid::Association::Referenced::HasMany::Enumerable

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Enumerable, FieldReadable, Pluckable
Defined in:
lib/mongoid/association/referenced/has_many/enumerable.rb

Overview

This class is the wrapper for all referenced associations that have a target that can be a criteria or array of _loaded documents. This handles both cases or a combination of the two.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target, base = nil, association = nil) ⇒ Enumerable

Initialize the new enumerable either with a criteria or an array.

Examples:

Initialize the enumerable with a criteria.

Enumerable.new(Post.where(:person_id => id))

Initialize the enumerable with an array.

Enumerable.new([ post ])

Parameters:



262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 262

def initialize(target, base = nil, association = nil)
  @_base = base
  @_association = association
  if target.is_a?(Criteria)
    @_added, @executed, @_loaded, @_unloaded = {}, false, {}, target
  else
    @_added, @executed = {}, true
    @_loaded = target.each_with_object({}) do |doc, _target|
      _target[doc._id] = doc if doc
    end
  end
end

Instance Attribute Details

#_addedObject

The three main instance variables are collections of documents.



24
25
26
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 24

def _added
  @_added
end

#_added Documents that have been appended.(Documentsthathavebeenappended.) ⇒ Object

The three main instance variables are collections of documents.



24
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 24

attr_accessor :_added, :_loaded, :_unloaded

#_loadedObject

The three main instance variables are collections of documents.



24
25
26
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 24

def _loaded
  @_loaded
end

#_loaded Persisted documents that have been _loaded.(Persisteddocumentsthathavebeen_loaded.) ⇒ Object

The three main instance variables are collections of documents.



24
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 24

attr_accessor :_added, :_loaded, :_unloaded

#_unloadedObject

The three main instance variables are collections of documents.



24
25
26
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 24

def _unloaded
  @_unloaded
end

#_unloaded A criteria representing persisted docs.(Acriteriarepresentingpersisteddocs.) ⇒ Object

The three main instance variables are collections of documents.



24
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 24

attr_accessor :_added, :_loaded, :_unloaded

Instance Method Details

#<<(document) ⇒ Document Also known as: push

Append a document to the enumerable.

Examples:

Append the document.

enumerable << document

Parameters:

  • document (Document)

    The document to append.

Returns:



65
66
67
68
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 65

def <<(document)
  _added[document._id] = document
  self
end

#==(other) ⇒ true | false

Check if the enumerable is equal to the other object.

Examples:

Check equality.

enumerable == []

Parameters:

Returns:

  • (true | false)

    If the objects are equal.



36
37
38
39
40
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 36

def ==(other)
  return false unless other.respond_to?(:entries)

  entries == other.entries
end

#===(other) ⇒ true | false

Check equality of the enumerable against the provided object for case statements.

Examples:

Check case equality.

enumerable === Array

Parameters:

  • other (Object)

    The object to check.

Returns:

  • (true | false)

    If the objects are equal in a case.



51
52
53
54
55
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 51

def ===(other)
  return false unless other.respond_to?(:entries)

  entries === other.entries
end

#_loaded?true | false

Has the enumerable been _loaded? This will be true if the criteria has been executed or we manually load the entire thing.

Examples:

Is the enumerable _loaded?

enumerable._loaded?

Returns:

  • (true | false)

    If the enumerable has been _loaded.



353
354
355
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 353

def _loaded?
  !!@executed
end

#any?(*args) ⇒ true | false

Returns whether the association has any documents, optionally subject to the provided filters.

This method returns true if the association has any persisted documents and if it has any not yet persisted documents.

If the association is already loaded, this method inspects the loaded documents and does not query the database. If the association is not loaded, the argument-less and block-less version does not load the association; the other versions (that delegate to Enumerable) may or may not load the association completely depending on whether it is iterated to completion.

This method can take a parameter and a block. The behavior with either the parameter or the block is delegated to the standard library Enumerable module.

Note that when Enumerable's any? method is invoked with both a block and a pattern, it only uses the pattern.

Parameters:

  • *args (Object...)

    The condition that documents must satisfy. See Enumerable documentation for details.

Returns:

  • (true | false)

    If the association has any documents.



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

def any?(*args)
  return super if args.any? || block_given?

  !empty?
end

#as_json(options = {}) ⇒ Hash

Send #as_json to the entries, without encoding.

Examples:

Get the enumerable as json.

enumerable.as_json

Parameters:

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

    Optional parameters.

Returns:

  • (Hash)

    The entries all _loaded as a hash.



506
507
508
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 506

def as_json(options = {})
  entries.as_json(options)
end

#avg(field) ⇒ Float | nil

Get the average of the provided field for all documents in the enumerable. When the association is loaded, computes in memory without querying the database.

Examples:

Get the average of a field.

enumerable.avg(:likes)

Parameters:

  • field (Symbol)

    The field to average.

Returns:

  • (Float | nil)

    The average value or nil if no documents.



536
537
538
539
540
541
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 536

def avg(field)
  values = field_values_for(field)
  return nil if values.empty?

  values.sum / values.size.to_f
end

#clear(&block) ⇒ Array<Document>

Clears out all the documents in this enumerable. If passed a block it will yield to each document that is in memory.

Examples:

Clear out the enumerable.

enumerable.clear

Clear out the enumerable with a block.

enumerable.clear do |doc|
  doc.unbind
end

Returns:

  • (Array<Document>)

    The cleared out _added docs.



84
85
86
87
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 84

def clear(&block)
  in_memory(&block) if block_given?
  _loaded.clear and _added.clear
end

#cloneArray<Document>

Note:

This loads all documents into memory.

Clones each document in the enumerable.

Examples:

Clone the enumerable.

enumerable.clone

Returns:

  • (Array<Document>)

    An array clone of the enumerable.



97
98
99
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 97

def clone
  collect { |doc| doc.clone }
end

#delete(document) {|doc| ... } ⇒ Document

Delete the supplied document from the enumerable.

Examples:

Delete the document.

enumerable.delete(document)

Parameters:

  • document (Document)

    The document to delete.

Yields:

  • (doc)

Returns:



109
110
111
112
113
114
115
116
117
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 109

def delete(document)
  doc = _loaded.delete(document._id) || _added.delete(document._id)
  if !doc && _unloaded && _unloaded.where(_id: document._id).exists?
    yield(document) if block_given?
    return document
  end
  yield(doc) if block_given?
  doc
end

#delete_if(&block) ⇒ Array<Document>

Note:

This operation loads all documents from the database.

Deletes every document in the enumerable for where the block returns true.

Examples:

Delete all matching documents.

enumerable.delete_if do |doc|
  dod._id == _id
end

Returns:

  • (Array<Document>)

    The remaining docs.



130
131
132
133
134
135
136
137
138
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 130

def delete_if(&block)
  load_all!
  deleted = in_memory.select(&block)
  deleted.each do |doc|
    _loaded.delete(doc._id)
    _added.delete(doc._id)
  end
  self
end

#eachtrue

Iterating over this enumerable has to handle a few different scenarios.

If the enumerable has its criteria _loaded into memory then it yields to all the _loaded docs and all the _added docs.

If the enumerable has not _loaded the criteria then it iterates over the cursor while loading the documents and then iterates over the _added docs.

If no block is passed then it returns an enumerator containing all docs.

Examples:

Iterate over the enumerable.

enumerable.each do |doc|
  puts doc
end

return an enumerator containing all the docs


a = enumerable.each

Returns:

  • (true)

    That the enumerable is now _loaded.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 163

def each
  return to_enum unless block_given?

  if _loaded?
    _loaded.each_pair do |_id, doc|
      document = _added.delete(doc._id) || doc
      set_base(document)
      yield(document)
    end
  else
    unloaded_documents.each do |doc|
      document = _added.delete(doc._id) || _loaded.delete(doc._id) || doc
      _loaded[document._id] = document
      set_base(document)
      yield(document)
    end
  end
  _added.each_pair do |_id, doc|
    yield(doc)
  end
  @executed = true
end

#empty?true | false

Is the enumerable empty? Will determine if the count is zero based on whether or not it is _loaded.

Examples:

Is the enumerable empty?

enumerable.empty?

Returns:

  • (true | false)

    If the enumerable is empty.



193
194
195
196
197
198
199
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 193

def empty?
  if _loaded?
    in_memory.empty?
  else
    _added.empty? && !_unloaded.exists?
  end
end

#first(limit = nil) ⇒ Document

Note:

Automatically adding a sort on _id when no other sort is defined on the criteria has the potential to cause bad performance issues. If you experience unexpected poor performance when using #first or #last, use #take instead. Be aware that #take won't guarantee order.

Get the first document in the enumerable. Will check the persisted documents first. Does not load the entire enumerable.

Examples:

Get the first document.

enumerable.first

Parameters:

  • limit (Integer) (defaults to: nil)

    The number of documents to return.

Returns:

  • (Document)

    The first document found.



246
247
248
249
250
251
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 246

def first(limit = nil)
  _loaded.try(:values).try(:first) ||
    _added[(ul = _unloaded.try(:first, limit)).try(:_id)] ||
    ul ||
    _added.values.try(:first)
end

#in_memoryArray<Document>

Note:

When passed a block it yields to each document.

Return all the documents in the enumerable that have been _loaded or _added.

Examples:

Get the in memory docs.

enumerable.in_memory

Returns:

  • (Array<Document>)

    The in memory docs.



309
310
311
312
313
314
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 309

def in_memory
  docs = (_loaded.values + _added.values)
  docs.each do |doc|
    yield(doc) if block_given?
  end
end

#include?(doc) ⇒ true | false

Does the target include the provided document?

Examples:

Does the target include the document?

enumerable.include?(document)

Parameters:

  • doc (Document)

    The document to check.

Returns:

  • (true | false)

    If the document is in the target.



283
284
285
286
287
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 283

def include?(doc)
  return super unless _unloaded

  _unloaded.where(_id: doc._id).exists? || _added.has_key?(doc._id)
end

#inspectString

Inspection will just inspect the entries for nice array-style printing.

Examples:

Inspect the enumerable.

enumerable.inspect

Returns:

  • (String)

    The inspected enum.



296
297
298
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 296

def inspect
  entries.inspect
end

#last(limit = nil) ⇒ Document

Note:

Automatically adding a sort on _id when no other sort is defined on the criteria has the potential to cause bad performance issues. If you experience unexpected poor performance when using #first or #last, use #take instead. Be aware that #take won't guarantee order.

Get the last document in the enumerable. Will check the new documents first. Does not load the entire enumerable.

Examples:

Get the last document.

enumerable.last

Parameters:

  • limit (Integer) (defaults to: nil)

    The number of documents to return.

Returns:

  • (Document)

    The last document found.



331
332
333
334
335
336
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 331

def last(limit = nil)
  _added.values.try(:last) ||
    _loaded.try(:values).try(:last) ||
    _added[(ul = _unloaded.try(:last, limit)).try(:_id)] ||
    ul
end

#marshal_dumpArray<Object>

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

Examples:

Dump the proxy.

Marshal.dump(proxy)

Returns:

  • (Array<Object>)

    The dumped data.



363
364
365
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 363

def marshal_dump
  [ _added, _loaded, _unloaded, @executed ]
end

#marshal_load(data) ⇒ Array<Object>

Loads the data needed to Marshal.load an enumerable proxy.

Examples:

Load the proxy.

Marshal.load(proxy)

Returns:

  • (Array<Object>)

    The dumped data.



373
374
375
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 373

def marshal_load(data)
  @_added, @_loaded, @_unloaded, @executed = data
end

#max(field = nil) ⇒ Numeric | nil

Get the maximum value of the provided field for all documents in the enumerable. When the association is loaded, computes in memory without querying the database.

Examples:

Get the max of a field.

enumerable.max(:likes)

Parameters:

  • field (Symbol) (defaults to: nil)

    The field to max.

Returns:

  • (Numeric | nil)

    The max value or nil if no documents.



569
570
571
572
573
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 569

def max(field = nil)
  return super() if block_given?

  field_values_for(field).max
end

#min(field = nil) ⇒ Numeric | nil

Get the minimum value of the provided field for all documents in the enumerable. When the association is loaded, computes in memory without querying the database.

Examples:

Get the min of a field.

enumerable.min(:likes)

Parameters:

  • field (Symbol) (defaults to: nil)

    The field to min.

Returns:

  • (Numeric | nil)

    The min value or nil if no documents.



553
554
555
556
557
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 553

def min(field = nil)
  return super() if block_given?

  field_values_for(field).min
end

#pluck(*keys) ⇒ Array | Array<Array>

Plucks the given field names from the documents in the target. If the collection has been loaded, it plucks from the loaded documents; otherwise, it plucks from the unloaded criteria. Regardless, it also plucks from any added documents.

Parameters:

  • *fields (Symbol...)

    The field names to pluck.

Returns:

  • (Array | Array<Array>)

    The array of field values. If multiple fields are given, an array of arrays is returned.



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 386

def pluck(*keys)
  [].tap do |results|
    if _loaded? || _added.any?
      klass = @_association.klass
      prepared = prepare_pluck(keys, document_class: klass)
    end

    if _loaded?
      docs = _loaded.values.map { |v| BSON::Document.new(v.attributes) }
      results.concat pluck_from_documents(docs, prepared[:field_names], document_class: klass)
    elsif _unloaded
      criteria = if _added.any?
                   ids_to_exclude = _added.keys
                   _unloaded.not(:_id.in => ids_to_exclude)
                 else
                   _unloaded
                 end

      results.concat criteria.pluck(*keys)
    end

    if _added.any?
      docs = _added.values.map { |v| BSON::Document.new(v.attributes) }
      results.concat pluck_from_documents(docs, prepared[:field_names], document_class: klass)
    end
  end
end

#resetfalse

Reset the enumerable back to its persisted state.

Examples:

Reset the enumerable.

enumerable.reset

Returns:

  • (false)

    Always false.



420
421
422
423
424
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 420

def reset
  _loaded.clear
  _added.clear
  @executed = false
end

#reset_unloaded(criteria) ⇒ Object

Resets the underlying unloaded criteria object with a new one. Used my HABTM associations to keep the underlying array in sync.

Examples:

Reset the unloaded documents.

enumerable.reset_unloaded(criteria)

Parameters:

  • criteria (Criteria)

    The criteria to replace with.



433
434
435
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 433

def reset_unloaded(criteria)
  @_unloaded = criteria if _unloaded.is_a?(Criteria)
end

#respond_to?(name, include_private = false) ⇒ true | false

Does this enumerable respond to the provided method?

Examples:

Does the enumerable respond to the method?

enumerable.respond_to?(:sum)

Parameters:

  • name (String | Symbol)

    The name of the method.

  • include_private (true | false) (defaults to: false)

    Whether to include private methods.

Returns:

  • (true | false)

    Whether the enumerable responds.



447
448
449
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 447

def respond_to?(name, include_private = false)
  [].respond_to?(name, include_private) || super
end

#sizeInteger Also known as: length

Gets the total size of this enumerable. This is a combination of all the persisted and unpersisted documents.

Examples:

Get the size.

enumerable.size

Returns:

  • (Integer)

    The size of the enumerable.



458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 458

def size
  # If _unloaded is present, then it will match the set of documents
  # that belong to this association, which have already been persisted
  # to the database. This set of documents must be considered when
  # computing the size of the association, along with anything that has
  # since been added.
  if _unloaded
    if _added.any?
      # Note that _added may include records that _unloaded already
      # matches. This is the case if the association is assigned an array
      # of items and some of them were already elements of the association.
      #
      # we need to thus make sure _unloaded.count excludes any elements
      # that already exist in _added.

      count = _unloaded.not(:_id.in => _added.values.map(&:id)).count
      count + _added.values.count
    else
      _unloaded.count
    end

  else
    _loaded.count + _added.count
  end
end

#sum(field = nil) ⇒ Numeric

Get the sum of the provided field for all documents in the enumerable. When the association is loaded, computes in memory without querying the database.

Examples:

Get the sum of a field.

enumerable.sum(:likes)

Parameters:

  • field (Symbol) (defaults to: nil)

    The field to sum.

Returns:

  • (Numeric)

    The sum value.



520
521
522
523
524
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 520

def sum(field = nil)
  return super(field || 0) if block_given?

  field_values_for(field).sum || 0
end

#to_json(options = {}) ⇒ String

Send #to_json to the entries.

Examples:

Get the enumerable as json.

enumerable.to_json

Parameters:

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

    Optional parameters.

Returns:

  • (String)

    The entries all _loaded as a string.



494
495
496
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 494

def to_json(options = {})
  entries.to_json(options)
end

#uniqArray<Document>

Note:

This operation loads all documents from the database.

Return all the unique documents in the enumerable.

Examples:

Get all the unique documents.

enumerable.uniq

Returns:

  • (Array<Document>)

    The unique documents.



583
584
585
# File 'lib/mongoid/association/referenced/has_many/enumerable.rb', line 583

def uniq
  entries.uniq
end