mongoid 7.5.3 → 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 (417) 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 +34 -11
  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.rb +7 -0
  55. data/lib/mongoid/attributes/dynamic.rb +4 -4
  56. data/lib/mongoid/attributes/nested.rb +6 -6
  57. data/lib/mongoid/attributes/processing.rb +37 -6
  58. data/lib/mongoid/attributes/projector.rb +2 -2
  59. data/lib/mongoid/attributes/readonly.rb +3 -3
  60. data/lib/mongoid/attributes.rb +51 -42
  61. data/lib/mongoid/changeable.rb +147 -14
  62. data/lib/mongoid/clients/options.rb +5 -1
  63. data/lib/mongoid/clients/sessions.rb +2 -14
  64. data/lib/mongoid/clients/storage_options.rb +2 -5
  65. data/lib/mongoid/clients/validators/storage.rb +3 -15
  66. data/lib/mongoid/collection_configurable.rb +58 -0
  67. data/lib/mongoid/composable.rb +2 -0
  68. data/lib/mongoid/config/defaults.rb +60 -0
  69. data/lib/mongoid/config/options.rb +3 -0
  70. data/lib/mongoid/config/validators/async_query_executor.rb +24 -0
  71. data/lib/mongoid/config/validators/client.rb +6 -6
  72. data/lib/mongoid/config/validators.rb +1 -0
  73. data/lib/mongoid/config.rb +140 -18
  74. data/lib/mongoid/contextual/aggregable/memory.rb +24 -16
  75. data/lib/mongoid/contextual/aggregable/mongo.rb +5 -5
  76. data/lib/mongoid/contextual/aggregable/none.rb +1 -1
  77. data/lib/mongoid/contextual/atomic.rb +1 -1
  78. data/lib/mongoid/contextual/geo_near.rb +7 -7
  79. data/lib/mongoid/contextual/map_reduce.rb +2 -2
  80. data/lib/mongoid/contextual/memory.rb +285 -58
  81. data/lib/mongoid/contextual/mongo/documents_loader.rb +177 -0
  82. data/lib/mongoid/contextual/mongo.rb +517 -346
  83. data/lib/mongoid/contextual/none.rb +193 -20
  84. data/lib/mongoid/contextual/queryable.rb +1 -1
  85. data/lib/mongoid/contextual.rb +14 -2
  86. data/lib/mongoid/copyable.rb +32 -8
  87. data/lib/mongoid/criteria/findable.rb +8 -5
  88. data/lib/mongoid/criteria/includable.rb +27 -22
  89. data/lib/mongoid/criteria/marshalable.rb +10 -2
  90. data/lib/mongoid/criteria/permission.rb +1 -1
  91. data/lib/mongoid/criteria/queryable/aggregable.rb +2 -2
  92. data/lib/mongoid/criteria/queryable/extensions/array.rb +3 -16
  93. data/lib/mongoid/criteria/queryable/extensions/big_decimal.rb +25 -4
  94. data/lib/mongoid/criteria/queryable/extensions/boolean.rb +2 -2
  95. data/lib/mongoid/criteria/queryable/extensions/date.rb +6 -1
  96. data/lib/mongoid/criteria/queryable/extensions/date_time.rb +6 -1
  97. data/lib/mongoid/criteria/queryable/extensions/hash.rb +1 -17
  98. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -9
  99. data/lib/mongoid/criteria/queryable/extensions/object.rb +2 -1
  100. data/lib/mongoid/criteria/queryable/extensions/range.rb +13 -5
  101. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +3 -3
  102. data/lib/mongoid/criteria/queryable/extensions/set.rb +1 -1
  103. data/lib/mongoid/criteria/queryable/extensions/string.rb +4 -14
  104. data/lib/mongoid/criteria/queryable/extensions/symbol.rb +4 -12
  105. data/lib/mongoid/criteria/queryable/extensions/time.rb +6 -1
  106. data/lib/mongoid/criteria/queryable/extensions/time_with_zone.rb +6 -1
  107. data/lib/mongoid/criteria/queryable/key.rb +4 -4
  108. data/lib/mongoid/criteria/queryable/mergeable.rb +1 -1
  109. data/lib/mongoid/criteria/queryable/optional.rb +11 -17
  110. data/lib/mongoid/criteria/queryable/options.rb +2 -2
  111. data/lib/mongoid/criteria/queryable/pipeline.rb +1 -1
  112. data/lib/mongoid/criteria/queryable/selectable.rb +47 -38
  113. data/lib/mongoid/criteria/queryable/selector.rb +93 -8
  114. data/lib/mongoid/criteria/queryable/smash.rb +40 -7
  115. data/lib/mongoid/criteria/queryable/storable.rb +1 -1
  116. data/lib/mongoid/criteria/queryable.rb +12 -7
  117. data/lib/mongoid/criteria/scopable.rb +2 -2
  118. data/lib/mongoid/criteria/translator.rb +45 -0
  119. data/lib/mongoid/criteria.rb +20 -40
  120. data/lib/mongoid/deprecable.rb +36 -0
  121. data/lib/mongoid/deprecation.rb +25 -0
  122. data/lib/mongoid/document.rb +127 -35
  123. data/lib/mongoid/equality.rb +8 -8
  124. data/lib/mongoid/errors/create_collection_failure.rb +33 -0
  125. data/lib/mongoid/errors/document_not_found.rb +10 -6
  126. data/lib/mongoid/errors/drop_collection_failure.rb +27 -0
  127. data/lib/mongoid/errors/immutable_attribute.rb +26 -0
  128. data/lib/mongoid/errors/invalid_async_query_executor.rb +25 -0
  129. data/lib/mongoid/errors/invalid_config_option.rb +1 -1
  130. data/lib/mongoid/errors/invalid_dependent_strategy.rb +1 -1
  131. data/lib/mongoid/errors/invalid_dot_dollar_assignment.rb +23 -0
  132. data/lib/mongoid/errors/invalid_field.rb +6 -2
  133. data/lib/mongoid/errors/invalid_field_type.rb +26 -0
  134. data/lib/mongoid/errors/invalid_global_executor_concurrency.rb +22 -0
  135. data/lib/mongoid/errors/invalid_relation.rb +1 -1
  136. data/lib/mongoid/errors/invalid_relation_option.rb +1 -1
  137. data/lib/mongoid/errors/invalid_session_use.rb +1 -1
  138. data/lib/mongoid/errors/invalid_storage_options.rb +1 -1
  139. data/lib/mongoid/errors/invalid_storage_parent.rb +2 -0
  140. data/lib/mongoid/errors/mongoid_error.rb +3 -3
  141. data/lib/mongoid/errors/nested_attributes_metadata_not_found.rb +1 -1
  142. data/lib/mongoid/errors/no_client_database.rb +1 -1
  143. data/lib/mongoid/errors/no_client_hosts.rb +1 -1
  144. data/lib/mongoid/errors/readonly_attribute.rb +1 -1
  145. data/lib/mongoid/errors/too_many_nested_attribute_records.rb +1 -1
  146. data/lib/mongoid/errors/unknown_attribute.rb +1 -1
  147. data/lib/mongoid/errors.rb +6 -3
  148. data/lib/mongoid/extensions/array.rb +9 -7
  149. data/lib/mongoid/extensions/big_decimal.rb +33 -10
  150. data/lib/mongoid/extensions/binary.rb +42 -0
  151. data/lib/mongoid/extensions/boolean.rb +8 -2
  152. data/lib/mongoid/extensions/date.rb +26 -20
  153. data/lib/mongoid/extensions/date_time.rb +1 -1
  154. data/lib/mongoid/extensions/false_class.rb +1 -1
  155. data/lib/mongoid/extensions/float.rb +7 -4
  156. data/lib/mongoid/extensions/hash.rb +19 -8
  157. data/lib/mongoid/extensions/integer.rb +7 -4
  158. data/lib/mongoid/extensions/module.rb +1 -1
  159. data/lib/mongoid/extensions/object.rb +10 -8
  160. data/lib/mongoid/extensions/range.rb +41 -10
  161. data/lib/mongoid/extensions/regexp.rb +11 -4
  162. data/lib/mongoid/extensions/set.rb +11 -4
  163. data/lib/mongoid/extensions/string.rb +11 -22
  164. data/lib/mongoid/extensions/symbol.rb +4 -15
  165. data/lib/mongoid/extensions/time.rb +29 -16
  166. data/lib/mongoid/extensions/time_with_zone.rb +1 -2
  167. data/lib/mongoid/extensions/true_class.rb +1 -1
  168. data/lib/mongoid/extensions.rb +1 -0
  169. data/lib/mongoid/factory.rb +55 -7
  170. data/lib/mongoid/fields/foreign_key.rb +11 -4
  171. data/lib/mongoid/fields/localized.rb +19 -4
  172. data/lib/mongoid/fields/standard.rb +17 -7
  173. data/lib/mongoid/fields/validators/macro.rb +3 -9
  174. data/lib/mongoid/fields.rb +129 -20
  175. data/lib/mongoid/findable.rb +54 -24
  176. data/lib/mongoid/indexable/specification.rb +2 -2
  177. data/lib/mongoid/indexable/validators/options.rb +6 -2
  178. data/lib/mongoid/interceptable.rb +76 -15
  179. data/lib/mongoid/matchable.rb +1 -1
  180. data/lib/mongoid/matcher/eq_impl.rb +1 -1
  181. data/lib/mongoid/matcher/type.rb +1 -1
  182. data/lib/mongoid/matcher.rb +33 -13
  183. data/lib/mongoid/persistable/creatable.rb +19 -9
  184. data/lib/mongoid/persistable/deletable.rb +2 -2
  185. data/lib/mongoid/persistable/destroyable.rb +1 -1
  186. data/lib/mongoid/persistable/savable.rb +14 -2
  187. data/lib/mongoid/persistable/unsettable.rb +2 -2
  188. data/lib/mongoid/persistable/updatable.rb +69 -12
  189. data/lib/mongoid/persistable/upsertable.rb +21 -2
  190. data/lib/mongoid/persistable.rb +6 -3
  191. data/lib/mongoid/persistence_context.rb +6 -4
  192. data/lib/mongoid/query_cache.rb +13 -261
  193. data/lib/mongoid/railties/controller_runtime.rb +1 -1
  194. data/lib/mongoid/railties/database.rake +7 -2
  195. data/lib/mongoid/reloadable.rb +10 -8
  196. data/lib/mongoid/scopable.rb +15 -13
  197. data/lib/mongoid/selectable.rb +1 -2
  198. data/lib/mongoid/serializable.rb +10 -6
  199. data/lib/mongoid/stateful.rb +57 -10
  200. data/lib/mongoid/tasks/database.rake +12 -0
  201. data/lib/mongoid/tasks/database.rb +20 -2
  202. data/lib/mongoid/threaded/lifecycle.rb +5 -5
  203. data/lib/mongoid/threaded.rb +42 -12
  204. data/lib/mongoid/timestamps/created.rb +1 -1
  205. data/lib/mongoid/timestamps/updated.rb +2 -2
  206. data/lib/mongoid/touchable.rb +2 -3
  207. data/lib/mongoid/traversable.rb +5 -4
  208. data/lib/mongoid/utils.rb +22 -0
  209. data/lib/mongoid/validatable/localizable.rb +1 -1
  210. data/lib/mongoid/validatable/macros.rb +5 -7
  211. data/lib/mongoid/validatable/presence.rb +2 -2
  212. data/lib/mongoid/validatable/uniqueness.rb +9 -8
  213. data/lib/mongoid/validatable.rb +9 -6
  214. data/lib/mongoid/version.rb +1 -1
  215. data/lib/mongoid/warnings.rb +19 -4
  216. data/lib/mongoid.rb +17 -3
  217. data/spec/config/mongoid.yml +16 -0
  218. data/spec/integration/app_spec.rb +10 -14
  219. data/spec/integration/associations/belongs_to_spec.rb +18 -0
  220. data/spec/integration/associations/embedded_spec.rb +15 -0
  221. data/spec/integration/associations/embeds_many_spec.rb +15 -2
  222. data/spec/integration/associations/embeds_one_spec.rb +18 -0
  223. data/spec/integration/associations/foreign_key_spec.rb +9 -0
  224. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +21 -0
  225. data/spec/integration/associations/has_one_spec.rb +97 -1
  226. data/spec/integration/associations/scope_option_spec.rb +1 -1
  227. data/spec/integration/callbacks_models.rb +132 -1
  228. data/spec/integration/callbacks_spec.rb +360 -4
  229. data/spec/integration/criteria/range_spec.rb +95 -1
  230. data/spec/integration/discriminator_key_spec.rb +118 -80
  231. data/spec/integration/dots_and_dollars_spec.rb +277 -0
  232. data/spec/integration/i18n_fallbacks_spec.rb +3 -32
  233. data/spec/integration/matcher_examples_spec.rb +20 -13
  234. data/spec/integration/matcher_operator_data/type_decimal.yml +3 -2
  235. data/spec/integration/matcher_operator_spec.rb +3 -5
  236. data/spec/integration/persistence/range_field_spec.rb +350 -0
  237. data/spec/mongoid/association/counter_cache_spec.rb +1 -1
  238. data/spec/mongoid/association/depending_spec.rb +9 -9
  239. data/spec/mongoid/association/eager_spec.rb +2 -1
  240. data/spec/mongoid/association/embedded/embedded_in/binding_spec.rb +2 -1
  241. data/spec/mongoid/association/embedded/embedded_in/buildable_spec.rb +54 -0
  242. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +96 -9
  243. data/spec/mongoid/association/embedded/embeds_many/buildable_spec.rb +112 -0
  244. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +255 -65
  245. data/spec/mongoid/association/embedded/embeds_many_models.rb +37 -0
  246. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +12 -0
  247. data/spec/mongoid/association/embedded/embeds_many_spec.rb +68 -0
  248. data/spec/mongoid/association/embedded/embeds_one/buildable_spec.rb +25 -0
  249. data/spec/mongoid/association/embedded/embeds_one/proxy_spec.rb +15 -2
  250. data/spec/mongoid/association/embedded/embeds_one_models.rb +19 -0
  251. data/spec/mongoid/association/embedded/embeds_one_spec.rb +28 -0
  252. data/spec/mongoid/association/referenced/belongs_to/binding_spec.rb +2 -1
  253. data/spec/mongoid/association/referenced/belongs_to/buildable_spec.rb +54 -0
  254. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +15 -0
  255. data/spec/mongoid/association/referenced/belongs_to_models.rb +11 -0
  256. data/spec/mongoid/association/referenced/belongs_to_spec.rb +4 -20
  257. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +186 -229
  258. data/spec/mongoid/association/referenced/has_and_belongs_to_many_models.rb +25 -0
  259. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +35 -2
  260. data/spec/mongoid/association/referenced/has_many/buildable_spec.rb +109 -0
  261. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +2 -56
  262. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +173 -177
  263. data/spec/mongoid/association/referenced/has_many_models.rb +3 -1
  264. data/spec/mongoid/association/referenced/has_many_spec.rb +25 -0
  265. data/spec/mongoid/association/referenced/has_one/buildable_spec.rb +2 -2
  266. data/spec/mongoid/association/referenced/has_one/proxy_spec.rb +107 -1
  267. data/spec/mongoid/association/referenced/has_one_models.rb +16 -0
  268. data/spec/mongoid/association/syncable_spec.rb +15 -1
  269. data/spec/mongoid/atomic/paths_spec.rb +0 -14
  270. data/spec/mongoid/attributes/nested_spec.rb +80 -11
  271. data/spec/mongoid/attributes/nested_spec_models.rb +48 -0
  272. data/spec/mongoid/attributes/projector_spec.rb +1 -5
  273. data/spec/mongoid/attributes_spec.rb +510 -33
  274. data/spec/mongoid/changeable_spec.rb +429 -37
  275. data/spec/mongoid/clients/factory_spec.rb +23 -30
  276. data/spec/mongoid/clients/sessions_spec.rb +0 -38
  277. data/spec/mongoid/clients_spec.rb +149 -15
  278. data/spec/mongoid/collection_configurable_spec.rb +158 -0
  279. data/spec/mongoid/config/defaults_spec.rb +160 -0
  280. data/spec/mongoid/config_spec.rb +214 -31
  281. data/spec/mongoid/contextual/aggregable/memory_spec.rb +396 -158
  282. data/spec/mongoid/contextual/aggregable/memory_table.yml +88 -0
  283. data/spec/mongoid/contextual/aggregable/memory_table_spec.rb +62 -0
  284. data/spec/mongoid/contextual/map_reduce_spec.rb +2 -16
  285. data/spec/mongoid/contextual/memory_spec.rb +850 -88
  286. data/spec/mongoid/contextual/mongo/documents_loader_spec.rb +187 -0
  287. data/spec/mongoid/contextual/mongo_spec.rb +1572 -435
  288. data/spec/mongoid/contextual/none_spec.rb +60 -21
  289. data/spec/mongoid/copyable_spec.rb +453 -11
  290. data/spec/mongoid/criteria/findable_spec.rb +86 -210
  291. data/spec/mongoid/criteria/includable_spec.rb +1492 -0
  292. data/spec/mongoid/criteria/includable_spec_models.rb +54 -0
  293. data/spec/mongoid/criteria/marshalable_spec.rb +18 -1
  294. data/spec/mongoid/criteria/queryable/extensions/array_spec.rb +7 -19
  295. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +134 -26
  296. data/spec/mongoid/criteria/queryable/extensions/date_spec.rb +11 -0
  297. data/spec/mongoid/criteria/queryable/extensions/date_time_spec.rb +11 -0
  298. data/spec/mongoid/criteria/queryable/extensions/hash_spec.rb +0 -15
  299. data/spec/mongoid/criteria/queryable/extensions/numeric_spec.rb +73 -7
  300. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +4 -69
  301. data/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb +0 -59
  302. data/spec/mongoid/criteria/queryable/extensions/time_spec.rb +11 -0
  303. data/spec/mongoid/criteria/queryable/extensions/time_with_zone_spec.rb +11 -0
  304. data/spec/mongoid/criteria/queryable/optional_spec.rb +15 -484
  305. data/spec/mongoid/criteria/queryable/options_spec.rb +1 -1
  306. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +469 -0
  307. data/spec/mongoid/criteria/queryable/selectable_spec.rb +78 -86
  308. data/spec/mongoid/criteria/queryable/selector_spec.rb +90 -5
  309. data/spec/mongoid/criteria/queryable/storable_spec.rb +72 -0
  310. data/spec/mongoid/criteria/translator_spec.rb +132 -0
  311. data/spec/mongoid/criteria_projection_spec.rb +1 -5
  312. data/spec/mongoid/criteria_spec.rb +469 -1205
  313. data/spec/mongoid/document_fields_spec.rb +173 -24
  314. data/spec/mongoid/document_spec.rb +32 -41
  315. data/spec/mongoid/errors/document_not_found_spec.rb +29 -2
  316. data/spec/mongoid/errors/invalid_field_spec.rb +1 -1
  317. data/spec/mongoid/errors/invalid_field_type_spec.rb +55 -0
  318. data/spec/mongoid/errors/mongoid_error_spec.rb +3 -1
  319. data/spec/mongoid/errors/no_environment_spec.rb +3 -3
  320. data/spec/mongoid/errors/readonly_document_spec.rb +2 -2
  321. data/spec/mongoid/errors/too_many_nested_attribute_records_spec.rb +1 -1
  322. data/spec/mongoid/extensions/array_spec.rb +16 -2
  323. data/spec/mongoid/extensions/big_decimal_spec.rb +712 -212
  324. data/spec/mongoid/extensions/binary_spec.rb +44 -9
  325. data/spec/mongoid/extensions/boolean_spec.rb +68 -82
  326. data/spec/mongoid/extensions/date_class_mongoize_spec.rb +7 -3
  327. data/spec/mongoid/extensions/date_spec.rb +71 -1
  328. data/spec/mongoid/extensions/date_time_spec.rb +15 -9
  329. data/spec/mongoid/extensions/float_spec.rb +53 -74
  330. data/spec/mongoid/extensions/hash_spec.rb +33 -3
  331. data/spec/mongoid/extensions/integer_spec.rb +50 -64
  332. data/spec/mongoid/extensions/range_spec.rb +255 -54
  333. data/spec/mongoid/extensions/regexp_spec.rb +58 -33
  334. data/spec/mongoid/extensions/set_spec.rb +106 -0
  335. data/spec/mongoid/extensions/string_spec.rb +53 -25
  336. data/spec/mongoid/extensions/symbol_spec.rb +18 -25
  337. data/spec/mongoid/extensions/time_spec.rb +639 -106
  338. data/spec/mongoid/extensions/time_with_zone_spec.rb +24 -83
  339. data/spec/mongoid/factory_spec.rb +61 -1
  340. data/spec/mongoid/fields/localized_spec.rb +80 -37
  341. data/spec/mongoid/fields_spec.rb +500 -84
  342. data/spec/mongoid/findable_spec.rb +450 -58
  343. data/spec/mongoid/indexable/specification_spec.rb +2 -2
  344. data/spec/mongoid/indexable_spec.rb +55 -30
  345. data/spec/mongoid/interceptable_spec.rb +599 -8
  346. data/spec/mongoid/interceptable_spec_models.rb +235 -4
  347. data/spec/mongoid/matcher/extract_attribute_spec.rb +1 -5
  348. data/spec/mongoid/mongoizable_spec.rb +285 -0
  349. data/spec/mongoid/persistable/creatable_spec.rb +2 -2
  350. data/spec/mongoid/persistable/deletable_spec.rb +28 -8
  351. data/spec/mongoid/persistable/destroyable_spec.rb +28 -8
  352. data/spec/mongoid/persistable/incrementable_spec.rb +37 -0
  353. data/spec/mongoid/persistable/logical_spec.rb +37 -0
  354. data/spec/mongoid/persistable/poppable_spec.rb +36 -0
  355. data/spec/mongoid/persistable/pullable_spec.rb +72 -0
  356. data/spec/mongoid/persistable/pushable_spec.rb +72 -0
  357. data/spec/mongoid/persistable/renamable_spec.rb +36 -0
  358. data/spec/mongoid/persistable/savable_spec.rb +96 -0
  359. data/spec/mongoid/persistable/settable_spec.rb +37 -0
  360. data/spec/mongoid/persistable/unsettable_spec.rb +36 -0
  361. data/spec/mongoid/persistable/updatable_spec.rb +20 -28
  362. data/spec/mongoid/persistable/upsertable_spec.rb +89 -1
  363. data/spec/mongoid/persistence_context_spec.rb +31 -57
  364. data/spec/mongoid/query_cache_middleware_spec.rb +0 -18
  365. data/spec/mongoid/query_cache_spec.rb +56 -215
  366. data/spec/mongoid/reloadable_spec.rb +83 -6
  367. data/spec/mongoid/scopable_spec.rb +91 -1
  368. data/spec/mongoid/serializable_spec.rb +9 -30
  369. data/spec/mongoid/shardable_spec.rb +4 -4
  370. data/spec/mongoid/stateful_spec.rb +150 -8
  371. data/spec/mongoid/tasks/database_rake_spec.rb +74 -0
  372. data/spec/mongoid/tasks/database_spec.rb +127 -0
  373. data/spec/mongoid/timestamps_spec.rb +392 -4
  374. data/spec/mongoid/timestamps_spec_models.rb +67 -0
  375. data/spec/mongoid/touchable_spec.rb +390 -2
  376. data/spec/mongoid/touchable_spec_models.rb +14 -8
  377. data/spec/mongoid/traversable_spec.rb +13 -35
  378. data/spec/mongoid/validatable/presence_spec.rb +1 -1
  379. data/spec/mongoid/validatable/uniqueness_spec.rb +58 -31
  380. data/spec/mongoid/warnings_spec.rb +35 -0
  381. data/spec/mongoid_spec.rb +34 -16
  382. data/spec/rails/controller_extension/controller_runtime_spec.rb +2 -2
  383. data/spec/rails/mongoid_spec.rb +4 -16
  384. data/spec/spec_helper.rb +5 -0
  385. data/spec/support/constraints.rb +24 -0
  386. data/spec/support/immutable_ids.rb +118 -0
  387. data/spec/support/macros.rb +78 -0
  388. data/spec/support/models/artist.rb +0 -1
  389. data/spec/support/models/augmentation.rb +12 -0
  390. data/spec/support/models/band.rb +4 -0
  391. data/spec/support/models/book.rb +1 -0
  392. data/spec/support/models/building.rb +2 -0
  393. data/spec/support/models/catalog.rb +24 -0
  394. data/spec/support/models/circus.rb +3 -0
  395. data/spec/support/models/cover.rb +10 -0
  396. data/spec/support/models/fanatic.rb +8 -0
  397. data/spec/support/models/implant.rb +9 -0
  398. data/spec/support/models/label.rb +2 -0
  399. data/spec/support/models/passport.rb +9 -0
  400. data/spec/support/models/person.rb +2 -0
  401. data/spec/support/models/player.rb +2 -0
  402. data/spec/support/models/powerup.rb +12 -0
  403. data/spec/support/models/product.rb +1 -0
  404. data/spec/support/models/purse.rb +9 -0
  405. data/spec/support/models/registry.rb +1 -0
  406. data/spec/support/models/school.rb +14 -0
  407. data/spec/support/models/shield.rb +18 -0
  408. data/spec/support/models/student.rb +14 -0
  409. data/spec/support/models/weapon.rb +12 -0
  410. data.tar.gz.sig +0 -0
  411. metadata +718 -641
  412. metadata.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
