mongoid 8.0.5 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) 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/nested/one.rb +40 -2
  17. data/lib/mongoid/association/proxy.rb +1 -1
  18. data/lib/mongoid/association/referenced/counter_cache.rb +2 -2
  19. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +1 -1
  20. data/lib/mongoid/association/referenced/has_many/enumerable.rb +2 -2
  21. data/lib/mongoid/association/referenced/has_many/proxy.rb +3 -3
  22. data/lib/mongoid/association/reflections.rb +2 -2
  23. data/lib/mongoid/atomic.rb +0 -7
  24. data/lib/mongoid/attributes/dynamic.rb +1 -1
  25. data/lib/mongoid/attributes/nested.rb +2 -2
  26. data/lib/mongoid/attributes/projector.rb +1 -1
  27. data/lib/mongoid/attributes/readonly.rb +1 -1
  28. data/lib/mongoid/attributes.rb +8 -2
  29. data/lib/mongoid/changeable.rb +107 -5
  30. data/lib/mongoid/clients/storage_options.rb +2 -5
  31. data/lib/mongoid/clients/validators/storage.rb +1 -13
  32. data/lib/mongoid/collection_configurable.rb +58 -0
  33. data/lib/mongoid/composable.rb +2 -0
  34. data/lib/mongoid/config/defaults.rb +60 -0
  35. data/lib/mongoid/config/validators/async_query_executor.rb +24 -0
  36. data/lib/mongoid/config/validators.rb +1 -0
  37. data/lib/mongoid/config.rb +101 -0
  38. data/lib/mongoid/contextual/atomic.rb +1 -1
  39. data/lib/mongoid/contextual/memory.rb +233 -33
  40. data/lib/mongoid/contextual/mongo/documents_loader.rb +177 -0
  41. data/lib/mongoid/contextual/mongo.rb +373 -113
  42. data/lib/mongoid/contextual/none.rb +162 -7
  43. data/lib/mongoid/contextual.rb +12 -0
  44. data/lib/mongoid/criteria/findable.rb +2 -2
  45. data/lib/mongoid/criteria/includable.rb +4 -3
  46. data/lib/mongoid/criteria/queryable/key.rb +1 -1
  47. data/lib/mongoid/criteria/queryable/mergeable.rb +1 -1
  48. data/lib/mongoid/criteria/queryable/optional.rb +8 -8
  49. data/lib/mongoid/criteria/queryable/selectable.rb +43 -12
  50. data/lib/mongoid/criteria.rb +6 -5
  51. data/lib/mongoid/deprecable.rb +1 -1
  52. data/lib/mongoid/errors/create_collection_failure.rb +33 -0
  53. data/lib/mongoid/errors/drop_collection_failure.rb +27 -0
  54. data/lib/mongoid/errors/immutable_attribute.rb +26 -0
  55. data/lib/mongoid/errors/invalid_async_query_executor.rb +25 -0
  56. data/lib/mongoid/errors/invalid_global_executor_concurrency.rb +22 -0
  57. data/lib/mongoid/errors/invalid_storage_parent.rb +2 -0
  58. data/lib/mongoid/errors.rb +4 -1
  59. data/lib/mongoid/extensions/object.rb +2 -2
  60. data/lib/mongoid/extensions/time.rb +2 -0
  61. data/lib/mongoid/fields/localized.rb +10 -0
  62. data/lib/mongoid/fields/standard.rb +10 -0
  63. data/lib/mongoid/fields.rb +69 -13
  64. data/lib/mongoid/findable.rb +27 -3
  65. data/lib/mongoid/interceptable.rb +7 -6
  66. data/lib/mongoid/matcher/eq_impl.rb +1 -1
  67. data/lib/mongoid/matcher/type.rb +1 -1
  68. data/lib/mongoid/persistable/creatable.rb +1 -0
  69. data/lib/mongoid/persistable/deletable.rb +1 -1
  70. data/lib/mongoid/persistable/savable.rb +13 -1
  71. data/lib/mongoid/persistable/unsettable.rb +2 -2
  72. data/lib/mongoid/persistable/updatable.rb +51 -1
  73. data/lib/mongoid/persistable/upsertable.rb +20 -1
  74. data/lib/mongoid/persistable.rb +3 -0
  75. data/lib/mongoid/query_cache.rb +5 -1
  76. data/lib/mongoid/railties/database.rake +7 -2
  77. data/lib/mongoid/reloadable.rb +5 -3
  78. data/lib/mongoid/stateful.rb +22 -1
  79. data/lib/mongoid/tasks/database.rake +12 -0
  80. data/lib/mongoid/tasks/database.rb +20 -0
  81. data/lib/mongoid/utils.rb +22 -0
  82. data/lib/mongoid/validatable/macros.rb +5 -5
  83. data/lib/mongoid/validatable.rb +4 -1
  84. data/lib/mongoid/version.rb +1 -1
  85. data/lib/mongoid/warnings.rb +17 -1
  86. data/lib/mongoid.rb +16 -3
  87. data/spec/integration/app_spec.rb +2 -2
  88. data/spec/integration/callbacks_models.rb +37 -0
  89. data/spec/integration/callbacks_spec.rb +134 -0
  90. data/spec/integration/discriminator_key_spec.rb +4 -5
  91. data/spec/integration/i18n_fallbacks_spec.rb +3 -2
  92. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +27 -0
  93. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +20 -25
  94. data/spec/mongoid/association/embedded/embeds_many_models.rb +1 -0
  95. data/spec/mongoid/association/embedded/embeds_one/proxy_spec.rb +15 -2
  96. data/spec/mongoid/association/referenced/belongs_to_spec.rb +2 -18
  97. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +5 -27
  98. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +9 -50
  99. data/spec/mongoid/association/syncable_spec.rb +1 -1
  100. data/spec/mongoid/attributes_spec.rb +3 -6
  101. data/spec/mongoid/changeable_spec.rb +299 -24
  102. data/spec/mongoid/clients_spec.rb +122 -13
  103. data/spec/mongoid/collection_configurable_spec.rb +158 -0
  104. data/spec/mongoid/config/defaults_spec.rb +160 -0
  105. data/spec/mongoid/config_spec.rb +154 -18
  106. data/spec/mongoid/contextual/memory_spec.rb +332 -76
  107. data/spec/mongoid/contextual/mongo/documents_loader_spec.rb +187 -0
  108. data/spec/mongoid/contextual/mongo_spec.rb +995 -36
  109. data/spec/mongoid/contextual/none_spec.rb +49 -2
  110. data/spec/mongoid/copyable_spec.rb +3 -11
  111. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +4 -10
  112. data/spec/mongoid/criteria/queryable/options_spec.rb +1 -1
  113. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +419 -0
  114. data/spec/mongoid/criteria/queryable/selectable_spec.rb +1 -1
  115. data/spec/mongoid/criteria/queryable/selector_spec.rb +1 -1
  116. data/spec/mongoid/criteria_projection_spec.rb +1 -4
  117. data/spec/mongoid/criteria_spec.rb +5 -9
  118. data/spec/mongoid/errors/readonly_document_spec.rb +2 -2
  119. data/spec/mongoid/extensions/time_spec.rb +8 -43
  120. data/spec/mongoid/extensions/time_with_zone_spec.rb +7 -52
  121. data/spec/mongoid/fields/localized_spec.rb +46 -28
  122. data/spec/mongoid/fields_spec.rb +136 -34
  123. data/spec/mongoid/findable_spec.rb +391 -34
  124. data/spec/mongoid/indexable_spec.rb +16 -10
  125. data/spec/mongoid/interceptable_spec.rb +15 -3
  126. data/spec/mongoid/persistable/deletable_spec.rb +26 -6
  127. data/spec/mongoid/persistable/destroyable_spec.rb +26 -6
  128. data/spec/mongoid/persistable/incrementable_spec.rb +37 -0
  129. data/spec/mongoid/persistable/logical_spec.rb +37 -0
  130. data/spec/mongoid/persistable/poppable_spec.rb +36 -0
  131. data/spec/mongoid/persistable/pullable_spec.rb +72 -0
  132. data/spec/mongoid/persistable/pushable_spec.rb +72 -0
  133. data/spec/mongoid/persistable/renamable_spec.rb +36 -0
  134. data/spec/mongoid/persistable/savable_spec.rb +96 -0
  135. data/spec/mongoid/persistable/settable_spec.rb +37 -0
  136. data/spec/mongoid/persistable/unsettable_spec.rb +36 -0
  137. data/spec/mongoid/persistable/updatable_spec.rb +20 -28
  138. data/spec/mongoid/persistable/upsertable_spec.rb +80 -6
  139. data/spec/mongoid/persistence_context_spec.rb +7 -57
  140. data/spec/mongoid/query_cache_spec.rb +56 -61
  141. data/spec/mongoid/reloadable_spec.rb +24 -28
  142. data/spec/mongoid/scopable_spec.rb +70 -0
  143. data/spec/mongoid/serializable_spec.rb +9 -30
  144. data/spec/mongoid/stateful_spec.rb +122 -8
  145. data/spec/mongoid/tasks/database_rake_spec.rb +74 -0
  146. data/spec/mongoid/tasks/database_spec.rb +127 -0
  147. data/spec/mongoid/timestamps_spec.rb +9 -11
  148. data/spec/mongoid/touchable_spec.rb +277 -5
  149. data/spec/mongoid/touchable_spec_models.rb +3 -1
  150. data/spec/mongoid/traversable_spec.rb +9 -24
  151. data/spec/mongoid/validatable/uniqueness_spec.rb +2 -3
  152. data/spec/mongoid_spec.rb +36 -10
  153. data/spec/shared/lib/mrss/docker_runner.rb +7 -0
  154. data/spec/shared/lib/mrss/event_subscriber.rb +15 -5
  155. data/spec/shared/lib/mrss/lite_constraints.rb +10 -2
  156. data/spec/shared/lib/mrss/server_version_registry.rb +16 -23
  157. data/spec/shared/lib/mrss/utils.rb +28 -6
  158. data/spec/shared/share/Dockerfile.erb +36 -40
  159. data/spec/shared/shlib/server.sh +32 -8
  160. data/spec/shared/shlib/set_env.sh +4 -4
  161. data/spec/spec_helper.rb +5 -0
  162. data/spec/support/immutable_ids.rb +118 -0
  163. data/spec/support/macros.rb +47 -15
  164. data/spec/support/models/artist.rb +0 -1
  165. data/spec/support/models/band.rb +1 -0
  166. data/spec/support/models/book.rb +1 -0
  167. data/spec/support/models/building.rb +2 -0
  168. data/spec/support/models/cover.rb +10 -0
  169. data/spec/support/models/product.rb +1 -0
  170. data.tar.gz.sig +0 -0
  171. metadata +686 -650
  172. metadata.gz.sig +0 -0
  173. data/spec/mongoid/criteria/queryable/extensions/bignum_spec.rb +0 -60
  174. data/spec/mongoid/criteria/queryable/extensions/fixnum_spec.rb +0 -60
