mongoid 8.0.7 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (177) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +3 -3
  4. data/README.md +3 -3
  5. data/Rakefile +0 -25
  6. data/lib/config/locales/en.yml +46 -14
  7. data/lib/mongoid/association/accessors.rb +2 -2
  8. data/lib/mongoid/association/builders.rb +1 -1
  9. data/lib/mongoid/association/embedded/batchable.rb +2 -2
  10. data/lib/mongoid/association/embedded/embedded_in/buildable.rb +2 -2
  11. data/lib/mongoid/association/embedded/embedded_in/proxy.rb +2 -1
  12. data/lib/mongoid/association/embedded/embeds_many/buildable.rb +3 -2
  13. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +6 -6
  14. data/lib/mongoid/association/embedded/embeds_one/buildable.rb +1 -1
  15. data/lib/mongoid/association/embedded/embeds_one/proxy.rb +1 -1
  16. data/lib/mongoid/association/macros.rb +0 -6
  17. data/lib/mongoid/association/nested/one.rb +40 -2
  18. data/lib/mongoid/association/proxy.rb +1 -1
  19. data/lib/mongoid/association/referenced/counter_cache.rb +2 -2
  20. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +1 -1
  21. data/lib/mongoid/association/referenced/has_many/enumerable.rb +2 -2
  22. data/lib/mongoid/association/referenced/has_many/proxy.rb +3 -3
  23. data/lib/mongoid/association/reflections.rb +2 -2
  24. data/lib/mongoid/atomic.rb +0 -7
  25. data/lib/mongoid/attributes/dynamic.rb +1 -1
  26. data/lib/mongoid/attributes/nested.rb +2 -2
  27. data/lib/mongoid/attributes/processing.rb +5 -29
  28. data/lib/mongoid/attributes/projector.rb +1 -1
  29. data/lib/mongoid/attributes/readonly.rb +1 -1
  30. data/lib/mongoid/attributes.rb +8 -2
  31. data/lib/mongoid/changeable.rb +107 -5
  32. data/lib/mongoid/clients/storage_options.rb +2 -5
  33. data/lib/mongoid/clients/validators/storage.rb +1 -13
  34. data/lib/mongoid/collection_configurable.rb +58 -0
  35. data/lib/mongoid/composable.rb +2 -0
  36. data/lib/mongoid/config/defaults.rb +60 -0
  37. data/lib/mongoid/config/options.rb +0 -3
  38. data/lib/mongoid/config/validators/async_query_executor.rb +24 -0
  39. data/lib/mongoid/config/validators.rb +1 -0
  40. data/lib/mongoid/config.rb +99 -28
  41. data/lib/mongoid/contextual/atomic.rb +1 -1
  42. data/lib/mongoid/contextual/memory.rb +233 -33
  43. data/lib/mongoid/contextual/mongo/documents_loader.rb +177 -0
  44. data/lib/mongoid/contextual/mongo.rb +370 -133
  45. data/lib/mongoid/contextual/none.rb +162 -7
  46. data/lib/mongoid/contextual.rb +12 -0
  47. data/lib/mongoid/criteria/findable.rb +2 -2
  48. data/lib/mongoid/criteria/includable.rb +4 -3
  49. data/lib/mongoid/criteria/queryable/key.rb +1 -1
  50. data/lib/mongoid/criteria/queryable/mergeable.rb +1 -1
  51. data/lib/mongoid/criteria/queryable/optional.rb +8 -8
  52. data/lib/mongoid/criteria/queryable/selectable.rb +43 -12
  53. data/lib/mongoid/criteria/queryable/selector.rb +1 -1
  54. data/lib/mongoid/criteria/queryable/storable.rb +1 -1
  55. data/lib/mongoid/criteria.rb +6 -5
  56. data/lib/mongoid/deprecable.rb +1 -2
  57. data/lib/mongoid/deprecation.rb +3 -3
  58. data/lib/mongoid/errors/create_collection_failure.rb +33 -0
  59. data/lib/mongoid/errors/drop_collection_failure.rb +27 -0
  60. data/lib/mongoid/errors/immutable_attribute.rb +26 -0
  61. data/lib/mongoid/errors/invalid_async_query_executor.rb +25 -0
  62. data/lib/mongoid/errors/invalid_global_executor_concurrency.rb +22 -0
  63. data/lib/mongoid/errors/invalid_storage_parent.rb +2 -0
  64. data/lib/mongoid/errors.rb +4 -1
  65. data/lib/mongoid/extensions/hash.rb +2 -24
  66. data/lib/mongoid/extensions/object.rb +2 -2
  67. data/lib/mongoid/extensions/time.rb +2 -0
  68. data/lib/mongoid/fields/localized.rb +10 -0
  69. data/lib/mongoid/fields/standard.rb +10 -0
  70. data/lib/mongoid/fields.rb +53 -24
  71. data/lib/mongoid/findable.rb +27 -3
  72. data/lib/mongoid/interceptable.rb +10 -118
  73. data/lib/mongoid/matcher/eq_impl.rb +1 -1
  74. data/lib/mongoid/matcher/type.rb +1 -1
  75. data/lib/mongoid/persistable/creatable.rb +1 -0
  76. data/lib/mongoid/persistable/deletable.rb +1 -1
  77. data/lib/mongoid/persistable/savable.rb +13 -1
  78. data/lib/mongoid/persistable/unsettable.rb +2 -2
  79. data/lib/mongoid/persistable/updatable.rb +51 -1
  80. data/lib/mongoid/persistable/upsertable.rb +20 -1
  81. data/lib/mongoid/persistable.rb +3 -0
  82. data/lib/mongoid/query_cache.rb +5 -1
  83. data/lib/mongoid/railties/database.rake +7 -2
  84. data/lib/mongoid/reloadable.rb +5 -3
  85. data/lib/mongoid/stateful.rb +22 -1
  86. data/lib/mongoid/tasks/database.rake +12 -0
  87. data/lib/mongoid/tasks/database.rb +20 -0
  88. data/lib/mongoid/utils.rb +22 -0
  89. data/lib/mongoid/validatable/macros.rb +5 -5
  90. data/lib/mongoid/validatable.rb +4 -1
  91. data/lib/mongoid/version.rb +1 -1
  92. data/lib/mongoid/warnings.rb +17 -1
  93. data/lib/mongoid.rb +16 -3
  94. data/spec/integration/app_spec.rb +2 -2
  95. data/spec/integration/callbacks_models.rb +37 -0
  96. data/spec/integration/callbacks_spec.rb +126 -12
  97. data/spec/integration/discriminator_key_spec.rb +4 -5
  98. data/spec/integration/i18n_fallbacks_spec.rb +3 -2
  99. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +27 -0
  100. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +20 -25
  101. data/spec/mongoid/association/embedded/embeds_many_models.rb +1 -0
  102. data/spec/mongoid/association/embedded/embeds_one/proxy_spec.rb +15 -2
  103. data/spec/mongoid/association/referenced/belongs_to_spec.rb +2 -18
  104. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +5 -27
  105. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +9 -50
  106. data/spec/mongoid/association/syncable_spec.rb +1 -1
  107. data/spec/mongoid/attributes_spec.rb +3 -33
  108. data/spec/mongoid/changeable_spec.rb +299 -24
  109. data/spec/mongoid/clients_spec.rb +122 -13
  110. data/spec/mongoid/collection_configurable_spec.rb +158 -0
  111. data/spec/mongoid/config/defaults_spec.rb +160 -0
  112. data/spec/mongoid/config_spec.rb +154 -27
  113. data/spec/mongoid/contextual/memory_spec.rb +332 -76
  114. data/spec/mongoid/contextual/mongo/documents_loader_spec.rb +187 -0
  115. data/spec/mongoid/contextual/mongo_spec.rb +1009 -125
  116. data/spec/mongoid/contextual/none_spec.rb +49 -2
  117. data/spec/mongoid/copyable_spec.rb +2 -10
  118. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +4 -10
  119. data/spec/mongoid/criteria/queryable/options_spec.rb +1 -1
  120. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +419 -0
  121. data/spec/mongoid/criteria/queryable/selectable_spec.rb +1 -1
  122. data/spec/mongoid/criteria/queryable/selector_spec.rb +3 -76
  123. data/spec/mongoid/criteria/queryable/storable_spec.rb +0 -72
  124. data/spec/mongoid/criteria_projection_spec.rb +1 -4
  125. data/spec/mongoid/criteria_spec.rb +5 -9
  126. data/spec/mongoid/errors/readonly_document_spec.rb +2 -2
  127. data/spec/mongoid/extensions/hash_spec.rb +3 -3
  128. data/spec/mongoid/extensions/time_spec.rb +8 -43
  129. data/spec/mongoid/extensions/time_with_zone_spec.rb +7 -52
  130. data/spec/mongoid/fields/localized_spec.rb +46 -28
  131. data/spec/mongoid/fields_spec.rb +136 -77
  132. data/spec/mongoid/findable_spec.rb +391 -34
  133. data/spec/mongoid/indexable_spec.rb +16 -10
  134. data/spec/mongoid/interceptable_spec.rb +173 -362
  135. data/spec/mongoid/persistable/deletable_spec.rb +26 -6
  136. data/spec/mongoid/persistable/destroyable_spec.rb +26 -6
  137. data/spec/mongoid/persistable/incrementable_spec.rb +37 -0
  138. data/spec/mongoid/persistable/logical_spec.rb +37 -0
  139. data/spec/mongoid/persistable/poppable_spec.rb +36 -0
  140. data/spec/mongoid/persistable/pullable_spec.rb +72 -0
  141. data/spec/mongoid/persistable/pushable_spec.rb +72 -0
  142. data/spec/mongoid/persistable/renamable_spec.rb +36 -0
  143. data/spec/mongoid/persistable/savable_spec.rb +96 -0
  144. data/spec/mongoid/persistable/settable_spec.rb +37 -0
  145. data/spec/mongoid/persistable/unsettable_spec.rb +36 -0
  146. data/spec/mongoid/persistable/updatable_spec.rb +20 -28
  147. data/spec/mongoid/persistable/upsertable_spec.rb +80 -6
  148. data/spec/mongoid/persistence_context_spec.rb +7 -57
  149. data/spec/mongoid/query_cache_spec.rb +56 -61
  150. data/spec/mongoid/reloadable_spec.rb +24 -28
  151. data/spec/mongoid/scopable_spec.rb +70 -0
  152. data/spec/mongoid/serializable_spec.rb +9 -30
  153. data/spec/mongoid/stateful_spec.rb +122 -8
  154. data/spec/mongoid/tasks/database_rake_spec.rb +74 -0
  155. data/spec/mongoid/tasks/database_spec.rb +127 -0
  156. data/spec/mongoid/timestamps_spec.rb +9 -11
  157. data/spec/mongoid/touchable_spec.rb +277 -5
  158. data/spec/mongoid/touchable_spec_models.rb +3 -1
  159. data/spec/mongoid/traversable_spec.rb +9 -24
  160. data/spec/mongoid/validatable/uniqueness_spec.rb +2 -3
  161. data/spec/mongoid_spec.rb +36 -10
  162. data/spec/spec_helper.rb +5 -0
  163. data/spec/support/immutable_ids.rb +118 -0
  164. data/spec/support/macros.rb +47 -15
  165. data/spec/support/models/artist.rb +0 -1
  166. data/spec/support/models/band.rb +1 -0
  167. data/spec/support/models/book.rb +1 -0
  168. data/spec/support/models/building.rb +2 -0
  169. data/spec/support/models/cover.rb +10 -0
  170. data/spec/support/models/person.rb +0 -1
  171. data/spec/support/models/product.rb +1 -0
  172. data.tar.gz.sig +0 -0
  173. metadata +685 -651
  174. metadata.gz.sig +0 -0
  175. data/spec/mongoid/criteria/queryable/extensions/bignum_spec.rb +0 -60
  176. data/spec/mongoid/criteria/queryable/extensions/fixnum_spec.rb +0 -60
  177. data/spec/support/models/purse.rb +0 -9
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "mongoid/contextual/mongo/documents_loader"
3
4
  require "mongoid/contextual/atomic"
