mongoid 8.0.13 → 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 (225) 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 +17 -66
  6. data/lib/config/locales/en.yml +46 -40
  7. data/lib/mongoid/association/accessors.rb +3 -7
  8. data/lib/mongoid/association/builders.rb +1 -1
  9. data/lib/mongoid/association/depending.rb +4 -8
  10. data/lib/mongoid/association/eager_loadable.rb +0 -3
  11. data/lib/mongoid/association/embedded/batchable.rb +2 -2
  12. data/lib/mongoid/association/embedded/embedded_in/buildable.rb +2 -2
  13. data/lib/mongoid/association/embedded/embedded_in/proxy.rb +2 -1
  14. data/lib/mongoid/association/embedded/embeds_many/buildable.rb +3 -2
  15. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +6 -6
  16. data/lib/mongoid/association/embedded/embeds_one/buildable.rb +1 -1
  17. data/lib/mongoid/association/embedded/embeds_one/proxy.rb +2 -2
  18. data/lib/mongoid/association/macros.rb +0 -6
  19. data/lib/mongoid/association/nested/many.rb +3 -38
  20. data/lib/mongoid/association/nested/nested_buildable.rb +0 -14
  21. data/lib/mongoid/association/nested/one.rb +40 -2
  22. data/lib/mongoid/association/proxy.rb +1 -1
  23. data/lib/mongoid/association/referenced/counter_cache.rb +2 -2
  24. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +1 -1
  25. data/lib/mongoid/association/referenced/has_many/enumerable.rb +6 -23
  26. data/lib/mongoid/association/referenced/has_many/proxy.rb +4 -103
  27. data/lib/mongoid/association/reflections.rb +2 -2
  28. data/lib/mongoid/atomic.rb +7 -16
  29. data/lib/mongoid/attributes/dynamic.rb +1 -1
  30. data/lib/mongoid/attributes/nested.rb +2 -2
  31. data/lib/mongoid/attributes/processing.rb +5 -29
  32. data/lib/mongoid/attributes/projector.rb +1 -1
  33. data/lib/mongoid/attributes/readonly.rb +1 -1
  34. data/lib/mongoid/attributes.rb +8 -2
  35. data/lib/mongoid/changeable.rb +107 -5
  36. data/lib/mongoid/clients/factory.rb +0 -4
  37. data/lib/mongoid/clients/storage_options.rb +2 -5
  38. data/lib/mongoid/clients/validators/storage.rb +1 -13
  39. data/lib/mongoid/collection_configurable.rb +58 -0
  40. data/lib/mongoid/composable.rb +2 -0
  41. data/lib/mongoid/config/defaults.rb +60 -0
  42. data/lib/mongoid/config/options.rb +0 -3
  43. data/lib/mongoid/config/validators/async_query_executor.rb +24 -0
  44. data/lib/mongoid/config/validators.rb +1 -0
  45. data/lib/mongoid/config.rb +88 -89
  46. data/lib/mongoid/contextual/aggregable/memory.rb +2 -5
  47. data/lib/mongoid/contextual/atomic.rb +1 -1
  48. data/lib/mongoid/contextual/memory.rb +240 -51
  49. data/lib/mongoid/contextual/mongo/documents_loader.rb +177 -0
  50. data/lib/mongoid/contextual/mongo.rb +370 -133
  51. data/lib/mongoid/contextual/none.rb +162 -7
  52. data/lib/mongoid/contextual.rb +12 -0
  53. data/lib/mongoid/criteria/findable.rb +2 -2
  54. data/lib/mongoid/criteria/includable.rb +4 -3
  55. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -15
  56. data/lib/mongoid/criteria/queryable/key.rb +1 -1
  57. data/lib/mongoid/criteria/queryable/mergeable.rb +1 -13
  58. data/lib/mongoid/criteria/queryable/optional.rb +8 -8
  59. data/lib/mongoid/criteria/queryable/selectable.rb +43 -121
  60. data/lib/mongoid/criteria/queryable/selector.rb +1 -1
  61. data/lib/mongoid/criteria/queryable/storable.rb +1 -1
  62. data/lib/mongoid/criteria.rb +6 -5
  63. data/lib/mongoid/deprecable.rb +1 -2
  64. data/lib/mongoid/deprecation.rb +3 -3
  65. data/lib/mongoid/document.rb +1 -8
  66. data/lib/mongoid/errors/create_collection_failure.rb +33 -0
  67. data/lib/mongoid/errors/drop_collection_failure.rb +27 -0
  68. data/lib/mongoid/errors/immutable_attribute.rb +26 -0
  69. data/lib/mongoid/errors/invalid_async_query_executor.rb +25 -0
  70. data/lib/mongoid/errors/invalid_global_executor_concurrency.rb +22 -0
  71. data/lib/mongoid/errors/invalid_storage_parent.rb +2 -0
  72. data/lib/mongoid/errors.rb +4 -2
  73. data/lib/mongoid/extensions/hash.rb +3 -51
  74. data/lib/mongoid/extensions/object.rb +2 -2
  75. data/lib/mongoid/extensions/time.rb +2 -0
  76. data/lib/mongoid/fields/localized.rb +10 -0
  77. data/lib/mongoid/fields/standard.rb +10 -0
  78. data/lib/mongoid/fields.rb +59 -35
  79. data/lib/mongoid/findable.rb +27 -3
  80. data/lib/mongoid/interceptable.rb +6 -116
  81. data/lib/mongoid/matchable.rb +1 -6
  82. data/lib/mongoid/matcher/eq_impl.rb +1 -1
  83. data/lib/mongoid/matcher/eq_impl_with_regexp.rb +5 -3
  84. data/lib/mongoid/matcher/regex.rb +9 -8
  85. data/lib/mongoid/matcher/type.rb +1 -1
  86. data/lib/mongoid/matcher.rb +0 -1
  87. data/lib/mongoid/persistable/creatable.rb +1 -0
  88. data/lib/mongoid/persistable/deletable.rb +1 -1
  89. data/lib/mongoid/persistable/savable.rb +13 -1
  90. data/lib/mongoid/persistable/unsettable.rb +2 -2
  91. data/lib/mongoid/persistable/updatable.rb +51 -1
  92. data/lib/mongoid/persistable/upsertable.rb +20 -1
  93. data/lib/mongoid/persistable.rb +3 -0
  94. data/lib/mongoid/query_cache.rb +5 -1
  95. data/lib/mongoid/railties/database.rake +7 -2
  96. data/lib/mongoid/reloadable.rb +5 -3
  97. data/lib/mongoid/stateful.rb +22 -1
  98. data/lib/mongoid/tasks/database.rake +12 -0
  99. data/lib/mongoid/tasks/database.rb +20 -0
  100. data/lib/mongoid/threaded.rb +0 -3
  101. data/lib/mongoid/timestamps/created.rb +1 -8
  102. data/lib/mongoid/traversable.rb +0 -12
  103. data/lib/mongoid/utils.rb +22 -0
  104. data/lib/mongoid/validatable/associated.rb +17 -98
  105. data/lib/mongoid/validatable/macros.rb +5 -20
  106. data/lib/mongoid/validatable.rb +4 -10
  107. data/lib/mongoid/version.rb +1 -5
  108. data/lib/mongoid/warnings.rb +17 -2
  109. data/lib/mongoid.rb +16 -3
  110. data/spec/integration/app_spec.rb +2 -20
  111. data/spec/integration/associations/embeds_one_spec.rb +5 -25
  112. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +0 -55
  113. data/spec/integration/callbacks_spec.rb +99 -12
  114. data/spec/integration/discriminator_key_spec.rb +4 -5
  115. data/spec/integration/dots_and_dollars_spec.rb +2 -12
  116. data/spec/integration/i18n_fallbacks_spec.rb +3 -2
  117. data/spec/integration/matcher_operator_data/regex.yml +0 -21
  118. data/spec/mongoid/association/eager_spec.rb +2 -24
  119. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +27 -0
  120. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +20 -25
  121. data/spec/mongoid/association/embedded/embeds_many_models.rb +1 -0
  122. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +0 -4
  123. data/spec/mongoid/association/embedded/embeds_one/proxy_spec.rb +15 -2
  124. data/spec/mongoid/association/referenced/belongs_to_spec.rb +2 -18
  125. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +42 -103
  126. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +9 -195
  127. data/spec/mongoid/association/syncable_spec.rb +1 -1
  128. data/spec/mongoid/attributes/nested_spec.rb +10 -204
  129. data/spec/mongoid/attributes_spec.rb +3 -33
  130. data/spec/mongoid/changeable_spec.rb +299 -24
  131. data/spec/mongoid/clients/factory_spec.rb +0 -30
  132. data/spec/mongoid/clients_spec.rb +122 -13
  133. data/spec/mongoid/collection_configurable_spec.rb +158 -0
  134. data/spec/mongoid/config/defaults_spec.rb +160 -0
  135. data/spec/mongoid/config_spec.rb +154 -27
  136. data/spec/mongoid/contextual/aggregable/memory_spec.rb +0 -91
  137. data/spec/mongoid/contextual/memory_spec.rb +332 -253
  138. data/spec/mongoid/contextual/mongo/documents_loader_spec.rb +187 -0
  139. data/spec/mongoid/contextual/mongo_spec.rb +1009 -125
  140. data/spec/mongoid/contextual/none_spec.rb +49 -2
  141. data/spec/mongoid/copyable_spec.rb +2 -10
  142. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +4 -10
  143. data/spec/mongoid/criteria/queryable/options_spec.rb +1 -1
  144. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +419 -2
  145. data/spec/mongoid/criteria/queryable/selectable_spec.rb +1 -1
  146. data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +0 -235
  147. data/spec/mongoid/criteria/queryable/selector_spec.rb +3 -76
  148. data/spec/mongoid/criteria/queryable/storable_spec.rb +0 -72
  149. data/spec/mongoid/criteria_projection_spec.rb +1 -4
  150. data/spec/mongoid/criteria_spec.rb +5 -11
  151. data/spec/mongoid/document_spec.rb +0 -27
  152. data/spec/mongoid/errors/readonly_document_spec.rb +2 -2
  153. data/spec/mongoid/extensions/hash_spec.rb +3 -239
  154. data/spec/mongoid/extensions/time_spec.rb +8 -43
  155. data/spec/mongoid/extensions/time_with_zone_spec.rb +7 -52
  156. data/spec/mongoid/fields/localized_spec.rb +46 -28
  157. data/spec/mongoid/fields_spec.rb +136 -77
  158. data/spec/mongoid/findable_spec.rb +391 -34
  159. data/spec/mongoid/indexable_spec.rb +16 -10
  160. data/spec/mongoid/interceptable_spec.rb +153 -442
  161. data/spec/mongoid/interceptable_spec_models.rb +111 -51
  162. data/spec/mongoid/persistable/deletable_spec.rb +26 -6
  163. data/spec/mongoid/persistable/destroyable_spec.rb +26 -6
  164. data/spec/mongoid/persistable/incrementable_spec.rb +37 -0
  165. data/spec/mongoid/persistable/logical_spec.rb +37 -0
  166. data/spec/mongoid/persistable/poppable_spec.rb +36 -0
  167. data/spec/mongoid/persistable/pullable_spec.rb +72 -0
  168. data/spec/mongoid/persistable/pushable_spec.rb +72 -0
  169. data/spec/mongoid/persistable/renamable_spec.rb +36 -0
  170. data/spec/mongoid/persistable/savable_spec.rb +96 -0
  171. data/spec/mongoid/persistable/settable_spec.rb +37 -0
  172. data/spec/mongoid/persistable/unsettable_spec.rb +36 -0
  173. data/spec/mongoid/persistable/updatable_spec.rb +20 -28
  174. data/spec/mongoid/persistable/upsertable_spec.rb +80 -6
  175. data/spec/mongoid/persistence_context_spec.rb +7 -57
  176. data/spec/mongoid/query_cache_spec.rb +56 -61
  177. data/spec/mongoid/reloadable_spec.rb +24 -28
  178. data/spec/mongoid/scopable_spec.rb +70 -0
  179. data/spec/mongoid/serializable_spec.rb +23 -44
  180. data/spec/mongoid/stateful_spec.rb +122 -8
  181. data/spec/mongoid/tasks/database_rake_spec.rb +74 -0
  182. data/spec/mongoid/tasks/database_spec.rb +127 -0
  183. data/spec/mongoid/timestamps/created_spec.rb +0 -23
  184. data/spec/mongoid/timestamps_spec.rb +9 -11
  185. data/spec/mongoid/touchable_spec.rb +277 -5
  186. data/spec/mongoid/touchable_spec_models.rb +3 -1
  187. data/spec/mongoid/traversable_spec.rb +9 -24
  188. data/spec/mongoid/validatable/associated_spec.rb +34 -27
  189. data/spec/mongoid/validatable/numericality_spec.rb +0 -16
  190. data/spec/mongoid/validatable/uniqueness_spec.rb +2 -3
  191. data/spec/mongoid_spec.rb +36 -10
  192. data/spec/shared/lib/mrss/docker_runner.rb +1 -1
  193. data/spec/shared/lib/mrss/server_version_registry.rb +1 -1
  194. data/spec/shared/lib/mrss/spec_organizer.rb +3 -32
  195. data/spec/shared/share/Dockerfile.erb +85 -15
  196. data/spec/shared/shlib/distro.sh +0 -10
  197. data/spec/shared/shlib/server.sh +27 -34
  198. data/spec/shared/shlib/set_env.sh +68 -9
  199. data/spec/spec_helper.rb +5 -0
  200. data/spec/support/expectations.rb +17 -20
  201. data/spec/support/immutable_ids.rb +118 -0
  202. data/spec/support/macros.rb +47 -15
  203. data/spec/support/models/artist.rb +0 -1
  204. data/spec/support/models/band.rb +1 -0
  205. data/spec/support/models/building.rb +2 -0
  206. data/spec/support/models/name.rb +0 -10
  207. data/spec/support/models/person.rb +0 -1
  208. data/spec/support/models/product.rb +1 -0
  209. data.tar.gz.sig +0 -0
  210. metadata +715 -671
  211. metadata.gz.sig +2 -0
  212. data/lib/mongoid/errors/in_memory_regexp_timeout.rb +0 -26
  213. data/lib/mongoid/field_readable.rb +0 -70
  214. data/lib/mongoid/matcher/regexp_budget.rb +0 -389
  215. data/lib/mongoid/validatable/numericality.rb +0 -19
  216. data/spec/integration/matcher_regexp_timeout_spec.rb +0 -215
  217. data/spec/integration/query_operator_guard_spec.rb +0 -89
  218. data/spec/mongoid/criteria/queryable/extensions/bignum_spec.rb +0 -61
  219. data/spec/mongoid/criteria/queryable/extensions/fixnum_spec.rb +0 -61
  220. data/spec/mongoid/matcher/regexp_budget_spec.rb +0 -570
  221. data/spec/shared/CANDIDATE.md +0 -28
  222. data/spec/shared/lib/mrss/release/candidate.rb +0 -281
  223. data/spec/shared/lib/mrss/release/product_data.rb +0 -144
  224. data/spec/shared/lib/tasks/candidate.rake +0 -64
  225. data/spec/support/models/purse.rb +0 -9
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "mongoid/contextual/aggregable/memory"
4
4
  require "mongoid/association/eager_loadable"