@@ -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.
@@ -154,22 +167,28 @@ module Mongoid
154
167
  # @example Do any documents exist for the context.
155
168
  # context.exists?
156
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
+ #
157
176
  # @note We don't use count here since Mongo does not use counted
158
177
  # b-tree indexes.
159
178
  #
160
- # @return [ true | false ] If the count is more than zero.
161
- def exists?
162
- !!(view.projection(_id: 1).limit(1).first)
163
- end
164
-
165
- # Run an explain on the criteria.
166
- #
167
- # @example Explain the criteria.
168
- # 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.
169
181
  #
170
- # @return [ Hash ] The explain result.
171
- def explain
172
- 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
173
192
  end
174
193
 
175
194
  # Execute the find and modify command, used for MongoDB's
@@ -225,28 +244,6 @@ module Mongoid
225
244
  end
226
245
  end
227
246
 
228
- # Get the first document in the database for the criteria's selector.
229
- #
230
- # @example Get the first document.
231
- # context.first
232
- #
233
- # @note Automatically adding a sort on _id when no other sort is
234
- # defined on the criteria has the potential to cause bad performance issues.
235
- # If you experience unexpected poor performance when using #first or #last
236
- # and have no sort defined on the criteria, use #take instead.
237
- # Be aware that #take won't guarantee order.
238
- #
239
- # @param [ Integer ] limit The number of documents to return.
240
- #
241
- # @return [ Document ] The first document.
242
- def first(limit = nil)
243
- sort = view.sort || { _id: 1 }
244
- if raw_docs = view.sort(sort).limit(limit || 1).to_a
245
- process_raw_docs(raw_docs, limit)
246
- end
247
- end
248
- alias :one :first
249
-
250
247
  # Return the first result without applying sort