4
5
  require "mongoid/contextual/aggregable/mongo"
5
6
  require "mongoid/contextual/command"
@@ -37,6 +38,18 @@ module Mongoid
37
38
  # @attribute [r] view The Mongo collection view.
38
39
  attr_reader :view
39
40
 
41
+ # Run an explain on the criteria.
42
+ #
43
+ # @example Explain the criteria.
44
+ # Band.where(name: "Depeche Mode").explain
45
+ #
46
+ # @param [ Hash ] options customizable options (See Mongo::Collection::View::Explainable)
47
+ #
48
+ # @return [ Hash ] The explain result.
49
+ def_delegator :view, :explain
50
+
51
+ attr_reader :documents_loader
52
+
40
53
  # Get the number of documents matching the query.
41
54
  #
42
55
  # @example Get the number of matching documents.
@@ -56,12 +69,7 @@ module Mongoid
56
69
  # @return [ Integer ] The number of matches.
57
70
  def count(options = {}, &block)
58
71
  return super(&block) if block_given?
59
-
60
- if valid_for_count_documents?
61
- view.count_documents(options)
62
- else
63
- view.count(options)
64
- end
72
+ view.count_documents(options)
65
73
  end
66
74
 
67
75
  # Get the estimated number of documents matching the query.
@@ -159,22 +167,28 @@ module Mongoid
159
167
  # @example Do any documents exist for the context.