5
- require "mongoid/field_readable"
6
5
 
7
6
  module Mongoid
8
7
  module Contextual
@@ -12,7 +11,6 @@ module Mongoid
12
11
  include Association::EagerLoadable
13
12
  include Queryable
14
13
  include Positional
15
- include FieldReadable
16
14
 
17
15
  # @attribute [r] root The root document.
18
16
  # @attribute [r] path The atomic path.
@@ -112,9 +110,24 @@ module Mongoid
112
110
  # @example Do any documents exist for the context.
113
111
  # context.exists?
114
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
+ #
115
122
  # @return [ true | false ] If the count is more than zero.
116
- def exists?
117
- 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
118
131
  end
119
132
 
120
133
  # Get the first document in the database for the criteria's selector.
@@ -135,6 +148,20 @@ module Mongoid
135
148
  alias :one :first
136
149
  alias :find_first :first
137
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
+
138
165
  # Create the new in memory context.
139
166
  #
140
167
  # @example Create the new context.
@@ -143,20 +170,10 @@ module Mongoid
143
170
  # @param [ Criteria ] criteria The criteria.
144
171
  def initialize(criteria)
145
172
  @criteria, @klass = criteria, criteria.klass
146
-
147
- # Resolving the collection can build a Mongo::Client the first time it
148
- # runs, so it happens before the budget opens. Where the budget is
149
- # enforced with Timeout the exception is asynchronous, and landing in
150
- # the middle of that would leave a half-built client behind.
151
- if (first = criteria.documents.first)
152
- @root = first._root
153
- @collection = @root.collection
154
- end
155
-
156
- # One regexp budget covers the whole scan, so that the limit bounds the
157
- # query rather than each document individually.
158
- @documents = Matcher::RegexpBudget.open(criteria.selector) do
159
- criteria.documents.select { |doc| doc._matches?(criteria.selector) }
173
+ @documents = criteria.documents.select do |doc|
174
+ @root ||= doc._root
175
+ @collection ||= root.collection
176
+ doc._matches?(criteria.selector)
160
177
  end