@@ -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, 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
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, 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.
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,71 +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.
563
+ #
564
+ # @example Get the first document.
565
+ # context.first
566
+ #
567
+ # @note Automatically adding a sort on _id when no other sort is
568
+ # defined on the criteria has the potential to cause bad performance issues.
569
+ # If you experience unexpected poor performance when using #first or #last
570
+ # and have no sort defined on the criteria, use #take instead.
571
+ # Be aware that #take won't guarantee order.
575
572
  #
576
- # @param [ String, Symbol ] key The instance variable name
573
+ # @param [ Integer ] limit The number of documents to return.
577
574
  #
578
- # @return the result of the block
579
- def try_cache(key, &block)
580
- unless cached?
581
- yield
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
 
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.
627
+ # Get the last document in the database for the criteria's selector or
628
+ # raise an error if none is found.
618
629
  #
619
- # @param [ nil | Integer | Hash ] limit_or_opts The value to pull the
620
- # limit and option hash from.
630
+ # @example Get the last document.
631
+ # context.last!
621
632
  #
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
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)
630
804
  end
631
805
 
806
+ private
807
+
632
808
  # Update the documents for the provided method.
633
809
  #
634
810
  # @api private
@@ -639,7 +815,7 @@ module Mongoid
639
815
  # @param [ Hash ] attributes The updates.
