mongoid 7.5.1 → 8.1.2

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