160
168
  # context.exists?
161
169
  #
170
+ # @example Do any documents exist for given _id.
171
+ # context.exists?(BSON::ObjectId(...))
172
+ #
173
+ # @example Do any documents exist for given conditions.
174
+ # context.exists?(name: "...")
175
+ #
162
176
  # @note We don't use count here since Mongo does not use counted
163
177
  # b-tree indexes.
164
178
  #
165
- # @return [ true | false ] If the count is more than zero.
166
- def exists?
167
- !!(view.projection(_id: 1).limit(1).first)
168
- end
169
-
170
- # Run an explain on the criteria.
171
- #
172
- # @example Explain the criteria.
173
- # Band.where(name: "Depeche Mode").explain
179
+ # @param [ Hash | Object | false ] id_or_conditions an _id to
180
+ # search for, a hash of conditions, nil or false.
174
181
  #
175
- # @return [ Hash ] The explain result.
176
- def explain
177
- view.explain
182
+ # @return [ true | false ] If the count is more than zero.
183
+ # Always false if passed nil or false.
184
+ def exists?(id_or_conditions = :none)
185
+ return false if self.view.limit == 0
186
+ case id_or_conditions
187
+ when :none then !!(view.projection(_id: 1).limit(1).first)
188
+ when nil, false then false
189
+ when Hash then Mongo.new(criteria.where(id_or_conditions)).exists?
190
+ else Mongo.new(criteria.where(_id: id_or_conditions)).exists?
191
+ end
178
192
  end