640
816
  # @param [ Symbol ] method The method to use.
641
817
  #
642
- # @return [ true, false ] If the update succeeded.
818
+ # @return [ true | false ] If the update succeeded.
643
819
  def update_documents(attributes, method = :update_one, opts = {})
644
820
  return false unless attributes
645
821
  attributes = Hash[attributes.map { |k, v| [klass.database_field_name(k.to_s), v] }]
@@ -689,78 +865,34 @@ module Mongoid
689
865
  # Map the inverse sort symbols to the correct MongoDB values.
690
866
  #
691
867
  # @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?
868
+ def inverse_sorting
869
+ sort = view.sort || { _id: 1 }
870
+ Hash[sort.map{|k, v| [k, -1*v]}]
718
871
  end
719
872
 
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
731
- end
732
-
733
- # Get the documents for cached queries.
734
- #
735
- # @api private
873
+ # Get the documents the context should iterate.
736
874
  #
737
- # @example Get the cached documents.
738
- # context.documents
875
+ # If the documents have been already preloaded by `Document::Loader`
876
+ # instance, they will be used.
739
877
  #
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:
746
- #
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.
878
+ # @return [ Array<Document> | Mongo::Collection::View ] The docs to iterate.
752
879
  #
753
880
  # @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
881
  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)
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
764
896
  end
