mongoid 7.5.4 → 8.1.7

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 (442) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -3
  3. data/README.md +6 -6
  4. data/Rakefile +44 -46
  5. data/lib/config/locales/en.yml +92 -43
  6. data/lib/mongoid/association/accessors.rb +44 -11
  7. data/lib/mongoid/association/bindable.rb +50 -2
  8. data/lib/mongoid/association/builders.rb +5 -3
  9. data/lib/mongoid/association/constrainable.rb +0 -1
  10. data/lib/mongoid/association/eager_loadable.rb +29 -7
  11. data/lib/mongoid/association/embedded/batchable.rb +34 -11
  12. data/lib/mongoid/association/embedded/cyclic.rb +1 -1
  13. data/lib/mongoid/association/embedded/embedded_in/binding.rb +24 -2
  14. data/lib/mongoid/association/embedded/embedded_in/buildable.rb +2 -2
  15. data/lib/mongoid/association/embedded/embedded_in/proxy.rb +4 -3
  16. data/lib/mongoid/association/embedded/embedded_in.rb +3 -2
  17. data/lib/mongoid/association/embedded/embeds_many/binding.rb +1 -0
  18. data/lib/mongoid/association/embedded/embeds_many/buildable.rb +4 -3
  19. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +85 -46
  20. data/lib/mongoid/association/embedded/embeds_many.rb +2 -2
  21. data/lib/mongoid/association/embedded/embeds_one/buildable.rb +19 -5
  22. data/lib/mongoid/association/embedded/embeds_one/proxy.rb +24 -5
  23. data/lib/mongoid/association/embedded/embeds_one.rb +3 -3
  24. data/lib/mongoid/association/macros.rb +8 -1
  25. data/lib/mongoid/association/many.rb +11 -7
  26. data/lib/mongoid/association/nested/many.rb +5 -4
  27. data/lib/mongoid/association/nested/nested_buildable.rb +4 -4
  28. data/lib/mongoid/association/nested/one.rb +45 -7
  29. data/lib/mongoid/association/one.rb +2 -2
  30. data/lib/mongoid/association/options.rb +9 -9
  31. data/lib/mongoid/association/proxy.rb +15 -4
  32. data/lib/mongoid/association/referenced/auto_save.rb +4 -3
  33. data/lib/mongoid/association/referenced/belongs_to/binding.rb +1 -0
  34. data/lib/mongoid/association/referenced/belongs_to/buildable.rb +1 -1
  35. data/lib/mongoid/association/referenced/belongs_to/proxy.rb +5 -6
  36. data/lib/mongoid/association/referenced/belongs_to.rb +2 -2
  37. data/lib/mongoid/association/referenced/counter_cache.rb +10 -10
  38. data/lib/mongoid/association/referenced/eager.rb +2 -2
  39. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +70 -13
  40. data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +6 -3
  41. data/lib/mongoid/association/referenced/has_many/enumerable.rb +22 -30
  42. data/lib/mongoid/association/referenced/has_many/proxy.rb +40 -21
  43. data/lib/mongoid/association/referenced/has_many.rb +3 -3
  44. data/lib/mongoid/association/referenced/has_one/buildable.rb +1 -1
  45. data/lib/mongoid/association/referenced/has_one/nested_builder.rb +5 -5
  46. data/lib/mongoid/association/referenced/has_one/proxy.rb +9 -12
  47. data/lib/mongoid/association/referenced/has_one.rb +3 -3
  48. data/lib/mongoid/association/referenced/syncable.rb +4 -4
  49. data/lib/mongoid/association/reflections.rb +4 -4
  50. data/lib/mongoid/association/relatable.rb +44 -10
  51. data/lib/mongoid/association.rb +5 -5
  52. data/lib/mongoid/atomic/modifiers.rb +2 -2
  53. data/lib/mongoid/atomic.rb +16 -7
  54. data/lib/mongoid/attributes/dynamic.rb +4 -4
  55. data/lib/mongoid/attributes/nested.rb +6 -6
  56. data/lib/mongoid/attributes/processing.rb +37 -6
  57. data/lib/mongoid/attributes/projector.rb +2 -2
  58. data/lib/mongoid/attributes/readonly.rb +3 -3
  59. data/lib/mongoid/attributes.rb +51 -42
  60. data/lib/mongoid/changeable.rb +147 -14
  61. data/lib/mongoid/clients/options.rb +5 -1
  62. data/lib/mongoid/clients/sessions.rb +2 -14
  63. data/lib/mongoid/clients/storage_options.rb +2 -5
  64. data/lib/mongoid/clients/validators/storage.rb +3 -15
  65. data/lib/mongoid/collection_configurable.rb +58 -0
  66. data/lib/mongoid/composable.rb +2 -0
  67. data/lib/mongoid/config/defaults.rb +60 -0
  68. data/lib/mongoid/config/options.rb +3 -0
  69. data/lib/mongoid/config/validators/async_query_executor.rb +24 -0
  70. data/lib/mongoid/config/validators/client.rb +6 -6
  71. data/lib/mongoid/config/validators.rb +1 -0
  72. data/lib/mongoid/config.rb +153 -18
  73. data/lib/mongoid/contextual/aggregable/memory.rb +24 -16
  74. data/lib/mongoid/contextual/aggregable/mongo.rb +5 -5
  75. data/lib/mongoid/contextual/aggregable/none.rb +1 -1
  76. data/lib/mongoid/contextual/atomic.rb +1 -1
  77. data/lib/mongoid/contextual/geo_near.rb +7 -7
  78. data/lib/mongoid/contextual/map_reduce.rb +2 -2
  79. data/lib/mongoid/contextual/memory.rb +285 -58
  80. data/lib/mongoid/contextual/mongo/documents_loader.rb +177 -0
  81. data/lib/mongoid/contextual/mongo.rb +540 -346
  82. data/lib/mongoid/contextual/none.rb +193 -20
  83. data/lib/mongoid/contextual/queryable.rb +1 -1
  84. data/lib/mongoid/contextual.rb +14 -2
  85. data/lib/mongoid/copyable.rb +32 -8
  86. data/lib/mongoid/criteria/findable.rb +8 -5
  87. data/lib/mongoid/criteria/includable.rb +27 -22
  88. data/lib/mongoid/criteria/marshalable.rb +10 -2
  89. data/lib/mongoid/criteria/permission.rb +1 -1
  90. data/lib/mongoid/criteria/queryable/aggregable.rb +2 -2
  91. data/lib/mongoid/criteria/queryable/extensions/array.rb +3 -16
  92. data/lib/mongoid/criteria/queryable/extensions/big_decimal.rb +25 -4
  93. data/lib/mongoid/criteria/queryable/extensions/boolean.rb +2 -2
  94. data/lib/mongoid/criteria/queryable/extensions/date.rb +6 -1
  95. data/lib/mongoid/criteria/queryable/extensions/date_time.rb +6 -1
  96. data/lib/mongoid/criteria/queryable/extensions/hash.rb +1 -17
  97. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +15 -9
  98. data/lib/mongoid/criteria/queryable/extensions/object.rb +2 -1
  99. data/lib/mongoid/criteria/queryable/extensions/range.rb +13 -5
  100. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +3 -3
  101. data/lib/mongoid/criteria/queryable/extensions/set.rb +1 -1
  102. data/lib/mongoid/criteria/queryable/extensions/string.rb +4 -14
  103. data/lib/mongoid/criteria/queryable/extensions/symbol.rb +4 -12
  104. data/lib/mongoid/criteria/queryable/extensions/time.rb +6 -1
  105. data/lib/mongoid/criteria/queryable/extensions/time_with_zone.rb +6 -1
  106. data/lib/mongoid/criteria/queryable/key.rb +4 -4
  107. data/lib/mongoid/criteria/queryable/mergeable.rb +1 -1
  108. data/lib/mongoid/criteria/queryable/optional.rb +11 -17
  109. data/lib/mongoid/criteria/queryable/options.rb +2 -2
  110. data/lib/mongoid/criteria/queryable/pipeline.rb +1 -1
  111. data/lib/mongoid/criteria/queryable/selectable.rb +47 -38
  112. data/lib/mongoid/criteria/queryable/selector.rb +92 -7
  113. data/lib/mongoid/criteria/queryable/smash.rb +40 -7
  114. data/lib/mongoid/criteria/queryable.rb +12 -7
  115. data/lib/mongoid/criteria/scopable.rb +2 -2
  116. data/lib/mongoid/criteria/translator.rb +45 -0
  117. data/lib/mongoid/criteria.rb +20 -40
  118. data/lib/mongoid/deprecable.rb +37 -0
  119. data/lib/mongoid/deprecation.rb +25 -0
  120. data/lib/mongoid/document.rb +127 -35
  121. data/lib/mongoid/equality.rb +8 -8
  122. data/lib/mongoid/errors/create_collection_failure.rb +33 -0
  123. data/lib/mongoid/errors/document_not_found.rb +10 -6
  124. data/lib/mongoid/errors/drop_collection_failure.rb +27 -0
  125. data/lib/mongoid/errors/immutable_attribute.rb +26 -0
  126. data/lib/mongoid/errors/invalid_async_query_executor.rb +25 -0
  127. data/lib/mongoid/errors/invalid_config_option.rb +1 -1
  128. data/lib/mongoid/errors/invalid_dependent_strategy.rb +1 -1
  129. data/lib/mongoid/errors/invalid_dot_dollar_assignment.rb +23 -0
  130. data/lib/mongoid/errors/invalid_field.rb +6 -2
  131. data/lib/mongoid/errors/invalid_field_type.rb +26 -0
  132. data/lib/mongoid/errors/invalid_global_executor_concurrency.rb +22 -0
  133. data/lib/mongoid/errors/invalid_relation.rb +1 -1
  134. data/lib/mongoid/errors/invalid_relation_option.rb +1 -1
  135. data/lib/mongoid/errors/invalid_session_use.rb +1 -1
  136. data/lib/mongoid/errors/invalid_storage_options.rb +1 -1
  137. data/lib/mongoid/errors/invalid_storage_parent.rb +2 -0
  138. data/lib/mongoid/errors/mongoid_error.rb +3 -3
  139. data/lib/mongoid/errors/nested_attributes_metadata_not_found.rb +1 -1
  140. data/lib/mongoid/errors/no_client_database.rb +1 -1
  141. data/lib/mongoid/errors/no_client_hosts.rb +1 -1
  142. data/lib/mongoid/errors/readonly_attribute.rb +1 -1
  143. data/lib/mongoid/errors/too_many_nested_attribute_records.rb +1 -1
  144. data/lib/mongoid/errors/unknown_attribute.rb +1 -1
  145. data/lib/mongoid/errors.rb +6 -3
  146. data/lib/mongoid/extensions/array.rb +9 -7
  147. data/lib/mongoid/extensions/big_decimal.rb +33 -10
  148. data/lib/mongoid/extensions/binary.rb +42 -0
  149. data/lib/mongoid/extensions/boolean.rb +8 -2
  150. data/lib/mongoid/extensions/date.rb +26 -20
  151. data/lib/mongoid/extensions/date_time.rb +1 -1
  152. data/lib/mongoid/extensions/false_class.rb +1 -1
  153. data/lib/mongoid/extensions/float.rb +7 -4
  154. data/lib/mongoid/extensions/hash.rb +38 -9
  155. data/lib/mongoid/extensions/integer.rb +7 -4
  156. data/lib/mongoid/extensions/module.rb +1 -1
  157. data/lib/mongoid/extensions/object.rb +10 -8
  158. data/lib/mongoid/extensions/range.rb +41 -10
  159. data/lib/mongoid/extensions/regexp.rb +11 -4
  160. data/lib/mongoid/extensions/set.rb +11 -4
  161. data/lib/mongoid/extensions/string.rb +11 -22
  162. data/lib/mongoid/extensions/symbol.rb +4 -15
  163. data/lib/mongoid/extensions/time.rb +29 -16
  164. data/lib/mongoid/extensions/time_with_zone.rb +1 -2
  165. data/lib/mongoid/extensions/true_class.rb +1 -1
  166. data/lib/mongoid/extensions.rb +1 -0
  167. data/lib/mongoid/factory.rb +55 -7
  168. data/lib/mongoid/fields/foreign_key.rb +11 -4
  169. data/lib/mongoid/fields/localized.rb +19 -4
  170. data/lib/mongoid/fields/standard.rb +17 -7
  171. data/lib/mongoid/fields/validators/macro.rb +3 -9
  172. data/lib/mongoid/fields.rb +142 -28
  173. data/lib/mongoid/findable.rb +54 -24
  174. data/lib/mongoid/indexable/specification.rb +2 -2
  175. data/lib/mongoid/indexable/validators/options.rb +6 -2
  176. data/lib/mongoid/interceptable.rb +186 -16
  177. data/lib/mongoid/matchable.rb +1 -1
  178. data/lib/mongoid/matcher/eq_impl.rb +1 -1
  179. data/lib/mongoid/matcher/type.rb +1 -1
  180. data/lib/mongoid/matcher.rb +48 -14
  181. data/lib/mongoid/persistable/creatable.rb +19 -9
  182. data/lib/mongoid/persistable/deletable.rb +2 -2
  183. data/lib/mongoid/persistable/destroyable.rb +1 -1
  184. data/lib/mongoid/persistable/savable.rb +14 -2
  185. data/lib/mongoid/persistable/unsettable.rb +2 -2
  186. data/lib/mongoid/persistable/updatable.rb +69 -12
  187. data/lib/mongoid/persistable/upsertable.rb +21 -2
  188. data/lib/mongoid/persistable.rb +6 -3
  189. data/lib/mongoid/persistence_context.rb +6 -4
  190. data/lib/mongoid/query_cache.rb +13 -261
  191. data/lib/mongoid/railties/controller_runtime.rb +1 -1
  192. data/lib/mongoid/railties/database.rake +7 -2
  193. data/lib/mongoid/reloadable.rb +10 -8
  194. data/lib/mongoid/scopable.rb +15 -13
  195. data/lib/mongoid/selectable.rb +1 -2
  196. data/lib/mongoid/serializable.rb +17 -13
  197. data/lib/mongoid/stateful.rb +57 -10
  198. data/lib/mongoid/tasks/database.rake +12 -0
  199. data/lib/mongoid/tasks/database.rb +20 -2
  200. data/lib/mongoid/threaded/lifecycle.rb +5 -5
  201. data/lib/mongoid/threaded.rb +42 -12
  202. data/lib/mongoid/timestamps/created.rb +1 -1
  203. data/lib/mongoid/timestamps/updated.rb +2 -2
  204. data/lib/mongoid/touchable.rb +3 -4
  205. data/lib/mongoid/traversable.rb +10 -5
  206. data/lib/mongoid/utils.rb +22 -0
  207. data/lib/mongoid/validatable/associated.rb +98 -17
  208. data/lib/mongoid/validatable/localizable.rb +1 -1
  209. data/lib/mongoid/validatable/macros.rb +5 -7
  210. data/lib/mongoid/validatable/presence.rb +2 -2
  211. data/lib/mongoid/validatable/uniqueness.rb +9 -8
  212. data/lib/mongoid/validatable.rb +17 -6
  213. data/lib/mongoid/version.rb +1 -1
  214. data/lib/mongoid/warnings.rb +19 -4
  215. data/lib/mongoid.rb +17 -3
  216. data/spec/config/mongoid.yml +16 -0
  217. data/spec/integration/app_spec.rb +24 -19
  218. data/spec/integration/associations/belongs_to_spec.rb +18 -0
  219. data/spec/integration/associations/embedded_spec.rb +15 -0
  220. data/spec/integration/associations/embeds_many_spec.rb +15 -2
  221. data/spec/integration/associations/embeds_one_spec.rb +18 -0
  222. data/spec/integration/associations/foreign_key_spec.rb +9 -0
  223. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +61 -0
  224. data/spec/integration/associations/has_one_spec.rb +97 -1
  225. data/spec/integration/associations/scope_option_spec.rb +1 -1
  226. data/spec/integration/callbacks_models.rb +132 -1
  227. data/spec/integration/callbacks_spec.rb +381 -4
  228. data/spec/integration/criteria/range_spec.rb +95 -1
  229. data/spec/integration/discriminator_key_spec.rb +118 -80
  230. data/spec/integration/dots_and_dollars_spec.rb +277 -0
  231. data/spec/integration/i18n_fallbacks_spec.rb +3 -32
  232. data/spec/integration/matcher_examples_spec.rb +20 -13
  233. data/spec/integration/matcher_operator_data/type_decimal.yml +3 -2
  234. data/spec/integration/matcher_operator_spec.rb +3 -5
  235. data/spec/integration/persistence/range_field_spec.rb +350 -0
  236. data/spec/mongoid/association/counter_cache_spec.rb +1 -1
  237. data/spec/mongoid/association/depending_spec.rb +9 -9
  238. data/spec/mongoid/association/eager_spec.rb +2 -1
  239. data/spec/mongoid/association/embedded/embedded_in/binding_spec.rb +2 -1
  240. data/spec/mongoid/association/embedded/embedded_in/buildable_spec.rb +54 -0
  241. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +96 -9
  242. data/spec/mongoid/association/embedded/embeds_many/buildable_spec.rb +112 -0
  243. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +290 -65
  244. data/spec/mongoid/association/embedded/embeds_many_models.rb +37 -0
  245. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +16 -0
  246. data/spec/mongoid/association/embedded/embeds_many_spec.rb +68 -0
  247. data/spec/mongoid/association/embedded/embeds_one/buildable_spec.rb +25 -0
  248. data/spec/mongoid/association/embedded/embeds_one/proxy_spec.rb +15 -2
  249. data/spec/mongoid/association/embedded/embeds_one_models.rb +19 -0
  250. data/spec/mongoid/association/embedded/embeds_one_spec.rb +28 -0
  251. data/spec/mongoid/association/referenced/belongs_to/binding_spec.rb +2 -1
  252. data/spec/mongoid/association/referenced/belongs_to/buildable_spec.rb +54 -0
  253. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +19 -0
  254. data/spec/mongoid/association/referenced/belongs_to_models.rb +11 -0
  255. data/spec/mongoid/association/referenced/belongs_to_spec.rb +4 -20
  256. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +186 -229
  257. data/spec/mongoid/association/referenced/has_and_belongs_to_many_models.rb +25 -0
  258. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +35 -2
  259. data/spec/mongoid/association/referenced/has_many/buildable_spec.rb +109 -0
  260. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +2 -56
  261. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +215 -177
  262. data/spec/mongoid/association/referenced/has_many_models.rb +3 -1
  263. data/spec/mongoid/association/referenced/has_many_spec.rb +25 -0
  264. data/spec/mongoid/association/referenced/has_one/buildable_spec.rb +2 -2
  265. data/spec/mongoid/association/referenced/has_one/proxy_spec.rb +107 -1
  266. data/spec/mongoid/association/referenced/has_one_models.rb +16 -0
  267. data/spec/mongoid/association/syncable_spec.rb +15 -1
  268. data/spec/mongoid/atomic/paths_spec.rb +0 -14
  269. data/spec/mongoid/attributes/nested_spec.rb +80 -11
  270. data/spec/mongoid/attributes/nested_spec_models.rb +48 -0
  271. data/spec/mongoid/attributes/projector_spec.rb +1 -5
  272. data/spec/mongoid/attributes_spec.rb +526 -33
  273. data/spec/mongoid/changeable_spec.rb +429 -37
  274. data/spec/mongoid/clients/factory_spec.rb +23 -30
  275. data/spec/mongoid/clients/sessions_spec.rb +0 -38
  276. data/spec/mongoid/clients_spec.rb +149 -15
  277. data/spec/mongoid/collection_configurable_spec.rb +158 -0
  278. data/spec/mongoid/config/defaults_spec.rb +160 -0
  279. data/spec/mongoid/config_spec.rb +214 -31
  280. data/spec/mongoid/contextual/aggregable/memory_spec.rb +396 -158
  281. data/spec/mongoid/contextual/aggregable/memory_table.yml +88 -0
  282. data/spec/mongoid/contextual/aggregable/memory_table_spec.rb +62 -0
  283. data/spec/mongoid/contextual/map_reduce_spec.rb +2 -16
  284. data/spec/mongoid/contextual/memory_spec.rb +850 -88
  285. data/spec/mongoid/contextual/mongo/documents_loader_spec.rb +187 -0
  286. data/spec/mongoid/contextual/mongo_spec.rb +2256 -1005
  287. data/spec/mongoid/contextual/none_spec.rb +60 -21
  288. data/spec/mongoid/copyable_spec.rb +453 -11
  289. data/spec/mongoid/criteria/findable_spec.rb +86 -210
  290. data/spec/mongoid/criteria/includable_spec.rb +1492 -0
  291. data/spec/mongoid/criteria/includable_spec_models.rb +54 -0
  292. data/spec/mongoid/criteria/marshalable_spec.rb +18 -1
  293. data/spec/mongoid/criteria/queryable/extensions/array_spec.rb +7 -19
  294. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +134 -26
  295. data/spec/mongoid/criteria/queryable/extensions/date_spec.rb +11 -0
  296. data/spec/mongoid/criteria/queryable/extensions/date_time_spec.rb +11 -0
  297. data/spec/mongoid/criteria/queryable/extensions/hash_spec.rb +0 -15
  298. data/spec/mongoid/criteria/queryable/extensions/numeric_spec.rb +73 -7
  299. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +4 -69
  300. data/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb +0 -59
  301. data/spec/mongoid/criteria/queryable/extensions/time_spec.rb +11 -0
  302. data/spec/mongoid/criteria/queryable/extensions/time_with_zone_spec.rb +11 -0
  303. data/spec/mongoid/criteria/queryable/optional_spec.rb +15 -484
  304. data/spec/mongoid/criteria/queryable/options_spec.rb +1 -1
  305. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +469 -0
  306. data/spec/mongoid/criteria/queryable/selectable_spec.rb +78 -86
  307. data/spec/mongoid/criteria/queryable/selector_spec.rb +15 -3
  308. data/spec/mongoid/criteria/translator_spec.rb +132 -0
  309. data/spec/mongoid/criteria_projection_spec.rb +1 -5
  310. data/spec/mongoid/criteria_spec.rb +469 -1205
  311. data/spec/mongoid/document_fields_spec.rb +173 -24
  312. data/spec/mongoid/document_spec.rb +32 -41
  313. data/spec/mongoid/errors/document_not_found_spec.rb +29 -2
  314. data/spec/mongoid/errors/invalid_field_spec.rb +1 -1
  315. data/spec/mongoid/errors/invalid_field_type_spec.rb +55 -0
  316. data/spec/mongoid/errors/mongoid_error_spec.rb +3 -1
  317. data/spec/mongoid/errors/no_environment_spec.rb +3 -3
  318. data/spec/mongoid/errors/readonly_document_spec.rb +2 -2
  319. data/spec/mongoid/errors/too_many_nested_attribute_records_spec.rb +1 -1
  320. data/spec/mongoid/extensions/array_spec.rb +16 -2
  321. data/spec/mongoid/extensions/big_decimal_spec.rb +712 -212
  322. data/spec/mongoid/extensions/binary_spec.rb +44 -9
  323. data/spec/mongoid/extensions/boolean_spec.rb +68 -82
  324. data/spec/mongoid/extensions/date_class_mongoize_spec.rb +7 -3
  325. data/spec/mongoid/extensions/date_spec.rb +71 -1
  326. data/spec/mongoid/extensions/date_time_spec.rb +15 -9
  327. data/spec/mongoid/extensions/float_spec.rb +53 -74
  328. data/spec/mongoid/extensions/hash_spec.rb +33 -3
  329. data/spec/mongoid/extensions/integer_spec.rb +50 -64
  330. data/spec/mongoid/extensions/range_spec.rb +255 -54
  331. data/spec/mongoid/extensions/regexp_spec.rb +58 -33
  332. data/spec/mongoid/extensions/set_spec.rb +106 -0
  333. data/spec/mongoid/extensions/string_spec.rb +53 -25
  334. data/spec/mongoid/extensions/symbol_spec.rb +18 -25
  335. data/spec/mongoid/extensions/time_spec.rb +639 -106
  336. data/spec/mongoid/extensions/time_with_zone_spec.rb +24 -83
  337. data/spec/mongoid/factory_spec.rb +61 -1
  338. data/spec/mongoid/fields/localized_spec.rb +80 -37
  339. data/spec/mongoid/fields_spec.rb +503 -87
  340. data/spec/mongoid/findable_spec.rb +450 -58
  341. data/spec/mongoid/indexable/specification_spec.rb +2 -2
  342. data/spec/mongoid/indexable_spec.rb +55 -30
  343. data/spec/mongoid/interceptable_spec.rb +824 -22
  344. data/spec/mongoid/interceptable_spec_models.rb +235 -4
  345. data/spec/mongoid/matcher/extract_attribute_spec.rb +1 -5
  346. data/spec/mongoid/mongoizable_spec.rb +285 -0
  347. data/spec/mongoid/persistable/creatable_spec.rb +2 -2
  348. data/spec/mongoid/persistable/deletable_spec.rb +28 -8
  349. data/spec/mongoid/persistable/destroyable_spec.rb +28 -8
  350. data/spec/mongoid/persistable/incrementable_spec.rb +37 -0
  351. data/spec/mongoid/persistable/logical_spec.rb +37 -0
  352. data/spec/mongoid/persistable/poppable_spec.rb +36 -0
  353. data/spec/mongoid/persistable/pullable_spec.rb +72 -0
  354. data/spec/mongoid/persistable/pushable_spec.rb +72 -0
  355. data/spec/mongoid/persistable/renamable_spec.rb +36 -0
  356. data/spec/mongoid/persistable/savable_spec.rb +96 -0
  357. data/spec/mongoid/persistable/settable_spec.rb +37 -0
  358. data/spec/mongoid/persistable/unsettable_spec.rb +36 -0
  359. data/spec/mongoid/persistable/updatable_spec.rb +20 -28
  360. data/spec/mongoid/persistable/upsertable_spec.rb +89 -1
  361. data/spec/mongoid/persistence_context_spec.rb +31 -57
  362. data/spec/mongoid/query_cache_middleware_spec.rb +0 -18
  363. data/spec/mongoid/query_cache_spec.rb +56 -215
  364. data/spec/mongoid/reloadable_spec.rb +83 -6
  365. data/spec/mongoid/scopable_spec.rb +91 -1
  366. data/spec/mongoid/serializable_spec.rb +25 -39
  367. data/spec/mongoid/shardable_spec.rb +4 -4
  368. data/spec/mongoid/stateful_spec.rb +150 -8
  369. data/spec/mongoid/tasks/database_rake_spec.rb +74 -0
  370. data/spec/mongoid/tasks/database_spec.rb +127 -0
  371. data/spec/mongoid/timestamps_spec.rb +392 -4
  372. data/spec/mongoid/timestamps_spec_models.rb +67 -0
  373. data/spec/mongoid/touchable_spec.rb +390 -2
  374. data/spec/mongoid/touchable_spec_models.rb +14 -8
  375. data/spec/mongoid/traversable_spec.rb +13 -35
  376. data/spec/mongoid/validatable/associated_spec.rb +27 -34
  377. data/spec/mongoid/validatable/presence_spec.rb +1 -1
  378. data/spec/mongoid/validatable/uniqueness_spec.rb +58 -31
  379. data/spec/mongoid/warnings_spec.rb +35 -0
  380. data/spec/mongoid_spec.rb +34 -16
  381. data/spec/rails/controller_extension/controller_runtime_spec.rb +2 -2
  382. data/spec/rails/mongoid_spec.rb +4 -16
  383. data/spec/spec_helper.rb +5 -0
  384. data/spec/support/constraints.rb +24 -0
  385. data/spec/support/immutable_ids.rb +118 -0
  386. data/spec/support/macros.rb +78 -0
  387. data/spec/support/models/artist.rb +0 -1
  388. data/spec/support/models/augmentation.rb +12 -0
  389. data/spec/support/models/band.rb +5 -0
  390. data/spec/support/models/book.rb +1 -0
  391. data/spec/support/models/building.rb +2 -0
  392. data/spec/support/models/catalog.rb +24 -0
  393. data/spec/support/models/circus.rb +3 -0
  394. data/spec/support/models/cover.rb +10 -0
  395. data/spec/support/models/fanatic.rb +8 -0
  396. data/spec/support/models/implant.rb +9 -0
  397. data/spec/support/models/label.rb +2 -0
  398. data/spec/support/models/lat_lng.rb +6 -0
  399. data/spec/support/models/name.rb +10 -0
  400. data/spec/support/models/passport.rb +9 -0
  401. data/spec/support/models/person.rb +2 -0
  402. data/spec/support/models/player.rb +2 -0
  403. data/spec/support/models/powerup.rb +12 -0
  404. data/spec/support/models/product.rb +1 -0
  405. data/spec/support/models/purse.rb +9 -0
  406. data/spec/support/models/registry.rb +1 -0
  407. data/spec/support/models/school.rb +14 -0
  408. data/spec/support/models/shield.rb +18 -0
  409. data/spec/support/models/student.rb +14 -0
  410. data/spec/support/models/weapon.rb +12 -0
  411. metadata +101 -96
  412. checksums.yaml.gz.sig +0 -0
  413. data/lib/mongoid/errors/eager_load.rb +0 -23
  414. data/lib/mongoid/errors/invalid_value.rb +0 -17
  415. data/spec/mongoid/criteria/queryable/extensions/bignum_spec.rb +0 -60
  416. data/spec/mongoid/criteria/queryable/extensions/fixnum_spec.rb +0 -60
  417. data/spec/mongoid/errors/eager_load_spec.rb +0 -31
  418. data/spec/shared/LICENSE +0 -20
  419. data/spec/shared/bin/get-mongodb-download-url +0 -17
  420. data/spec/shared/bin/s3-copy +0 -45
  421. data/spec/shared/bin/s3-upload +0 -69
  422. data/spec/shared/lib/mrss/child_process_helper.rb +0 -80
  423. data/spec/shared/lib/mrss/cluster_config.rb +0 -231
  424. data/spec/shared/lib/mrss/constraints.rb +0 -378
  425. data/spec/shared/lib/mrss/docker_runner.rb +0 -291
  426. data/spec/shared/lib/mrss/eg_config_utils.rb +0 -51
  427. data/spec/shared/lib/mrss/event_subscriber.rb +0 -210
  428. data/spec/shared/lib/mrss/lite_constraints.rb +0 -238
  429. data/spec/shared/lib/mrss/server_version_registry.rb +0 -120
  430. data/spec/shared/lib/mrss/session_registry.rb +0 -69
  431. data/spec/shared/lib/mrss/session_registry_legacy.rb +0 -60
  432. data/spec/shared/lib/mrss/spec_organizer.rb +0 -179
  433. data/spec/shared/lib/mrss/utils.rb +0 -15
  434. data/spec/shared/share/Dockerfile.erb +0 -325
  435. data/spec/shared/share/haproxy-1.conf +0 -16
  436. data/spec/shared/share/haproxy-2.conf +0 -17
  437. data/spec/shared/shlib/config.sh +0 -27
  438. data/spec/shared/shlib/distro.sh +0 -74
  439. data/spec/shared/shlib/server.sh +0 -392
  440. data/spec/shared/shlib/set_env.sh +0 -169
  441. data.tar.gz.sig +0 -0
  442. metadata.gz.sig +0 -3
