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
@@ -110,9 +110,24 @@ module Mongoid
110
110
  # @example Do any documents exist for the context.
111
111
  # context.exists?
112
112
  #
113
+ # @example Do any documents exist for given _id.
114
+ # context.exists?(BSON::ObjectId(...))
115
+ #
116
+ # @example Do any documents exist for given conditions.
117
+ # context.exists?(name: "...")
118
+ #
119
+ # @param [ Hash | Object | false ] id_or_conditions an _id to
120
+ # search for, a hash of conditions, nil or false.
121
+ #
113
122
  # @return [ true | false ] If the count is more than zero.
114
- def exists?
115
- any?
123
+ # Always false if passed nil or false.
124
+ def exists?(id_or_conditions = :none)
125
+ case id_or_conditions
126
+ when :none then any?
127
+ when nil, false then false
128
+ when Hash then Memory.new(criteria.where(id_or_conditions)).exists?
129
+ else Memory.new(criteria.where(_id: id_or_conditions)).exists?
130
+ end
116
131
  end
117
132
 
118
133
  # Get the first document in the database for the criteria's selector.
@@ -133,6 +148,20 @@ module Mongoid
133
148
  alias :one :first
134
149
  alias :find_first :first
135
150
 
151
+ # Get the first document in the database for the criteria's selector or
152
+ # raise an error if none is found.
153
+ #
154
+ # @example Get the first document.
155
+ # context.first!
156
+ #
157
+ # @return [ Document ] The first document.
158
+ #
159
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
160
+ # documents to take.
161
+ def first!
162
+ first || raise_document_not_found_error
163
+ end
164
+
136
165
  # Create the new in memory context.
137
166
  #
138
167
  # @example Create the new context.
@@ -180,37 +209,18 @@ module Mongoid
180
209
  end
181
210
  end
182
211
 
183
- # Take the given number of documents from the database.
184
- #
185
- # @example Take a document.
186
- # context.take
187
- #
188
- # @param [ Integer | nil ] limit The number of documents to take or nil.
212
+ # Get the last document in the database for the criteria's selector or
213
+ # raise an error if none is found.
189
214
  #
190
- # @return [ Document ] The document.
191
- def take(limit = nil)
192
- if limit
193
- eager_load(documents.take(limit))
194
- else
195
- eager_load([documents.first]).first
196
- end
197
- end
198
-
199
- # Take the given number of documents from the database.
200
- #
201
- # @example Take a document.
202
- # context.take
215
+ # @example Get the last document.
216
+ # context.last!
203
217
  #
204
- # @return [ Document ] The document.
218
+ # @return [ Document ] The last document.
205
219
  #
206
220
  # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
207
221
  # documents to take.
208
- def take!
209
- if documents.empty?
210
- raise Errors::DocumentNotFound.new(klass, nil, nil)
211
- else
212
- eager_load([documents.first]).first
213
- end
222
+ def last!
223
+ last || raise_document_not_found_error
214
224
  end
215
225
 
216
226
  # Get the length of matching documents in the context.
@@ -242,7 +252,7 @@ module Mongoid
242
252
  # @example Get the values in memory.
243
253
  # context.pluck(:name)
244
254
  #
245
- # @param [ String | Symbol ] *fields Field(s) to pluck.
255
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pluck.
246
256
  #
247
257
  # @return [ Array<Object> | Array<Array<Object>> ] The plucked values.
248
258
  def pluck(*fields)
@@ -260,9 +270,9 @@ module Mongoid
260
270
  # @example Get the values in memory.
261
271
  # context.pick(:name)
262
272
  #
263
- # @param [ String | Symbol ] *fields Field(s) to pick.
273
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pick.
264
274
  #
265
- # @return [ Object, Array<Object> ] The picked values.
275
+ # @return [ Object | Array<Object> ] The picked values.
266
276
  def pick(*fields)
267
277
  if doc = documents.first
268
278
  pluck_from_doc(doc, *fields)
@@ -285,6 +295,36 @@ module Mongoid
285
295
  end
286
296
  end
287
297
 