161
178
  apply_sorting
162
179
  apply_options
@@ -192,37 +209,18 @@ module Mongoid
192
209
  end
193
210
  end
194
211
 
195
- # Take the given number of documents from the database.
196
- #
197
- # @example Take a document.
198
- # context.take
199
- #
200
- # @param [ Integer | nil ] limit The number of documents to take or nil.
201
- #
202
- # @return [ Document ] The document.
203
- def take(limit = nil)
204
- if limit
205
- eager_load(documents.take(limit))
206
- else
207
- eager_load([documents.first]).first
208
- end
209
- end
210
-
211
- # Take the given number of documents from the database.
212
+ # Get the last document in the database for the criteria's selector or
213
+ # raise an error if none is found.
212
214
  #
213
- # @example Take a document.
214
- # context.take
215
+ # @example Get the last document.
216
+ # context.last!
215
217
  #
216
- # @return [ Document ] The document.
218
+ # @return [ Document ] The last document.
217
219
  #
218
220
  # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
219
221
  # documents to take.
220
- def take!
221
- if documents.empty?
222
- raise Errors::DocumentNotFound.new(klass, nil, nil)
223
- else
224
- eager_load([documents.first]).first
225
- end
222
+ def last!
223
+ last || raise_document_not_found_error
226
224
  end