765
897
 
766
898
  # Yield to the document.
@@ -777,11 +909,8 @@ module Mongoid
777
909
  doc = document.respond_to?(:_id) ?
778
910
  document : Factory.from_db(klass, document, criteria)
779
911
  yield(doc)
780
- documents.push(doc) if cacheable?
781
912
  end
782
913
 
783
- private
784
-
785
914
  def _session
786
915
  @criteria.send(:_session)
787
916
  end
@@ -790,6 +919,26 @@ module Mongoid
790
919
  collection.write_concern.nil? || collection.write_concern.acknowledged?
791
920
  end
792
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
+
793
942
  # Extracts the value for the given field name from the given attribute
794
943
  # hash.
795
944
  #
@@ -798,24 +947,18 @@ module Mongoid
798
947
  #
799
948
  # @param [ Object ] The value for the given field name
800
949
  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
950
+ i = 1
951
+ num_meths = field_name.count('.') + 1
952
+ curr = attrs.dup
809
953
 
810
- k = klass
811
- meths = field_name.split('.')
812
- 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
813
956
  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
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
819
962
  end
820
963
 
821
964
  # 1. If curr is an array fetch from all elements in the array.
@@ -828,31 +971,24 @@ module Mongoid
828
971
  # 3. If the meth is an _translations field, do not demongoize the