@@ -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"
@@ -17,6 +18,8 @@ module Mongoid
17
18
  include Association::EagerLoadable
18
19
  include Queryable
19
20
 
21
+ Mongoid.deprecate(self, :geo_near)
22
+
20
23
  # Options constant.
21
24
  OPTIONS = [ :hint,
22
25
  :limit,
@@ -35,16 +38,17 @@ module Mongoid
35
38
  # @attribute [r] view The Mongo collection view.
36
39
  attr_reader :view
37
40
 
38
- # Is the context cached?
41
+ # Run an explain on the criteria.
42
+ #
43
+ # @example Explain the criteria.
44
+ # Band.where(name: "Depeche Mode").explain
39
45
  #
40
- # @example Is the context cached?
41
- # context.cached?
46
+ # @param [ Hash ] options customizable options (See Mongo::Collection::View::Explainable)
42
47
  #
43
- # @return [ true, false ] If the context is cached.
44
- def cached?
45
- Mongoid::Warnings.warn_criteria_cache_deprecated
46
- !!@cache
47
- end
48
+ # @return [ Hash ] The explain result.
49
+ def_delegator :view, :explain
50
+
51
+ attr_reader :documents_loader
48
52
 
49
53
  # Get the number of documents matching the query.
50
54
  #
@@ -65,7 +69,12 @@ module Mongoid
65
69
  # @return [ Integer ] The number of matches.
66
70
  def count(options = {}, &block)
67
71
  return super(&block) if block_given?
68
- try_cache(:count) { view.count_documents(options) }
72
+
73
+ if valid_for_count_documents?
74
+ view.count_documents(options)
75
+ else
76
+ view.count(options)
77
+ end
69
78
  end
70
79
 
71
80
  # Get the estimated number of documents matching the query.
@@ -84,7 +93,7 @@ module Mongoid
84
93
  unless self.criteria.selector.empty?
85
94
  raise Mongoid::Errors::InvalidEstimatedCountCriteria.new(self.klass)
86
95
  end
87
- try_cache(:estimated_count) { view.estimated_document_count(options) }
96
+ view.estimated_document_count(options)
88
97
  end
89
98
 
90
99
  # Delete all documents in the database that match the selector.
@@ -118,7 +127,7 @@ module Mongoid
118
127
  # @example Get the distinct values.
119
128
  # context.distinct(:name)
120
129
  #
121
- # @param [ String, Symbol ] field The name of the field.
130
+ # @param [ String | Symbol ] field The name of the field.
122
131
  #
123
132
  # @return [ Array<Object> ] The distinct values for the field.
124
133
  def distinct(field)
@@ -152,7 +161,6 @@ module Mongoid
152
161
  documents_for_iteration.each do |doc|
153
162
  yield_document(doc, &block)
154
163
  end
155
- @cache_loaded = true
156
164
  self
157
165
  else
158
166
  to_enum
@@ -164,28 +172,28 @@ module Mongoid
164
172
  # @example Do any documents exist for the context.
165
173
  # context.exists?
166
174
  #
167
- # @note We don't use count here since Mongo does not use counted
168
- # b-tree indexes, unless a count is already cached then that is
169
- # used to determine the value.
175
+ # @example Do any documents exist for given _id.
176
+ # context.exists?(BSON::ObjectId(...))
170
177
  #
171
- # @return [ true, false ] If the count is more than zero.
172
- def exists?
173
- return !documents.empty? if cached? && cache_loaded?
174
- return @count > 0 if instance_variable_defined?(:@count)
175
-
176
- try_cache(:exists) do
177
- !!(view.projection(_id: 1).limit(1).first)
178
- end
179
- end
180
-
181
- # Run an explain on the criteria.
178
+ # @example Do any documents exist for given conditions.
179
+ # context.exists?(name: "...")
182
180
  #
183
- # @example Explain the criteria.
184
- # Band.where(name: "Depeche Mode").explain
185
- #
186
- # @return [ Hash ] The explain result.
187
- def explain
188
- view.explain
181
+ # @note We don't use count here since Mongo does not use counted
182
+ # b-tree indexes.
183
+ #
184
+ # @param [ Hash | Object | false ] id_or_conditions an _id to
185
+ # search for, a hash of conditions, nil or false.
186
+ #
187
+ # @return [ true | false ] If the count is more than zero.
188
+ # Always false if passed nil or false.
189
+ def exists?(id_or_conditions = :none)
190
+ return false if self.view.limit == 0
191
+ case id_or_conditions
192
+ when :none then !!(view.projection(_id: 1).limit(1).first)
193
+ when nil, false then false
194
+ when Hash then Mongo.new(criteria.where(id_or_conditions)).exists?
195
+ else Mongo.new(criteria.where(_id: id_or_conditions)).exists?
196
+ end
189
197
  end
190
198
 
191
199
  # Execute the find and modify command, used for MongoDB's
@@ -197,9 +205,9 @@ module Mongoid
197
205
  # @param [ Hash ] update The updates.
198
206
  # @param [ Hash ] options The command options.
199
207
  #
200
- # @option options [ :before, :after ] :return_document Return the updated document
208
+ # @option options [ :before | :after ] :return_document Return the updated document
201
209
  # from before or after update.
202
- # @option options [ true, false ] :upsert Create the document if it doesn't exist.
210
+ # @option options [ true | false ] :upsert Create the document if it doesn't exist.
203
211
  #
204
212
  # @return [ Document ] The result of the command.
205
213
  def find_one_and_update(update, options = {})
@@ -217,9 +225,9 @@ module Mongoid
217
225
  # @param [ Hash ] replacement The replacement.
218
226
  # @param [ Hash ] options The command options.
219
227
  #
220
- # @option options [ :before, :after ] :return_document Return the updated document
228
+ # @option options [ :before | :after ] :return_document Return the updated document
221
229
  # from before or after update.
222
- # @option options [ true, false ] :upsert Create the document if it doesn't exist.
230
+ # @option options [ true | false ] :upsert Create the document if it doesn't exist.
223
231
  #
224
232
  # @return [ Document ] The result of the command.
225
233
  def find_one_and_replace(replacement, options = {})
@@ -241,49 +249,10 @@ module Mongoid
241
249
  end
242
250
  end
243
251
 
244
- # Get the first document in the database for the criteria's selector.
245
- #
246
- # @example Get the first document.
247
- # context.first
248
- #
249
- # @note Automatically adding a sort on _id when no other sort is
250
- # defined on the criteria has the potential to cause bad performance issues.
251
- # If you experience unexpected poor performance when using #first or #last
252
- # and have no sort defined on the criteria, use the option { id_sort: :none }.
253
- # Be aware that #first/#last won't guarantee order in this case.
254
- #
255
- # @param [ Integer | Hash ] limit_or_opts The number of documents to
256
- # return, or a hash of options.
257
- #
258
- # @option limit_or_opts [ :none ] :id_sort This option is deprecated.
259
- # Don't apply a sort on _id if no other sort is defined on the criteria.
260
- #
261
- # @return [ Document ] The first document.
262
- def first(limit_or_opts = nil)
263
- limit, opts = extract_limit_and_opts(limit_or_opts)
264
- if cached? && cache_loaded?
265
- return limit ? documents.first(limit) : documents.first
266
- end
267
- try_numbered_cache(:first, limit) do
268
- if opts.key?(:id_sort)
269
- Mongoid::Warnings.warn_id_sort_deprecated
270
- end
271
- sorted_view = view
272
- if sort = view.sort || ({ _id: 1 } unless opts[:id_sort] == :none)
273
- sorted_view = view.sort(sort)
274
- end
275
- if raw_docs = sorted_view.limit(limit || 1).to_a
276
- process_raw_docs(raw_docs, limit)
277
- end
278
- end
279
- end
280
- alias :one :first
281
-
282
252
  # Return the first result without applying sort
283
253
  #
284
254
  # @api private
285
255
  def find_first
286
- return documents.first if cached? && cache_loaded?
287
256
  if raw_doc = view.first
288
257
  doc = Factory.from_db(klass, raw_doc, criteria)
289
258
  eager_load([doc]).first
@@ -313,33 +282,6 @@ module Mongoid
313
282
  GeoNear.new(collection, criteria, coordinates)
314
283
  end
315
284
 
316
- # Invoke the block for each element of Contextual. Create a new array
317
- # containing the values returned by the block.
318
- #
319
- # If the symbol field name is passed instead of the block, additional
320
- # optimizations would be used.
321
- #
322
- # @example Map by some field.
323
- # context.map(:field1)
324
- #
325
- # @example Map with block.
326
- # context.map(&:field1)
327
- #
328
- # @param [ Symbol ] field The field name.
329
- #
330
- # @return [ Array ] The result of mapping.
331
- def map(field = nil, &block)
332
- if !field.nil?
333
- Mongoid::Warnings.warn_map_field_deprecated
334
- end
335
-
336
- if block_given?
337
- super(&block)
338
- else
339
- criteria.pluck(field)
340
- end
341
- end
342
-
343
285
  # Create the new Mongo context. This delegates operations to the
344
286
  # underlying driver.
345
287
  #
@@ -348,7 +290,7 @@ module Mongoid
348
290
  #
349
291
  # @param [ Criteria ] criteria The criteria.
350
292
  def initialize(criteria)
351
- @criteria, @klass, @cache = criteria, criteria.klass, criteria.options[:cache]
293
+ @criteria, @klass = criteria, criteria.klass
352
294
  @collection = @klass.collection
353
295
  criteria.send(:merge_type_selection)
354
296
  @view = collection.find(criteria.selector, session: _session)
@@ -357,47 +299,15 @@ module Mongoid
357
299
 
358
300
  def_delegator :@klass, :database_field_name
359
301
 
360
- # Get the last document in the database for the criteria's selector.
361
- #
362
- # @example Get the last document.
363
- # context.last
364
- #
365
- # @note Automatically adding a sort on _id when no other sort is
366
- # defined on the criteria has the potential to cause bad performance issues.
367
- # If you experience unexpected poor performance when using #first or #last
368
- # and have no sort defined on the criteria, use the option { id_sort: :none }.
369
- # Be aware that #first/#last won't guarantee order in this case.
370
- #
371
- # @param [ Integer | Hash ] limit_or_opts The number of documents to
372
- # return, or a hash of options.
373
- #
374
- # @option limit_or_opts [ :none ] :id_sort This option is deprecated.
375
- # Don't apply a sort on _id if no other sort is defined on the criteria.
376
- #
377
- # @return [ Document ] The last document.
378
- def last(limit_or_opts = nil)
379
- limit, opts = extract_limit_and_opts(limit_or_opts)
380
- if cached? && cache_loaded?
381
- return limit ? documents.last(limit) : documents.last
382
- end
383
- res = try_numbered_cache(:last, limit) do
384
- with_inverse_sorting(opts) do
385
- if raw_docs = view.limit(limit || 1).to_a
386
- process_raw_docs(raw_docs, limit)
387
- end
388
- end
389
- end
390
- res.is_a?(Array) ? res.reverse : res
391
- end
392
-
393
- # Get's the number of documents matching the query selector.
302
+ # Returns the number of documents in the database matching
303
+ # the query selector.
394
304
  #
395
305
  # @example Get the length.
396
306
  # context.length
397
307
  #
398
308
  # @return [ Integer ] The number of documents.
399
309
  def length
400
- @length ||= self.count
310
+ self.count
401
311
  end
402
312
  alias :size :length
403
313
 
@@ -413,6 +323,76 @@ module Mongoid
413
323
  @view = view.limit(value) and self
414
324
  end
415
325
 
326
+ # Initiate a map/reduce operation from the context.
327
+ #
328
+ # @example Initiate a map/reduce.
329
+ # context.map_reduce(map, reduce)
330
+ #
331
+ # @param [ String ] map The map js function.
332
+ # @param [ String ] reduce The reduce js function.
333
+ #
334
+ # @return [ MapReduce ] The map/reduce lazy wrapper.
335
+ def map_reduce(map, reduce)
336
+ MapReduce.new(collection, criteria, map, reduce)
337
+ end
338
+
339
+ # Pluck the field value(s) from the database. Returns one
340
+ # result for each document found in the database for
341
+ # the context. The results are normalized according to their
342
+ # Mongoid field types. Note that the results may include
343
+ # duplicates and nil values.
344
+ #
345
+ # @example Pluck a field.
346
+ # context.pluck(:_id)
347
+ #
348
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pluck,
349
+ # which may include nested fields using dot-notation.
350
+ #
351
+ # @return [ Array<Object> | Array<Array<Object>> ] The plucked values.
352
+ # If the *fields arg contains a single value, each result
353
+ # in the array will be a single value. Otherwise, each
354
+ # result in the array will be an array of values.
355
+ def pluck(*fields)
356
+ # Multiple fields can map to the same field name. For example, plucking
357
+ # a field and its _translations field map to the same field in the database.
358
+ # because of this, we need to keep track of the fields requested.
359
+ normalized_field_names = []
360
+ normalized_select = fields.inject({}) do |hash, f|
361
+ db_fn = klass.database_field_name(f)
362
+ normalized_field_names.push(db_fn)
363
+
364
+ if Mongoid.legacy_pluck_distinct
365
+ hash[db_fn] = true
366
+ else
367
+ hash[klass.cleanse_localized_field_names(f)] = true
368
+ end
369
+ hash
370
+ end
371
+
372
+ view.projection(normalized_select).reduce([]) do |plucked, doc|
373
+ values = normalized_field_names.map do |n|
374
+ if Mongoid.legacy_pluck_distinct
375
+ n.include?('.') ? doc[n.partition('.')[0]] : doc[n]
376
+ else
377
+ extract_value(doc, n)
378
+ end
379
+ end
380
+ plucked << (values.size == 1 ? values.first : values)
381
+ end
382
+ end
383
+
384
+ # Pick the single field values from the database.
385
+ #
386
+ # @example Pick a field.
387
+ # context.pick(:_id)
388
+ #
389
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pick.
390
+ #
391
+ # @return [ Object | Array<Object> ] The picked values.
392
+ def pick(*fields)
393
+ limit(1).pluck(*fields).first
394
+ end
395
+
416
396
  # Take the given number of documents from the database.
417
397
  #
418
398
  # @example Take 10 documents
@@ -451,57 +431,72 @@ module Mongoid
451
431
  end
452
432
  end
453
433
 
454
- # Initiate a map/reduce operation from the context.
434
+ # Get a hash of counts for the values of a single field. For example,
435
+ # if the following documents were in the database:
455
436
  #
456
- # @example Initiate a map/reduce.
457
- # context.map_reduce(map, reduce)
437
+ # { _id: 1, age: 21 }
438
+ # { _id: 2, age: 21 }
439
+ # { _id: 3, age: 22 }
458
440
  #
459
- # @param [ String ] map The map js function.
460
- # @param [ String ] reduce The reduce js function.
441
+ # Model.tally("age")
461
442
  #
462
- # @return [ MapReduce ] The map/reduce lazy wrapper.
463
- def map_reduce(map, reduce)
464
- MapReduce.new(collection, criteria, map, reduce)
465
- end
466
-
467
- # Pluck the single field values from the database. Will return duplicates
468
- # if they exist and only works for top level fields.
443
+ # would yield the following result:
469
444
  #
470
- # @example Pluck a field.
471
- # context.pluck(:_id)
445
+ # { 21 => 2, 22 => 1 }
472
446
  #
473
- # @note This method will return the raw db values - it performs no custom
474
- # serialization.
447
+ # When tallying a field inside an array or embeds_many association:
475
448
  #
476
- # @param [ String, Symbol, Array ] fields Fields to pluck.
449
+ # { _id: 1, array: [ { x: 1 }, { x: 2 } ] }
450
+ # { _id: 2, array: [ { x: 1 }, { x: 2 } ] }
451
+ # { _id: 3, array: [ { x: 1 }, { x: 3 } ] }
477
452
  #
478
- # @return [ Array<Object, Array> ] The plucked values.
479
- def pluck(*fields)
480
- # Multiple fields can map to the same field name. For example, plucking
481
- # a field and its _translations field map to the same field in the database.
482
- # because of this, we need to keep track of the fields requested.
483
- normalized_field_names = []
484
- normalized_select = fields.inject({}) do |hash, f|
485
- db_fn = klass.database_field_name(f)
486
- normalized_field_names.push(db_fn)
453
+ # Model.tally("array.x")
454
+ #
455
+ # The keys of the resulting hash are arrays:
456
+ #
457
+ # { [ 1, 2 ] => 2, [ 1, 3 ] => 1 }
458
+ #
459
+ # Note that if tallying an element in an array of hashes, and the key
460
+ # doesn't exist in some of the hashes, tally will not include those
461
+ # nil keys in the resulting hash:
462
+ #
463
+ # { _id: 1, array: [ { x: 1 }, { x: 2 }, { y: 3 } ] }
464
+ #
465
+ # Model.tally("array.x")
466
+ # # => { [ 1, 2 ] => 1 }
467
+ #
468
+ # @param [ String | Symbol ] field The field name.
469
+ #
470
+ # @return [ Hash ] The hash of counts.
471
+ def tally(field)
472
+ name = klass.cleanse_localized_field_names(field)
487
473
 
488
- if Mongoid.legacy_pluck_distinct
489
- hash[db_fn] = true
490
- else
491
- hash[klass.cleanse_localized_field_names(f)] = true
492
- end
493
- hash
494
- end
474
+ fld = klass.traverse_association_tree(name)
475
+ pipeline = [ { "$group" => { _id: "$#{name}", counts: { "$sum": 1 } } } ]
476
+ pipeline.unshift("$match" => view.filter) unless view.filter.blank?
495
477
 
496
- view.projection(normalized_select).reduce([]) do |plucked, doc|
497
- values = normalized_field_names.map do |n|
498
- if Mongoid.legacy_pluck_distinct
499
- n.include?('.') ? doc[n.partition('.')[0]] : doc[n]
500
- else
501
- extract_value(doc, n)
478
+ collection.aggregate(pipeline).reduce({}) do |tallies, doc|
479
+ is_translation = "#{name}_translations" == field.to_s
480
+ val = doc["_id"]
481
+
482
+ key = if val.is_a?(Array)
483
+ val.map do |v|
484
+ demongoize_with_field(fld, v, is_translation)
502
485
  end
486
+ else
487
+ demongoize_with_field(fld, val, is_translation)
503
488
  end
504
- plucked << (values.size == 1 ? values.first : values)
489
+
490
+ # The only time where a key will already exist in the tallies hash
491
+ # is when the values are stored differently in the database, but
492
+ # demongoize to the same value. A good example of when this happens
493
+ # is when using localized fields. While the server query won't group
494
+ # together hashes that have other values in different languages, the
495
+ # demongoized value is just the translation in the current locale,
496
+ # which can be the same across multiple of those unequal hashes.
497
+ tallies[key] ||= 0
498
+ tallies[key] += doc["counts"]
499
+ tallies
505
500
  end
506
501
  end
507
502
 
@@ -548,7 +543,7 @@ module Mongoid
548
543
  # @option opts [ Array ] :array_filters A set of filters specifying to which array elements
549
544
  # an update should apply.
550
545
  #
551
- # @return [ nil, false ] False if no attributes were provided.
546
+ # @return [ nil | false ] False if no attributes were provided.
552
547
  def update(attributes = nil, opts = {})
553
548
  update_documents(attributes, :update_one, opts)
554
549
  end
@@ -564,71 +559,257 @@ module Mongoid
564
559
  # @option opts [ Array ] :array_filters A set of filters specifying to which array elements
565
560
  # an update should apply.
566
561
  #
567
- # @return [ nil, false ] False if no attributes were provided.
562
+ # @return [ nil | false ] False if no attributes were provided.
568
563
  def update_all(attributes = nil, opts = {})
569
564
  update_documents(attributes, :update_many, opts)
570
565
  end
571
566
 
572
- private
573
-
574
- # yield the block given or return the cached value
567
+ # Get the first document in the database for the criteria's selector.
575
568
  #
576
- # @param [ String, Symbol ] key The instance variable name
569
+ # @example Get the first document.
570
+ # context.first
577
571
  #
578
- # @return the result of the block
579
- def try_cache(key, &block)
580
- unless cached?
581
- yield
572
+ # @note Automatically adding a sort on _id when no other sort is
573
+ # defined on the criteria has the potential to cause bad performance issues.
574
+ # If you experience unexpected poor performance when using #first or #last
575
+ # and have no sort defined on the criteria, use #take instead.
576
+ # Be aware that #take won't guarantee order.
577
+ #
578
+ # @param [ Integer ] limit The number of documents to return.
579
+ #
580
+ # @return [ Document | nil ] The first document or nil if none is found.
581
+ def first(limit = nil)
582
+ if limit.nil?
583
+ retrieve_nth(0)
582
584
  else
583
- unless ret = instance_variable_get("@#{key}")
584
- instance_variable_set("@#{key}", ret = yield)
585
- end
586
- ret
585
+ retrieve_nth_with_limit(0, limit)
587
586
  end
588
587
  end
588
+ alias :one :first
589
589
 
590
- # yield the block given or return the cached value
590
+ # Get the first document in the database for the criteria's selector or
591
+ # raise an error if none is found.
592
+ #
593
+ # @example Get the first document.
594
+ # context.first!
595
+ #
596
+ # @note Automatically adding a sort on _id when no other sort is
597
+ # defined on the criteria has the potential to cause bad performance issues.
598
+ # If you experience unexpected poor performance when using #first! or #last!
599
+ # and have no sort defined on the criteria, use #take! instead.
600
+ # Be aware that #take! won't guarantee order.
601
+ #
602
+ # @return [ Document ] The first document.
603
+ #
604
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
605
+ # documents available.
606
+ def first!
607
+ first || raise_document_not_found_error
608
+ end
609
+
610
+ # Get the last document in the database for the criteria's selector.
611
+ #
612
+ # @example Get the last document.
613
+ # context.last
614
+ #
615
+ # @note Automatically adding a sort on _id when no other sort is
616
+ # defined on the criteria has the potential to cause bad performance issues.
617
+ # If you experience unexpected poor performance when using #first or #last
618
+ # and have no sort defined on the criteria, use #take instead.
619
+ # Be aware that #take won't guarantee order.
591
620
  #
592
- # @param [ String, Symbol ] key The instance variable name
593
- # @param [ Integer | nil ] n The number of documents requested or nil
594
- # if none is requested.
621
+ # @param [ Integer ] limit The number of documents to return.
595
622
  #
596
- # @return [ Object ] The result of the block.
597
- def try_numbered_cache(key, n, &block)
598
- unless cached?
599
- yield if block_given?
623
+ # @return [ Document | nil ] The last document or nil if none is found.
624
+ def last(limit = nil)
625
+ if limit.nil?
626
+ retrieve_nth_to_last(0)
600
627
  else
601
- len = n || 1
602
- ret = instance_variable_get("@#{key}")
603
- if !ret || ret.length < len
604
- instance_variable_set("@#{key}", ret = Array.wrap(yield))
605
- elsif !n
606
- ret.is_a?(Array) ? ret.first : ret
607
- elsif ret.length > len
608
- ret.first(n)
609
- else
610
- ret
611
- end
628
+ retrieve_nth_to_last_with_limit(0, limit)
612
629
  end
613
630
  end
614
631
 
615
- # Extract the limit and opts from the given argument, so that code
616
- # can operate without having to worry about the current type and
617
- # state of the argument.
632
+ # Get the last document in the database for the criteria's selector or
633
+ # raise an error if none is found.
618
634
  #
619
- # @param [ nil | Integer | Hash ] limit_or_opts The value to pull the
620
- # limit and option hash from.
635
+ # @example Get the last document.
636
+ # context.last!
621
637
  #
622
- # @return [ Array<nil | Integer, Hash> ] A 2-array of the limit and the
623
- # option hash.
624
- def extract_limit_and_opts(limit_or_opts)
625
- case limit_or_opts
626
- when nil, Integer then [ limit_or_opts, {} ]
627
- when Hash then [ nil, limit_or_opts ]
628
- else raise ArgumentError, "expected nil, Integer, or Hash"
629
- end
638
+ # @note Automatically adding a sort on _id when no other sort is
639
+ # defined on the criteria has the potential to cause bad performance issues.
640
+ # If you experience unexpected poor performance when using #first! or #last!
641
+ # and have no sort defined on the criteria, use #take! instead.
642
+ # Be aware that #take! won't guarantee order.
643
+ #
644
+ # @return [ Document ] The last document.
645
+ #
646
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
647
+ # documents available.
648
+ def last!
649
+ last || raise_document_not_found_error
650
+ end
651
+
652
+ # Get the second document in the database for the criteria's selector.
653
+ #
654
+ # @example Get the second document.
655
+ # context.second
656
+ #
657
+ # @return [ Document | nil ] The second document or nil if none is found.
658
+ def second
659
+ retrieve_nth(1)
630
660
  end
631
661
 
662
+ # Get the second document in the database for the criteria's selector or
663
+ # raise an error if none is found.
664
+ #
665
+ # @example Get the second document.
666
+ # context.second!
667
+ #
668
+ # @return [ Document ] The second document.
669
+ #
670
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
671
+ # documents available.
672
+ def second!
673
+ second || raise_document_not_found_error
674
+ end
675
+
676
+ # Get the third document in the database for the criteria's selector.
677
+ #
678
+ # @example Get the third document.
679
+ # context.third
680
+ #
681
+ # @return [ Document | nil ] The third document or nil if none is found.
682
+ def third
683
+ retrieve_nth(2)
684
+ end
685
+
686
+ # Get the third document in the database for the criteria's selector or
687
+ # raise an error if none is found.
688
+ #
689
+ # @example Get the third document.
690
+ # context.third!
691
+ #
692
+ # @return [ Document ] The third document.
693
+ #
694
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
695
+ # documents available.
696
+ def third!
697
+ third || raise_document_not_found_error
698
+ end
699
+
700
+ # Get the fourth document in the database for the criteria's selector.
701
+ #
702
+ # @example Get the fourth document.
703
+ # context.fourth
704
+ #
705
+ # @return [ Document | nil ] The fourth document or nil if none is found.
706
+ def fourth
707
+ retrieve_nth(3)
708
+ end
709
+
710
+ # Get the fourth document in the database for the criteria's selector or
711
+ # raise an error if none is found.
712
+ #
713
+ # @example Get the fourth document.
714
+ # context.fourth!
715
+ #
716
+ # @return [ Document ] The fourth document.
717
+ #
718
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
719
+ # documents available.
720
+ def fourth!
721
+ fourth || raise_document_not_found_error
722
+ end
723
+
724
+ # Get the fifth document in the database for the criteria's selector.
725
+ #
726
+ # @example Get the fifth document.
727
+ # context.fifth
728
+ #
729
+ # @return [ Document | nil ] The fifth document or nil if none is found.
730
+ def fifth
731
+ retrieve_nth(4)
732
+ end
733
+
734
+ # Get the fifth document in the database for the criteria's selector or
735
+ # raise an error if none is found.
736
+ #
737
+ # @example Get the fifth document.
738
+ # context.fifth!
739
+ #
740
+ # @return [ Document ] The fifth document.
741
+ #
742
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
743
+ # documents available.
744
+ def fifth!
745
+ fifth || raise_document_not_found_error
746
+ end
747
+
748
+ # Get the second to last document in the database for the criteria's
749
+ # selector.
750
+ #
751
+ # @example Get the second to last document.
752
+ # context.second_to_last
753
+ #
754
+ # @return [ Document | nil ] The second to last document or nil if none
755
+ # is found.
756
+ def second_to_last
757
+ retrieve_nth_to_last(1)
758
+ end
759
+
760
+ # Get the second to last document in the database for the criteria's
761
+ # selector or raise an error if none is found.
762
+ #
763
+ # @example Get the second to last document.
764
+ # context.second_to_last!
765
+ #
766
+ # @return [ Document ] The second to last document.
767
+ #
768
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
769
+ # documents available.
770
+ def second_to_last!
771
+ second_to_last || raise_document_not_found_error
772
+ end
773
+
774
+ # Get the third to last document in the database for the criteria's
775
+ # selector.
776
+ #
777
+ # @example Get the third to last document.
778
+ # context.third_to_last
779
+ #
780
+ # @return [ Document | nil ] The third to last document or nil if none
781
+ # is found.
782
+ def third_to_last
783
+ retrieve_nth_to_last(2)
784
+ end
785
+
786
+ # Get the third to last document in the database for the criteria's
787
+ # selector or raise an error if none is found.
788
+ #
789
+ # @example Get the third to last document.
790
+ # context.third_to_last!
791
+ #
792
+ # @return [ Document ] The third to last document.
793
+ #
794
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
795
+ # documents available.
796
+ def third_to_last!
797
+ third_to_last || raise_document_not_found_error
798
+ end
799
+
800
+ # Schedule a task to load documents for the context.
801
+ #
802
+ # Depending on the Mongoid configuration, the scheduled task can be executed
803
+ # immediately on the caller's thread, or can be scheduled for an
804
+ # asynchronous execution.
805
+ #
806
+ # @api private
807
+ def load_async
808
+ @documents_loader ||= DocumentsLoader.new(view, klass, criteria)
809
+ end
810
+
811
+ private
812
+
632
813
  # Update the documents for the provided method.
633
814
  #
634
815
  # @api private
@@ -639,7 +820,7 @@ module Mongoid
639
820
  # @param [ Hash ] attributes The updates.
640
821
  # @param [ Symbol ] method The method to use.
641
822
  #
642
- # @return [ true, false ] If the update succeeded.
823
+ # @return [ true | false ] If the update succeeded.
643
824
  def update_documents(attributes, method = :update_one, opts = {})
644
825
  return false unless attributes
645
826
  attributes = Hash[attributes.map { |k, v| [klass.database_field_name(k.to_s), v] }]
@@ -689,78 +870,34 @@ module Mongoid
689
870
  # Map the inverse sort symbols to the correct MongoDB values.
690
871
  #
691
872
  # @api private
692
- #
693
- # @example Apply the inverse sorting params to the given block
694
- # context.with_inverse_sorting
695
- def with_inverse_sorting(opts = {})
696
- Mongoid::Warnings.warn_id_sort_deprecated if opts.key?(:id_sort)
697
-
698
- begin
699
- if sort = criteria.options[:sort] || ( { _id: 1 } unless opts[:id_sort] == :none )
700
- @view = view.sort(Hash[sort.map{|k, v| [k, -1*v]}])
701
- end
702
- yield
703
- ensure
704
- apply_option(:sort)
705
- end
706
- end
707
-
708
- # Is the cache able to be added to?
709
- #
710
- # @api private
711
- #
712
- # @example Is the context cacheable?
713
- # context.cacheable?
714
- #
715
- # @return [ true, false ] If caching, and the cache isn't loaded.
716
- def cacheable?
717
- cached? && !cache_loaded?
718
- end
719
-
720
- # Is the cache fully loaded? Will be true if caching after one full
721
- # iteration.
722
- #
723
- # @api private
724
- #
725
- # @example Is the cache loaded?
726
- # context.cache_loaded?
727
- #
728
- # @return [ true, false ] If the cache is loaded.
729
- def cache_loaded?
730
- !!@cache_loaded
873
+ def inverse_sorting
874
+ sort = view.sort || { _id: 1 }
875
+ Hash[sort.map{|k, v| [k, -1*v]}]
731
876
  end
732
877
 
733
- # Get the documents for cached queries.
878
+ # Get the documents the context should iterate.
734
879
  #
735
- # @api private
736
- #
737
- # @example Get the cached documents.
738
- # context.documents
739
- #
740
- # @return [ Array<Document> ] The documents.
741
- def documents
742
- @documents ||= []
743
- end
744
-
745
- # Get the documents the context should iterate. This follows 3 rules:
880
+ # If the documents have been already preloaded by `Document::Loader`
881
+ # instance, they will be used.
746
882
  #
747
- # 1. If the query is cached, and we already have documents loaded, use
748
- # them.
749
- # 2. If we are eager loading, then eager load the documents and use
750
- # those.
751
- # 3. Use the query.
883
+ # @return [ Array<Document> | Mongo::Collection::View ] The docs to iterate.
752
884
  #
753
885
  # @api private
754
- #
755
- # @example Get the documents for iteration.
756
- # context.documents_for_iteration
757
- #
758
- # @return [ Array<Document>, Mongo::Collection::View ] The docs to iterate.
759
886
  def documents_for_iteration
760
- return documents if cached? && !documents.empty?
761
- return view unless eager_loadable?
762
- docs = view.map{ |doc| Factory.from_db(klass, doc, criteria) }
763
- eager_load(docs)
887
+ if @documents_loader
888
+ if @documents_loader.started?
889
+ @documents_loader.value!
890
+ else
891
+ @documents_loader.unschedule
892
+ @documents_loader.execute
893
+ end
894
+ else
895
+ return view unless eager_loadable?
896
+ docs = view.map do |doc|
897
+ Factory.from_db(klass, doc, criteria)
898
+ end
899
+ eager_load(docs)
900
+ end
764
901
  end
765
902
 
766
903
  # Yield to the document.
@@ -777,11 +914,8 @@ module Mongoid
777
914
  doc = document.respond_to?(:_id) ?
778
915
  document : Factory.from_db(klass, document, criteria)
779
916
  yield(doc)
780
- documents.push(doc) if cacheable?
781
917
  end
782
918
 
783
- private
784
-
785
919
  def _session
786
920
  @criteria.send(:_session)
787
921
  end
@@ -790,6 +924,26 @@ module Mongoid
790
924
  collection.write_concern.nil? || collection.write_concern.acknowledged?
791
925
  end
792
926
 
927
+ # Fetch the element from the given hash and demongoize it using the
928
+ # given field. If the obj is an array, map over it and call this method
929
+ # on all of its elements.
930
+ #
931
+ # @param [ Hash | Array<Hash> ] obj The hash or array of hashes to fetch from.
932
+ # @param [ String ] meth The key to fetch from the hash.
933
+ # @param [ Field ] field The field to use for demongoization.
934
+ #
935
+ # @return [ Object ] The demongoized value.
936
+ #
937
+ # @api private
938
+ def fetch_and_demongoize(obj, meth, field)
939
+ if obj.is_a?(Array)
940
+ obj.map { |doc| fetch_and_demongoize(doc, meth, field) }
941
+ else
942
+ res = obj.try(:fetch, meth, nil)
943
+ field ? field.demongoize(res) : res.class.demongoize(res)
944
+ end
945
+ end
946
+
793
947
  # Extracts the value for the given field name from the given attribute
794
948
  # hash.
795
949
  #
@@ -798,24 +952,18 @@ module Mongoid
798
952
  #
799
953
  # @param [ Object ] The value for the given field name
800
954
  def extract_value(attrs, field_name)
801
- def fetch_and_demongoize(d, meth, klass)
802
- res = d.try(:fetch, meth, nil)
803
- if field = klass.fields[meth]
804
- field.demongoize(res)
805
- else
806
- res.class.demongoize(res)
807
- end
808
- end
955
+ i = 1
956
+ num_meths = field_name.count('.') + 1
957
+ curr = attrs.dup
809
958
 
810
- k = klass
811
- meths = field_name.split('.')
812
- meths.each_with_index.inject(attrs) do |curr, (meth, i)|
959
+ klass.traverse_association_tree(field_name) do |meth, obj, is_field|
960
+ field = obj if is_field
813
961
  is_translation = false
814
- if !k.fields.key?(meth) && !k.relations.key?(meth)
815
- if tr = meth.match(/(.*)_translations\z/)&.captures&.first
816
- is_translation = true
817
- meth = tr
818
- end
962
+ # If no association or field was found, check if the meth is an
963
+ # _translations field.
964
+ if obj.nil? & tr = meth.match(/(.*)_translations\z/)&.captures&.first
965
+ is_translation = true
966
+ meth = tr
819
967
  end
820
968
 
821
969
  # 1. If curr is an array fetch from all elements in the array.
@@ -828,31 +976,24 @@ module Mongoid
828
976
  # 3. If the meth is an _translations field, do not demongoize the
829
977
  # value so the full hash is returned.
830
978
  # 4. Otherwise, fetch and demongoize the value for the key meth.
831
- if curr.is_a? Array
832
- res = curr.map { |x| fetch_and_demongoize(x, meth, k) }
979
+ curr = if curr.is_a? Array
980
+ res = fetch_and_demongoize(curr, meth, field)
833
981
  res.empty? ? nil : res
834
- elsif !is_translation && k.fields[meth]&.localized?
835
- if i < meths.length-1
982
+ elsif !is_translation && field&.localized?
983
+ if i < num_meths
836
984
  curr.try(:fetch, meth, nil)
837
985
  else
838
- fetch_and_demongoize(curr, meth, k)
986
+ fetch_and_demongoize(curr, meth, field)
839
987
  end
840
988
  elsif is_translation
841
989
  curr.try(:fetch, meth, nil)
842
990
  else
843
- fetch_and_demongoize(curr, meth, k)
844
- end.tap do
845
- if as = k.try(:aliased_associations)
846
- if a = as.fetch(meth, nil)
847
- meth = a
848
- end
849
- end
850
-
851
- if relation = k.relations[meth]
852
- k = relation.klass
853
- end
991
+ fetch_and_demongoize(curr, meth, field)
854
992
  end
993
+
994
+ i += 1
855
995
  end
996
+ curr
856
997
  end
857
998
 
858
999
  # Recursively demongoize the given value. This method recursively traverses
@@ -860,34 +1001,41 @@ module Mongoid
860
1001
  #
861
1002
  # @param [ String ] field_name The name of the field to demongoize.
862
1003
  # @param [ Object ] value The value to demongoize.
863
- # @param [ Boolean ] is_translation The field we are retrieving is an
1004
+ # @param [ true | false ] is_translation The field we are retrieving is an
864
1005
  # _translations field.
865
1006
  #
866
1007
  # @return [ Object ] The demongoized value.
867
1008
  def recursive_demongoize(field_name, value, is_translation)
868
- k = klass
869
- field_name.split('.').each do |meth|
870
- if as = k.try(:aliased_associations)
871
- if a = as.fetch(meth, nil)
872
- meth = a.to_s
873
- end
874
- end
1009
+ field = klass.traverse_association_tree(field_name)
1010
+ demongoize_with_field(field, value, is_translation)
1011
+ end
875
1012
 
876
- if relation = k.relations[meth]
877
- k = relation.klass
878
- elsif field = k.fields[meth]
879
- # If it's a localized field that's not a hash, don't demongoize
880
- # again, we already have the translation. If it's an _translation
881
- # field, don't demongoize, we want the full hash not just a
882
- # specific translation.
883
- if field.localized? && (!value.is_a?(Hash) || is_translation)
884
- return value.class.demongoize(value)
885
- else
886
- return field.demongoize(value)
887
- end
1013
+ # Demongoize the value for the given field. If the field is nil or the
1014
+ # field is a translations field, the value is demongoized using its class.
1015
+ #
1016
+ # @param [ Field ] field The field to use to demongoize.
1017
+ # @param [ Object ] value The value to demongoize.
1018
+ # @param [ true | false ] is_translation The field we are retrieving is an
1019
+ # _translations field.
1020
+ #
1021
+ # @return [ Object ] The demongoized value.
1022
+ #
1023
+ # @api private
1024
+ def demongoize_with_field(field, value, is_translation)
1025
+ if field
1026
+ # If it's a localized field that's not a hash, don't demongoize
1027
+ # again, we already have the translation. If it's an _translations
1028
+ # field, don't demongoize, we want the full hash not just a
1029
+ # specific translation.
1030
+ # If it is a hash, and it's not a translations field, we need to
1031
+ # demongoize to get the correct translation.
1032
+ if field.localized? && (!value.is_a?(Hash) || is_translation)
1033
+ value.class.demongoize(value)
888
1034
  else
889
- return value.class.demongoize(value)
1035
+ field.demongoize(value)
890
1036
  end
1037
+ else
1038
+ value.class.demongoize(value)
891
1039
  end
892
1040
  end
893
1041
 
@@ -902,6 +1050,52 @@ module Mongoid
902
1050
  docs = eager_load(docs)
903
1051
  limit ? docs : docs.first
904
1052
  end
1053
+
1054
+ # Queries whether the current context is valid for use with
1055
+ # the #count_documents? predicate. A context is valid if it
1056
+ # does not include a `$where` operator.
1057
+ #
1058
+ # @return [ true | false ] whether or not the current context
1059
+ # excludes a `$where` operator.
1060
+ def valid_for_count_documents?(hash = view.filter)
1061
+ # Note that `view.filter` is a BSON::Document, and all keys in a
1062
+ # BSON::Document are strings; we don't need to worry about symbol
1063
+ # representations of `$where`.
1064
+ hash.keys.each do |key|
1065
+ return false if key == '$where'
1066
+ return false if hash[key].is_a?(Hash) && !valid_for_count_documents?(hash[key])
1067
+ end
1068
+
1069
+ true
1070
+ end
1071
+
1072
+ def raise_document_not_found_error
1073
+ raise Errors::DocumentNotFound.new(klass, nil, nil)
1074
+ end
1075
+
1076
+ def retrieve_nth(n)
1077
+ retrieve_nth_with_limit(n, 1).first
1078
+ end
1079
+
1080
+ def retrieve_nth_with_limit(n, limit)
1081
+ sort = view.sort || { _id: 1 }
1082
+ v = view.sort(sort).limit(limit || 1)
1083
+ v = v.skip(n) if n > 0
1084
+ if raw_docs = v.to_a
1085
+ process_raw_docs(raw_docs, limit)
1086
+ end
1087
+ end
1088
+
1089
+ def retrieve_nth_to_last(n)
1090
+ retrieve_nth_to_last_with_limit(n, 1).first
1091
+ end
1092
+
1093
+ def retrieve_nth_to_last_with_limit(n, limit)
1094
+ v = view.sort(inverse_sorting).skip(n).limit(limit || 1)
1095
+ v = v.skip(n) if n > 0
1096
+ raw_docs = v.to_a.reverse
1097
+ process_raw_docs(raw_docs, limit)
1098
+ end
905
1099
  end
906
1100
  end
907
1101
  end