298
+ # Take the given number of documents from the database.
299
+ #
300
+ # @example Take a document.
301
+ # context.take
302
+ #
303
+ # @param [ Integer | nil ] limit The number of documents to take or nil.
304
+ #
305
+ # @return [ Document ] The document.
306
+ def take(limit = nil)
307
+ if limit
308
+ eager_load(documents.take(limit))
309
+ else
310
+ eager_load([documents.first]).first
311
+ end
312
+ end
313
+
314
+ # Take the given number of documents from the database or raise an error
315
+ # if none are found.
316
+ #
317
+ # @example Take a document.
318
+ # context.take
319
+ #
320
+ # @return [ Document ] The document.
321
+ #
322
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
323
+ # documents to take.
324
+ def take!
325
+ take || raise_document_not_found_error
326
+ end
327
+
288
328
  # Skips the provided number of documents.
289
329
  #
290
330
  # @example Skip the documents.
@@ -335,6 +375,162 @@ module Mongoid
335
375
  update_documents(attributes, entries)
336
376
  end
337
377
 
378
+ # Get the second document in the database for the criteria's selector.
379
+ #
380
+ # @example Get the second document.
381
+ # context.second
382
+ #
383
+ # @param [ Integer ] limit The number of documents to return.
384
+ #
385
+ # @return [ Document ] The second document.
386
+ def second
387
+ eager_load([documents.second]).first
388
+ end
389
+
390
+ # Get the second document in the database for the criteria's selector or
391
+ # raise an error if none is found.
392
+ #
393
+ # @example Get the second document.
394
+ # context.second!
395
+ #
396
+ # @return [ Document ] The second document.
397
+ #
398
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
399
+ # documents to take.
400
+ def second!
401
+ second || raise_document_not_found_error
402
+ end
403
+
404
+ # Get the third document in the database for the criteria's selector.
405
+ #
406
+ # @example Get the third document.
407
+ # context.third
408
+ #
409
+ # @param [ Integer ] limit The number of documents to return.
410
+ #
411
+ # @return [ Document ] The third document.
412
+ def third
413
+ eager_load([documents.third]).first
414
+ end
415
+
416
+ # Get the third document in the database for the criteria's selector or
417
+ # raise an error if none is found.
418
+ #
419
+ # @example Get the third document.
420
+ # context.third!
421
+ #
422
+ # @return [ Document ] The third document.
423
+ #
424
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
425
+ # documents to take.
426
+ def third!
427
+ third || raise_document_not_found_error
428
+ end
429
+
430
+ # Get the fourth document in the database for the criteria's selector.
431
+ #
432
+ # @example Get the fourth document.
433
+ # context.fourth
434
+ #
435
+ # @param [ Integer ] limit The number of documents to return.
436
+ #
437
+ # @return [ Document ] The fourth document.
438
+ def fourth
439
+ eager_load([documents.fourth]).first
440
+ end
441
+
442
+ # Get the fourth document in the database for the criteria's selector or
443
+ # raise an error if none is found.
444
+ #
445
+ # @example Get the fourth document.
446
+ # context.fourth!
447
+ #
448
+ # @return [ Document ] The fourth document.
449
+ #
450
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
451
+ # documents to take.
452
+ def fourth!
453
+ fourth || raise_document_not_found_error
454
+ end
455
+
456
+ # Get the fifth document in the database for the criteria's selector.
457
+ #
458
+ # @example Get the fifth document.
459
+ # context.fifth
460
+ #
461
+ # @param [ Integer ] limit The number of documents to return.
462
+ #
463
+ # @return [ Document ] The fifth document.
464
+ def fifth
465
+ eager_load([documents.fifth]).first
466
+ end
467
+
468
+ # Get the fifth document in the database for the criteria's selector or
469
+ # raise an error if none is found.
470
+ #
471
+ # @example Get the fifth document.
472
+ # context.fifth!
473
+ #
474
+ # @return [ Document ] The fifth document.
475
+ #
476
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
477
+ # documents to take.
478
+ def fifth!
479
+ fifth || raise_document_not_found_error
480
+ end
481
+
482
+ # Get the second to last document in the database for the criteria's selector.
483
+ #
484
+ # @example Get the second to last document.
485
+ # context.second_to_last
486
+ #
487
+ # @param [ Integer ] limit The number of documents to return.
488
+ #
489
+ # @return [ Document ] The second to last document.
490
+ def second_to_last
491
+ eager_load([documents.second_to_last]).first
492
+ end
493
+
494
+ # Get the second to last document in the database for the criteria's selector or
495
+ # raise an error if none is found.
496
+ #
497
+ # @example Get the second to last document.
498
+ # context.second_to_last!
499
+ #
500
+ # @return [ Document ] The second to last document.
501
+ #
502
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
503
+ # documents to take.
504
+ def second_to_last!
505
+ second_to_last || raise_document_not_found_error
506
+ end
507
+
508
+ # Get the third to last document in the database for the criteria's selector.
509
+ #
510
+ # @example Get the third to last document.
511
+ # context.third_to_last
512
+ #
513
+ # @param [ Integer ] limit The number of documents to return.
514
+ #
515
+ # @return [ Document ] The third to last document.
516
+ def third_to_last
517
+ eager_load([documents.third_to_last]).first
518
+ end
519
+
520
+ # Get the third to last document in the database for the criteria's selector or
521
+ # raise an error if none is found.
522
+ #
523
+ # @example Get the third to last document.
524
+ # context.third_to_last!
525
+ #
526
+ # @return [ Document ] The third to last document.
527
+ #
528
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
529
+ # documents to take.
530
+ def third_to_last!
531
+ third_to_last || raise_document_not_found_error
532
+ end
533
+
338
534
  private