829
972
  # value so the full hash is returned.
830
973
  # 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) }
974
+ curr = if curr.is_a? Array
975
+ res = fetch_and_demongoize(curr, meth, field)
833
976
  res.empty? ? nil : res
834
- elsif !is_translation && k.fields[meth]&.localized?
835
- if i < meths.length-1
977
+ elsif !is_translation && field&.localized?
978
+ if i < num_meths
836
979
  curr.try(:fetch, meth, nil)
837
980
  else
838
- fetch_and_demongoize(curr, meth, k)
981
+ fetch_and_demongoize(curr, meth, field)
839
982
  end
840
983
  elsif is_translation
841
984
  curr.try(:fetch, meth, nil)
842
985
  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
986
+ fetch_and_demongoize(curr, meth, field)
854
987
  end
988
+
989
+ i += 1
855
990
  end
991
+ curr
856
992
  end
857
993
 
858
994
  # Recursively demongoize the given value. This method recursively traverses
@@ -860,34 +996,41 @@ module Mongoid
860
996
  #
861
997
  # @param [ String ] field_name The name of the field to demongoize.
862
998
  # @param [ Object ] value The value to demongoize.
863
- # @param [ Boolean ] is_translation The field we are retrieving is an
999
+ # @param [ true | false ] is_translation The field we are retrieving is an
864
1000
  # _translations field.
865
1001
  #
866
1002
  # @return [ Object ] The demongoized value.
867
1003
  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
1004
+ field = klass.traverse_association_tree(field_name)
1005
+ demongoize_with_field(field, value, is_translation)
1006
+ end
875
1007
 
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
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)
888
1029
  else
889
- return value.class.demongoize(value)
1030
+ field.demongoize(value)
890
1031
  end
1032
+ else
1033
+ value.class.demongoize(value)
891
1034
  end
892
1035
  end
893
1036
 
@@ -902,6 +1045,34 @@ module Mongoid
902
1045
  docs = eager_load(docs)
903
1046
  limit ? docs : docs.first
904
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
905
1076
  end
906
1077
  end
907
1078
  end