251
248
  #
252
249
  # @api private
@@ -297,25 +294,6 @@ module Mongoid
297
294
 
298
295
  def_delegator :@klass, :database_field_name
299
296
 
300
- # Get the last document in the database for the criteria's selector.
301
- #
302
- # @example Get the last document.
303
- # context.last
304
- #
305
- # @note Automatically adding a sort on _id when no other sort is
306
- # defined on the criteria has the potential to cause bad performance issues.
307
- # If you experience unexpected poor performance when using #first or #last
308
- # and have no sort defined on the criteria, use #take instead.
309
- # Be aware that #take won't guarantee order.
310
- #
311
- # @param [ Integer ] limit The number of documents to return.
312
- #
313
- # @return [ Document ] The last document.
314
- def last(limit = nil)
315
- raw_docs = view.sort(inverse_sorting).limit(limit || 1).to_a.reverse
316
- process_raw_docs(raw_docs, limit)
317
- end
318
-
319
297
  # Returns the number of documents in the database matching
320
298
  # the query selector.
321
299
  #
@@ -340,44 +318,6 @@ module Mongoid
340
318
  @view = view.limit(value) and self
341
319
  end
342
320
 
343
- # Take the given number of documents from the database.
344
- #
345
- # @example Take 10 documents
346
- # context.take(10)
347
- #
348
- # @param [ Integer | nil ] limit The number of documents to return or nil.
349
- #
350
- # @return [ Document | Array<Document> ] The list of documents, or one
351
- # document if no value was given.
352
- def take(limit = nil)
353
- if limit
354
- limit(limit).to_a
355
- else
356
- # Do to_a first so that the Mongo#first method is not used and the
357
- # result is not sorted.
358
- limit(1).to_a.first
359
- end
360
- end
361
-
362
- # Take one document from the database and raise an error if there are none.
363
- #
364
- # @example Take a document
365
- # context.take!
366
- #
367
- # @return [ Document ] The document.
368
- #
369
- # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
370
- # documents to take.
371
- def take!
372
- # Do to_a first so that the Mongo#first method is not used and the
373
- # result is not sorted.
374
- if fst = limit(1).to_a.first
375
- fst
376
- else
377
- raise Errors::DocumentNotFound.new(klass, nil, nil)
378
- end
379
- end
380
-
381
321
  # Initiate a map/reduce operation from the context.