339
535
 
340
536
  # Get the documents the context should iterate. This follows 3 rules:
@@ -561,9 +757,9 @@ module Mongoid
561
757
  # Pluck the field values from the given document.
562
758
  #
563
759
  # @param [ Document ] doc The document to pluck from.
564
- # @param [ String | Symbol ] *fields Field(s) to pluck.
760
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pluck.
565
761
  #
566
- # @return [ Object, Array<Object> ] The plucked values.
762
+ # @return [ Object | Array<Object> ] The plucked values.
567
763
  def pluck_from_doc(doc, *fields)
568
764
  if fields.length == 1
569
765
  retrieve_value_at_path(doc, fields.first)
@@ -573,6 +769,10 @@ module Mongoid
573
769
  end
574
770
  end
575
771
  end
772
+
773
+ def raise_document_not_found_error
774
+ raise Errors::DocumentNotFound.new(klass, nil, nil)
775
+ end
576
776
  end
577
777
  end
578
778
  end
@@ -0,0 +1,177 @@
1
+ require "mongoid/association/eager_loadable"
2
+
3
+ module Mongoid
4
+ module Contextual
5
+ class Mongo
6
+ # Loads documents for the provided criteria.
7
+ #
8
+ # @api private
9
+ class DocumentsLoader
10
+ extend Forwardable
11
+ include Association::EagerLoadable
12
+
13
+ def_delegators :@future, :value!, :value, :wait!, :wait
14
+
15
+ # Returns synchronous executor to be used when async_query_executor config option
16
+ # is set to :immediate. This executor runs all operations on the current
17
+ # thread, blocking as necessary.
18
+ #
19
+ # @return [ Concurrent::ImmediateExecutor ] The executor
20
+ # to be used to execute document loading tasks.
21
+ def self.immediate_executor
22
+ @@immediate_executor ||= Concurrent::ImmediateExecutor.new
23
+ end
24
+
25
+ # Returns asynchronous executor to be used when async_query_executor config option
26
+ # is set to :global_thread_pool. This executor runs operations on background threads
27
+ # using a thread pool.
28
+ #
29
+ # @return [ Concurrent::ThreadPoolExecutor ] The executor
30
+ # to be used to execute document loading tasks.
31
+ def self.global_thread_pool_async_query_executor
32
+ create_pool = Proc.new do |concurrency|
33
+ Concurrent::ThreadPoolExecutor.new(
34
+ min_threads: 0,
35
+ max_threads: concurrency,
36
+ max_queue: concurrency * 4,
37
+ fallback_policy: :caller_runs
38
+ )
39
+ end
40
+ concurrency = Mongoid.global_executor_concurrency || 4
41
+ @@global_thread_pool_async_query_executor ||= create_pool.call(concurrency)
42
+ if @@global_thread_pool_async_query_executor.max_length != concurrency
43
+ old_pool = @@global_thread_pool_async_query_executor
44
+ @@global_thread_pool_async_query_executor = create_pool.call(concurrency)
45
+ old_pool.shutdown
46
+ end
47
+ @@global_thread_pool_async_query_executor
48
+ end
49
+
50
+ # Returns suitable executor according to Mongoid config options.
51
+ #
52
+ # @param [ String | Symbol] name The query executor name, can be either
53
+ # :immediate or :global_thread_pool. Defaulted to `async_query_executor`
54
+ # config option.
55
+ #
56
+ # @return [ Concurrent::ImmediateExecutor | Concurrent::ThreadPoolExecutor ] The executor
57
+ # to be used to execute document loading tasks.
58
+ #
59
+ # @raise [ Errors::InvalidQueryExecutor ] If an unknown name is provided.
60
+ def self.executor(name = Mongoid.async_query_executor)
61
+ case name.to_sym
62
+ when :immediate
63
+ immediate_executor
64
+ when :global_thread_pool
65
+ global_thread_pool_async_query_executor
66
+ else
67
+ raise Errors::InvalidQueryExecutor.new(name)
68
+ end
69
+ end
70
+
71
+ # @return [ Mongoid::Criteria ] Criteria that specifies which documents should
72
+ # be loaded. Exposed here because `eager_loadable?` method from
73
+ # `Association::EagerLoadable` expects this to be available.
74
+ attr_accessor :criteria
75
+
76
+ # Instantiates the document loader instance and immediately schedules
77
+ # its execution using the provided executor.
78
+ #
79
+ # @param [ Mongo::Collection::View ] view The collection view to get
80
+ # records from the database.
81
+ # @param [ Class ] klass Mongoid model class to instantiate documents.
82
+ # All records obtained from the database will be converted to an
83
+ # instance of this class, if possible.
84
+ # @param [ Mongoid::Criteria ] criteria. Criteria that specifies which
85
+ # documents should be loaded.
86
+ # @param [ Concurrent::AbstractExecutorService ] executor. Executor that
87
+ # is capable of running `Concurrent::Promises::Future` instances.
88
+ def initialize(view, klass, criteria, executor: self.class.executor)
89
+ @view = view
90
+ @klass = klass
91
+ @criteria = criteria
92
+ @mutex = Mutex.new
93
+ @state = :pending
94
+ @future = Concurrent::Promises.future_on(executor) do
95
+ start && execute
96
+ end
97
+ end
98
+
99
+ # Returns false or true whether the loader is in pending state.
100
+ #
101
+ # Pending state means that the loader execution has been scheduled,
102
+ # but has not been started yet.
103
+ #
104
+ # @return [ true | false ] true if the loader is in pending state,
105
+ # otherwise false.
106
+ def pending?
107
+ @mutex.synchronize do
108
+ @state == :pending
109
+ end
110
+ end
111
+
112
+ # Returns false or true whether the loader is in started state.
113
+ #
114
+ # Started state means that the loader execution has been started.
115
+ # Note that the loader stays in this state even after the execution
116
+ # completed (successfully or failed).
117
+ #
118
+ # @return [ true | false ] true if the loader is in started state,
119
+ # otherwise false.
120
+ def started?
121
+ @mutex.synchronize do
122
+ @state == :started
123
+ end
124
+ end
125
+
126
+ # Mark the loader as unscheduled.
127
+ #
128
+ # If the loader is marked unscheduled, it will not be executed. The only
129
+ # option to load the documents is to call `execute` method directly.
130
+ #
131
+ # Please note that if execution of a task has been already started,
132
+ # unscheduling does not have any effect.
133
+ def unschedule
134
+ @mutex.synchronize do
135
+ @state = :cancelled unless @state == :started
136
+ end
137
+ end
138
+
139
+ # Loads records specified by `@criteria` from the database, and convert
140
+ # them to Mongoid documents of `@klass` type.
141
+ #
142
+ # This method is called by the task (possibly asynchronous) scheduled
143
+ # when creating an instance of the loader. However, this method can be
144
+ # called directly, if it is desired to execute loading on the caller
145
+ # thread immediately.
146
+ #
147
+ # Calling this method does not change the state of the loader.
148
+ #
149
+ # @return [ Array<Mongoid::Document> ] Array of document loaded from
150
+ # the database.
151
+ def execute
152
+ documents = @view.map do |doc|
153
+ Factory.from_db(@klass, doc, @criteria)
154
+ end
155
+ eager_load(documents) if eager_loadable?
156
+ documents
157
+ end
158
+
159
+ private
160
+
161
+ # Mark the loader as started if possible.
162
+ #
163
+ # @return [ true | false ] Whether the state was changed to :started.
164
+ def start
165
+ @mutex.synchronize do
166
+ if @state == :pending
167
+ @state = :started
168
+ true
169
+ else
170
+ false
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end