179
193
 
180
194
  # Execute the find and modify command, used for MongoDB's
@@ -230,28 +244,6 @@ module Mongoid
230
244
  end
231
245
  end
232
246
 
233
- # Get the first document in the database for the criteria's selector.
234
- #
235
- # @example Get the first document.
236
- # context.first
237
- #
238
- # @note Automatically adding a sort on _id when no other sort is
239
- # defined on the criteria has the potential to cause bad performance issues.
240
- # If you experience unexpected poor performance when using #first or #last
241
- # and have no sort defined on the criteria, use #take instead.
242
- # Be aware that #take won't guarantee order.
243
- #
244
- # @param [ Integer ] limit The number of documents to return.
245
- #
246
- # @return [ Document ] The first document.
247
- def first(limit = nil)
248
- sort = view.sort || { _id: 1 }
249
- if raw_docs = view.sort(sort).limit(limit || 1).to_a
250
- process_raw_docs(raw_docs, limit)
251
- end
252
- end
253
- alias :one :first
254
-
255
247
  # Return the first result without applying sort
256
248
  #
257
249
  # @api private
@@ -302,25 +294,6 @@ module Mongoid
302
294
 
303
295
  def_delegator :@klass, :database_field_name
304
296
 
305
- # Get the last document in the database for the criteria's selector.
306
- #
307
- # @example Get the last document.
308
- # context.last
309
- #
310
- # @note Automatically adding a sort on _id when no other sort is
311
- # defined on the criteria has the potential to cause bad performance issues.
312
- # If you experience unexpected poor performance when using #first or #last
313
- # and have no sort defined on the criteria, use #take instead.
314
- # Be aware that #take won't guarantee order.
315
- #
316
- # @param [ Integer ] limit The number of documents to return.
317
- #
318
- # @return [ Document ] The last document.
319
- def last(limit = nil)
320
- raw_docs = view.sort(inverse_sorting).limit(limit || 1).to_a.reverse
321
- process_raw_docs(raw_docs, limit)
322
- end
323
-
324
297
  # Returns the number of documents in the database matching