382
322
  #
383
323
  # @example Initiate a map/reduce.
@@ -391,15 +331,22 @@ module Mongoid
391
331
  MapReduce.new(collection, criteria, map, reduce)
392
332
  end
393
333
 
394
- # Pluck the single field values from the database. Will return duplicates
395
- # 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.
396
339
  #
397
340
  # @example Pluck a field.
398
341
  # context.pluck(:_id)
399
342
  #
400
- # @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.
401
345
  #
402
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.
403
350
  def pluck(*fields)
404
351
  # Multiple fields can map to the same field name. For example, plucking
405
352
  # a field and its _translations field map to the same field in the database.
@@ -434,13 +381,51 @@ module Mongoid
434
381
  # @example Pick a field.
435
382
  # context.pick(:_id)
436
383
  #
437
- # @param [ String | Symbol ] *fields Field(s) to pick.
384
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pick.
438
385
  #
439
386
  # @return [ Object | Array<Object> ] The picked values.
440
387
  def pick(*fields)
441
388
  limit(1).pluck(*fields).first
442
389
  end
443
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
+
444
429
  # Get a hash of counts for the values of a single field. For example,
445
430
  # if the following documents were in the database:
446
431
  #
@@ -553,7 +538,7 @@ module Mongoid
553
538
  # @option opts [ Array ] :array_filters A set of filters specifying to which array elements