227
225
 
228
226
  # Get the length of matching documents in the context.
@@ -254,7 +252,7 @@ module Mongoid
254
252
  # @example Get the values in memory.
255
253
  # context.pluck(:name)
256
254
  #
257
- # @param [ String | Symbol ] *fields Field(s) to pluck.
255
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pluck.
258
256
  #
259
257
  # @return [ Array<Object> | Array<Array<Object>> ] The plucked values.
260
258
  def pluck(*fields)
@@ -272,9 +270,9 @@ module Mongoid
272
270
  # @example Get the values in memory.
273
271
  # context.pick(:name)
274
272
  #
275
- # @param [ String | Symbol ] *fields Field(s) to pick.
273
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pick.
276
274
  #
277
- # @return [ Object, Array<Object> ] The picked values.
275
+ # @return [ Object | Array<Object> ] The picked values.
278
276
  def pick(*fields)
279
277
  if doc = documents.first
280
278
  pluck_from_doc(doc, *fields)
@@ -297,6 +295,36 @@ module Mongoid
297
295
  end
298
296
  end
299
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
+
300
328
  # Skips the provided number of documents.
301
329
  #
302
330
  # @example Skip the documents.
@@ -347,6 +375,162 @@ module Mongoid
347
375
  update_documents(attributes, entries)