325
298
  # the query selector.
326
299
  #
@@ -345,44 +318,6 @@ module Mongoid
345
318
  @view = view.limit(value) and self
346
319
  end
347
320
 
348
- # Take the given number of documents from the database.
349
- #
350
- # @example Take 10 documents
351
- # context.take(10)
352
- #
353
- # @param [ Integer | nil ] limit The number of documents to return or nil.
354
- #
355
- # @return [ Document | Array<Document> ] The list of documents, or one
356
- # document if no value was given.
357
- def take(limit = nil)
358
- if limit
359
- limit(limit).to_a
360
- else
361
- # Do to_a first so that the Mongo#first method is not used and the
362
- # result is not sorted.
363
- limit(1).to_a.first
364
- end
365
- end
366
-
367
- # Take one document from the database and raise an error if there are none.
368
- #
369
- # @example Take a document
370
- # context.take!
371
- #
372
- # @return [ Document ] The document.
373
- #
374
- # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
375
- # documents to take.
376
- def take!
377
- # Do to_a first so that the Mongo#first method is not used and the
378
- # result is not sorted.
379
- if fst = limit(1).to_a.first
380
- fst
381
- else
382
- raise Errors::DocumentNotFound.new(klass, nil, nil)
383
- end
384
- end
385
-
386
321
  # Initiate a map/reduce operation from the context.
387
322
  #
388
323
  # @example Initiate a map/reduce.
@@ -396,15 +331,22 @@ module Mongoid
396
331
  MapReduce.new(collection, criteria, map, reduce)
397
332
  end
398
333
 
399
- # Pluck the single field values from the database. Will return duplicates
400
- # if they exist and only works for top level fields.
334
+ # Pluck the field value(s) from the database. Returns one
335
+ # result for each document found in the database for
336
+ # the context. The results are normalized according to their
337
+ # Mongoid field types. Note that the results may include
338
+ # duplicates and nil values.
401
339
  #
402
340
  # @example Pluck a field.
403
341
  # context.pluck(:_id)
404
342
  #
405
- # @param [ String | Symbol ] *fields Field(s) to pluck.
343
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pluck,
344
+ # which may include nested fields using dot-notation.
406
345
  #
407
346
  # @return [ Array<Object> | Array<Array<Object>> ] The plucked values.
347
+ # If the *fields arg contains a single value, each result
348
+ # in the array will be a single value. Otherwise, each
349
+ # result in the array will be an array of values.
408
350
  def pluck(*fields)
409
351
  # Multiple fields can map to the same field name. For example, plucking
410
352
  # a field and its _translations field map to the same field in the database.
@@ -439,13 +381,51 @@ module Mongoid
439
381
  # @example Pick a field.
440
382
  # context.pick(:_id)
441
383
  #
442
- # @param [ String | Symbol ] *fields Field(s) to pick.
384
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pick.
443
385
  #
444
386
  # @return [ Object | Array<Object> ] The picked values.
445
387
  def pick(*fields)
446
388
  limit(1).pluck(*fields).first
447
389
  end
448
390
 
391
+ # Take the given number of documents from the database.
392
+ #
393
+ # @example Take 10 documents
394
+ # context.take(10)
395
+ #
396
+ # @param [ Integer | nil ] limit The number of documents to return or nil.
397
+ #
398
+ # @return [ Document | Array<Document> ] The list of documents, or one
399
+ # document if no value was given.
400
+ def take(limit = nil)
401
+ if limit
402
+ limit(limit).to_a
403
+ else
404
+ # Do to_a first so that the Mongo#first method is not used and the
405
+ # result is not sorted.
406
+ limit(1).to_a.first
407
+ end
408
+ end
409
+
410
+ # Take one document from the database and raise an error if there are none.
411
+ #
412
+ # @example Take a document
413
+ # context.take!
414
+ #
415
+ # @return [ Document ] The document.
416
+ #
417
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
418
+ # documents to take.
419
+ def take!
420
+ # Do to_a first so that the Mongo#first method is not used and the
421
+ # result is not sorted.
422
+ if fst = limit(1).to_a.first
423
+ fst
424
+ else
425
+ raise Errors::DocumentNotFound.new(klass, nil, nil)
426
+ end
427
+ end
428
+
449
429
  # Get a hash of counts for the values of a single field. For example,