554
539
  # an update should apply.
555
540
  #
556
- # @return [ nil, false ] False if no attributes were provided.
541
+ # @return [ nil | false ] False if no attributes were provided.
557
542
  def update(attributes = nil, opts = {})
558
543
  update_documents(attributes, :update_one, opts)
559
544
  end
@@ -569,11 +554,255 @@ module Mongoid
569
554
  # @option opts [ Array ] :array_filters A set of filters specifying to which array elements
570
555
  # an update should apply.
571
556
  #
572
- # @return [ nil, false ] False if no attributes were provided.
557
+ # @return [ nil | false ] False if no attributes were provided.
573
558
  def update_all(attributes = nil, opts = {})
574
559
  update_documents(attributes, :update_many, opts)
575
560
  end
576
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
+
577
806
  private
578
807
 
579
808
  # Update the documents for the provided method.
@@ -641,24 +870,29 @@ module Mongoid
641
870
  Hash[sort.map{|k, v| [k, -1*v]}]
642
871
  end
643
872
 
644
- # Get the documents the context should iterate. This follows 3 rules:
873
+ # Get the documents the context should iterate.
645
874
  #
646
- # 1. If the query is cached, and we already have documents loaded, use
647
- # them.
648
- # 2. If we are eager loading, then eager load the documents and use
649
- # those.
650
- # 3. Use the query.
651
- #
652
- # @api private
653
- #
654
- # @example Get the documents for iteration.
655
- # context.documents_for_iteration
875
+ # If the documents have been already preloaded by `Document::Loader`
876
+ # instance, they will be used.
656
877
  #
657
878
  # @return [ Array<Document> | Mongo::Collection::View ] The docs to iterate.
879
+ #
880
+ # @api private
658
881
  def documents_for_iteration
659
- return view unless eager_loadable?
660
- docs = view.map{ |doc| Factory.from_db(klass, doc, criteria) }
661
- 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
662
896
  end
663
897
 
664
898
  # Yield to the document.
@@ -677,8 +911,6 @@ module Mongoid
677
911
  yield(doc)
678
912
  end
679
913
 
680
- private
681
-
682
914
  def _session
683
915
  @criteria.send(:_session)
684
916
  end
@@ -813,6 +1045,34 @@ module Mongoid
813
1045
  docs = eager_load(docs)
814
1046
  limit ? docs : docs.first
815
1047
  end
1048
+
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)
1063
+ end
1064
+ end
1065
+
1066
+ def retrieve_nth_to_last(n)
1067
+ retrieve_nth_to_last_with_limit(n, 1).first
1068
+ end
1069
+
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)
1075
+ end
816
1076
  end
817
1077
  end
818
1078
  end