348
376
  end
349
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
+
350
534
  private
351
535
 
352
536
  # Get the documents the context should iterate. This follows 3 rules:
@@ -546,10 +730,11 @@ module Mongoid
546
730
  # _translations hash so that we can get the specified translation in
547
731
  # the remaining
548
732
  if field&.localized?
549
- document.public_send("#{segment}_translations")
733
+ document.send("#{segment}_translations")
550
734
  end
551
735
  end
552
- res.nil? ? read_field_value(document, segment) : res
736
+ meth = klass.aliased_associations[segment] || segment
737
+ res.nil? ? document.try(meth) : res
553
738
  elsif document.is_a?(Hash)
554
739
  # TODO: Remove the indifferent access when implementing MONGOID-5410.
555
740
  document.key?(segment.to_s) ?
@@ -572,9 +757,9 @@ module Mongoid
572
757
  # Pluck the field values from the given document.
573
758
  #
574
759
  # @param [ Document ] doc The document to pluck from.
575
- # @param [ String | Symbol ] *fields Field(s) to pluck.
760
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pluck.
576
761
  #
577
- # @return [ Object, Array<Object> ] The plucked values.
762
+ # @return [ Object | Array<Object> ] The plucked values.
578
763
  def pluck_from_doc(doc, *fields)
579
764
  if fields.length == 1
580
765
  retrieve_value_at_path(doc, fields.first)
@@ -584,6 +769,10 @@ module Mongoid
584
769
  end
585
770
  end
586
771
  end
772
+
773
+ def raise_document_not_found_error
774
+ raise Errors::DocumentNotFound.new(klass, nil, nil)
775
+ end
587
776
  end
588
777
  end
589
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