450
430
  # if the following documents were in the database:
451
431
  #
@@ -558,7 +538,7 @@ module Mongoid
558
538
  # @option opts [ Array ] :array_filters A set of filters specifying to which array elements
559
539
  # an update should apply.
560
540
  #
561
- # @return [ nil, false ] False if no attributes were provided.
541
+ # @return [ nil | false ] False if no attributes were provided.
562
542
  def update(attributes = nil, opts = {})
563
543
  update_documents(attributes, :update_one, opts)
564
544
  end
@@ -574,11 +554,255 @@ module Mongoid
574
554
  # @option opts [ Array ] :array_filters A set of filters specifying to which array elements
575
555
  # an update should apply.
576
556
  #
577
- # @return [ nil, false ] False if no attributes were provided.
557
+ # @return [ nil | false ] False if no attributes were provided.
578
558
  def update_all(attributes = nil, opts = {})
579
559
  update_documents(attributes, :update_many, opts)
580
560
  end
581
561
 
562
+ # Get the first document in the database for the criteria's selector.
563
+ #
564
+ # @example Get the first document.
565
+ # context.first
566
+ #
567
+ # @note Automatically adding a sort on _id when no other sort is
568
+ # defined on the criteria has the potential to cause bad performance issues.
569
+ # If you experience unexpected poor performance when using #first or #last
570
+ # and have no sort defined on the criteria, use #take instead.
571
+ # Be aware that #take won't guarantee order.
572
+ #
573
+ # @param [ Integer ] limit The number of documents to return.
574
+ #
575
+ # @return [ Document | nil ] The first document or nil if none is found.
576
+ def first(limit = nil)
577
+ if limit.nil?
578
+ retrieve_nth(0)
579
+ else
580
+ retrieve_nth_with_limit(0, limit)
581
+ end
582
+ end
583
+ alias :one :first
584
+
585
+ # Get the first document in the database for the criteria's selector or
586
+ # raise an error if none is found.
587
+ #
588
+ # @example Get the first document.
589
+ # context.first!
590
+ #
591
+ # @note Automatically adding a sort on _id when no other sort is
592
+ # defined on the criteria has the potential to cause bad performance issues.
593
+ # If you experience unexpected poor performance when using #first! or #last!
594
+ # and have no sort defined on the criteria, use #take! instead.
595
+ # Be aware that #take! won't guarantee order.
596
+ #
597
+ # @return [ Document ] The first document.
598
+ #
599
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
600
+ # documents available.
601
+ def first!
602
+ first || raise_document_not_found_error
603
+ end
604
+
605
+ # Get the last document in the database for the criteria's selector.
606
+ #
607
+ # @example Get the last document.
608
+ # context.last
609
+ #
610
+ # @note Automatically adding a sort on _id when no other sort is
611
+ # defined on the criteria has the potential to cause bad performance issues.
612
+ # If you experience unexpected poor performance when using #first or #last
613
+ # and have no sort defined on the criteria, use #take instead.
614
+ # Be aware that #take won't guarantee order.
615
+ #
616
+ # @param [ Integer ] limit The number of documents to return.
617
+ #
618
+ # @return [ Document | nil ] The last document or nil if none is found.
619
+ def last(limit = nil)
620
+ if limit.nil?
621
+ retrieve_nth_to_last(0)
622
+ else
623
+ retrieve_nth_to_last_with_limit(0, limit)
624
+ end
625
+ end
626
+
627
+ # Get the last document in the database for the criteria's selector or
628
+ # raise an error if none is found.
629
+ #
630
+ # @example Get the last document.
631
+ # context.last!
632
+ #
633
+ # @note Automatically adding a sort on _id when no other sort is
634
+ # defined on the criteria has the potential to cause bad performance issues.
635
+ # If you experience unexpected poor performance when using #first! or #last!
636
+ # and have no sort defined on the criteria, use #take! instead.
637
+ # Be aware that #take! won't guarantee order.
638
+ #
639
+ # @return [ Document ] The last document.
640
+ #
641
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
642
+ # documents available.
643
+ def last!
644
+ last || raise_document_not_found_error
645
+ end
646
+
647
+ # Get the second document in the database for the criteria's selector.
648
+ #
649
+ # @example Get the second document.
650
+ # context.second
651
+ #
652
+ # @return [ Document | nil ] The second document or nil if none is found.
653
+ def second
654
+ retrieve_nth(1)
655
+ end
656
+
657
+ # Get the second document in the database for the criteria's selector or
658
+ # raise an error if none is found.
659
+ #
660
+ # @example Get the second document.
661
+ # context.second!
662
+ #
663
+ # @return [ Document ] The second document.
664
+ #
665
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
666
+ # documents available.
667
+ def second!
668
+ second || raise_document_not_found_error
669
+ end
670
+
671
+ # Get the third document in the database for the criteria's selector.
672
+ #
673
+ # @example Get the third document.
674
+ # context.third
675
+ #
676
+ # @return [ Document | nil ] The third document or nil if none is found.
677
+ def third
678
+ retrieve_nth(2)
679
+ end
680
+
681
+ # Get the third document in the database for the criteria's selector or
682
+ # raise an error if none is found.
683
+ #
684
+ # @example Get the third document.
685
+ # context.third!
686
+ #
687
+ # @return [ Document ] The third document.
688
+ #
689
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
690
+ # documents available.
691
+ def third!
692
+ third || raise_document_not_found_error
693
+ end
694
+
695
+ # Get the fourth document in the database for the criteria's selector.
696
+ #
697
+ # @example Get the fourth document.
698
+ # context.fourth
699
+ #
700
+ # @return [ Document | nil ] The fourth document or nil if none is found.
701
+ def fourth
702
+ retrieve_nth(3)
703
+ end
704
+
705
+ # Get the fourth document in the database for the criteria's selector or
706
+ # raise an error if none is found.
707
+ #
708
+ # @example Get the fourth document.
709
+ # context.fourth!
710
+ #
711
+ # @return [ Document ] The fourth document.
712
+ #
713
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
714
+ # documents available.
715
+ def fourth!
716
+ fourth || raise_document_not_found_error
717
+ end
718
+
719
+ # Get the fifth document in the database for the criteria's selector.
720
+ #
721
+ # @example Get the fifth document.
722
+ # context.fifth
723
+ #
724
+ # @return [ Document | nil ] The fifth document or nil if none is found.
725
+ def fifth
726
+ retrieve_nth(4)
727
+ end
728
+
729
+ # Get the fifth document in the database for the criteria's selector or
730
+ # raise an error if none is found.
731
+ #
732
+ # @example Get the fifth document.
733
+ # context.fifth!
734
+ #
735
+ # @return [ Document ] The fifth document.
736
+ #
737
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
738
+ # documents available.
739
+ def fifth!
740
+ fifth || raise_document_not_found_error
741
+ end
742
+
743
+ # Get the second to last document in the database for the criteria's
744
+ # selector.
745
+ #
746
+ # @example Get the second to last document.
747
+ # context.second_to_last
748
+ #
749
+ # @return [ Document | nil ] The second to last document or nil if none
750
+ # is found.
751
+ def second_to_last
752
+ retrieve_nth_to_last(1)
753
+ end
754
+
755
+ # Get the second to last document in the database for the criteria's
756
+ # selector or raise an error if none is found.
757
+ #
758
+ # @example Get the second to last document.
759
+ # context.second_to_last!
760
+ #
761
+ # @return [ Document ] The second to last document.
762
+ #
763
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
764
+ # documents available.
765
+ def second_to_last!
766
+ second_to_last || raise_document_not_found_error
767
+ end
768
+
769
+ # Get the third to last document in the database for the criteria's
770
+ # selector.
771
+ #
772
+ # @example Get the third to last document.
773
+ # context.third_to_last
774
+ #
775
+ # @return [ Document | nil ] The third to last document or nil if none
776
+ # is found.
777
+ def third_to_last
778
+ retrieve_nth_to_last(2)
779
+ end
780
+
781
+ # Get the third to last document in the database for the criteria's
782
+ # selector or raise an error if none is found.
783
+ #
784
+ # @example Get the third to last document.
785
+ # context.third_to_last!
786
+ #
787
+ # @return [ Document ] The third to last document.
788
+ #
789
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
790
+ # documents available.
791
+ def third_to_last!
792
+ third_to_last || raise_document_not_found_error
793
+ end
794
+
795
+ # Schedule a task to load documents for the context.
796
+ #
797
+ # Depending on the Mongoid configuration, the scheduled task can be executed
798
+ # immediately on the caller's thread, or can be scheduled for an
799
+ # asynchronous execution.
800
+ #
801
+ # @api private
802
+ def load_async
803
+ @documents_loader ||= DocumentsLoader.new(view, klass, criteria)
804
+ end
805
+
582
806
  private
583
807
 
584
808
  # Update the documents for the provided method.
@@ -646,24 +870,29 @@ module Mongoid
646
870
  Hash[sort.map{|k, v| [k, -1*v]}]
647
871
  end
648
872
 
649
- # Get the documents the context should iterate. This follows 3 rules:
873
+ # Get the documents the context should iterate.
650
874
  #
651
- # 1. If the query is cached, and we already have documents loaded, use
652
- # them.
653
- # 2. If we are eager loading, then eager load the documents and use
654
- # those.
655
- # 3. Use the query.
656
- #
657
- # @api private
658
- #
659
- # @example Get the documents for iteration.
660
- # context.documents_for_iteration
875
+ # If the documents have been already preloaded by `Document::Loader`
876
+ # instance, they will be used.
661
877
  #
662
878
  # @return [ Array<Document> | Mongo::Collection::View ] The docs to iterate.
879
+ #
880
+ # @api private
663
881
  def documents_for_iteration
664
- return view unless eager_loadable?
665
- docs = view.map{ |doc| Factory.from_db(klass, doc, criteria) }
666
- eager_load(docs)
882
+ if @documents_loader
883
+ if @documents_loader.started?
884
+ @documents_loader.value!
885
+ else
886
+ @documents_loader.unschedule
887
+ @documents_loader.execute
888
+ end
889
+ else
890
+ return view unless eager_loadable?
891
+ docs = view.map do |doc|
892
+ Factory.from_db(klass, doc, criteria)
893
+ end
894
+ eager_load(docs)
895
+ end
667
896
  end
668
897
 
669
898
  # Yield to the document.
@@ -682,8 +911,6 @@ module Mongoid
682
911
  yield(doc)
683
912
  end
684
913
 
685
- private
686
-
687
914
  def _session
688
915
  @criteria.send(:_session)
689
916
  end
@@ -819,22 +1046,32 @@ module Mongoid
819
1046
  limit ? docs : docs.first
820
1047
  end
821
1048
 
822
- # Queries whether the current context is valid for use with
823
- # the #count_documents? predicate. A context is valid if it
824
- # does not include a `$where` operator.
825
- #
826
- # @return [ true | false ] whether or not the current context
827
- # excludes a `$where` operator.
828
- def valid_for_count_documents?(hash = view.filter)
829
- # Note that `view.filter` is a BSON::Document, and all keys in a
830
- # BSON::Document are strings; we don't need to worry about symbol
831
- # representations of `$where`.
832
- hash.keys.each do |key|
833
- return false if key == '$where'
834
- return false if hash[key].is_a?(Hash) && !valid_for_count_documents?(hash[key])
1049
+ def raise_document_not_found_error
1050
+ raise Errors::DocumentNotFound.new(klass, nil, nil)
1051
+ end
1052
+
1053
+ def retrieve_nth(n)
1054
+ retrieve_nth_with_limit(n, 1).first
1055
+ end
1056
+
1057
+ def retrieve_nth_with_limit(n, limit)
1058
+ sort = view.sort || { _id: 1 }
1059
+ v = view.sort(sort).limit(limit || 1)
1060
+ v = v.skip(n) if n > 0
1061
+ if raw_docs = v.to_a
1062
+ process_raw_docs(raw_docs, limit)
835
1063
  end
1064
+ end
1065
+
1066
+ def retrieve_nth_to_last(n)
1067
+ retrieve_nth_to_last_with_limit(n, 1).first
1068
+ end
836
1069
 
837
- true
1070
+ def retrieve_nth_to_last_with_limit(n, limit)
1071
+ v = view.sort(inverse_sorting).skip(n).limit(limit || 1)
1072
+ v = v.skip(n) if n > 0
1073
+ raw_docs = v.to_a.reverse
1074
+ process_raw_docs(raw_docs, limit)
838
1075
  end
839
1076
  end
840
1077
  end