mongoid 7.5.4 → 8.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (414) 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/lib/config/locales/en.yml +92 -43
  6. data/lib/mongoid/association/accessors.rb +40 -11
  7. data/lib/mongoid/association/bindable.rb +50 -2
  8. data/lib/mongoid/association/builders.rb +5 -3
  9. data/lib/mongoid/association/constrainable.rb +0 -1
  10. data/lib/mongoid/association/eager_loadable.rb +29 -7
  11. data/lib/mongoid/association/embedded/batchable.rb +34 -11
  12. data/lib/mongoid/association/embedded/cyclic.rb +1 -1
  13. data/lib/mongoid/association/embedded/embedded_in/binding.rb +24 -2
  14. data/lib/mongoid/association/embedded/embedded_in/buildable.rb +2 -2
  15. data/lib/mongoid/association/embedded/embedded_in/proxy.rb +4 -3
  16. data/lib/mongoid/association/embedded/embedded_in.rb +3 -2
  17. data/lib/mongoid/association/embedded/embeds_many/binding.rb +1 -0
  18. data/lib/mongoid/association/embedded/embeds_many/buildable.rb +4 -3
  19. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +69 -45
  20. data/lib/mongoid/association/embedded/embeds_many.rb +2 -2
  21. data/lib/mongoid/association/embedded/embeds_one/buildable.rb +19 -5
  22. data/lib/mongoid/association/embedded/embeds_one/proxy.rb +24 -5
  23. data/lib/mongoid/association/embedded/embeds_one.rb +3 -3
  24. data/lib/mongoid/association/macros.rb +8 -1
  25. data/lib/mongoid/association/many.rb +11 -7
  26. data/lib/mongoid/association/nested/many.rb +5 -4
  27. data/lib/mongoid/association/nested/nested_buildable.rb +4 -4
  28. data/lib/mongoid/association/nested/one.rb +45 -7
  29. data/lib/mongoid/association/one.rb +2 -2
  30. data/lib/mongoid/association/options.rb +9 -9
  31. data/lib/mongoid/association/proxy.rb +15 -4
  32. data/lib/mongoid/association/referenced/auto_save.rb +4 -3
  33. data/lib/mongoid/association/referenced/belongs_to/binding.rb +1 -0
  34. data/lib/mongoid/association/referenced/belongs_to/buildable.rb +1 -1
  35. data/lib/mongoid/association/referenced/belongs_to/proxy.rb +5 -6
  36. data/lib/mongoid/association/referenced/belongs_to.rb +2 -2
  37. data/lib/mongoid/association/referenced/counter_cache.rb +10 -10
  38. data/lib/mongoid/association/referenced/eager.rb +2 -2
  39. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +70 -13
  40. data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +6 -3
  41. data/lib/mongoid/association/referenced/has_many/enumerable.rb +22 -30
  42. data/lib/mongoid/association/referenced/has_many/proxy.rb +29 -19
  43. data/lib/mongoid/association/referenced/has_many.rb +3 -3
  44. data/lib/mongoid/association/referenced/has_one/buildable.rb +1 -1
  45. data/lib/mongoid/association/referenced/has_one/nested_builder.rb +5 -5
  46. data/lib/mongoid/association/referenced/has_one/proxy.rb +9 -12
  47. data/lib/mongoid/association/referenced/has_one.rb +3 -3
  48. data/lib/mongoid/association/referenced/syncable.rb +4 -4
  49. data/lib/mongoid/association/reflections.rb +4 -4
  50. data/lib/mongoid/association/relatable.rb +44 -10
  51. data/lib/mongoid/association.rb +5 -5
  52. data/lib/mongoid/atomic/modifiers.rb +2 -2
  53. data/lib/mongoid/atomic.rb +7 -0
  54. data/lib/mongoid/attributes/dynamic.rb +4 -4
  55. data/lib/mongoid/attributes/nested.rb +6 -6
  56. data/lib/mongoid/attributes/processing.rb +37 -6
  57. data/lib/mongoid/attributes/projector.rb +2 -2
  58. data/lib/mongoid/attributes/readonly.rb +3 -3
  59. data/lib/mongoid/attributes.rb +51 -42
  60. data/lib/mongoid/changeable.rb +147 -14
  61. data/lib/mongoid/clients/options.rb +5 -1
  62. data/lib/mongoid/clients/sessions.rb +2 -14
  63. data/lib/mongoid/clients/storage_options.rb +2 -5
  64. data/lib/mongoid/clients/validators/storage.rb +3 -15
  65. data/lib/mongoid/collection_configurable.rb +58 -0
  66. data/lib/mongoid/composable.rb +2 -0
  67. data/lib/mongoid/config/defaults.rb +60 -0
  68. data/lib/mongoid/config/options.rb +3 -0
  69. data/lib/mongoid/config/validators/async_query_executor.rb +24 -0
  70. data/lib/mongoid/config/validators/client.rb +6 -6
  71. data/lib/mongoid/config/validators.rb +1 -0
  72. data/lib/mongoid/config.rb +140 -18
  73. data/lib/mongoid/contextual/aggregable/memory.rb +24 -16
  74. data/lib/mongoid/contextual/aggregable/mongo.rb +5 -5
  75. data/lib/mongoid/contextual/aggregable/none.rb +1 -1
  76. data/lib/mongoid/contextual/atomic.rb +1 -1
  77. data/lib/mongoid/contextual/geo_near.rb +7 -7
  78. data/lib/mongoid/contextual/map_reduce.rb +2 -2
  79. data/lib/mongoid/contextual/memory.rb +285 -58
  80. data/lib/mongoid/contextual/mongo/documents_loader.rb +177 -0
  81. data/lib/mongoid/contextual/mongo.rb +517 -346
  82. data/lib/mongoid/contextual/none.rb +193 -20
  83. data/lib/mongoid/contextual/queryable.rb +1 -1
  84. data/lib/mongoid/contextual.rb +14 -2
  85. data/lib/mongoid/copyable.rb +32 -8
  86. data/lib/mongoid/criteria/findable.rb +8 -5
  87. data/lib/mongoid/criteria/includable.rb +27 -22
  88. data/lib/mongoid/criteria/marshalable.rb +10 -2
  89. data/lib/mongoid/criteria/permission.rb +1 -1
  90. data/lib/mongoid/criteria/queryable/aggregable.rb +2 -2
  91. data/lib/mongoid/criteria/queryable/extensions/array.rb +3 -16
  92. data/lib/mongoid/criteria/queryable/extensions/big_decimal.rb +25 -4
  93. data/lib/mongoid/criteria/queryable/extensions/boolean.rb +2 -2
  94. data/lib/mongoid/criteria/queryable/extensions/date.rb +6 -1
  95. data/lib/mongoid/criteria/queryable/extensions/date_time.rb +6 -1
  96. data/lib/mongoid/criteria/queryable/extensions/hash.rb +1 -17
  97. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -9
  98. data/lib/mongoid/criteria/queryable/extensions/object.rb +2 -1
  99. data/lib/mongoid/criteria/queryable/extensions/range.rb +13 -5
  100. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +3 -3
  101. data/lib/mongoid/criteria/queryable/extensions/set.rb +1 -1
  102. data/lib/mongoid/criteria/queryable/extensions/string.rb +4 -14
  103. data/lib/mongoid/criteria/queryable/extensions/symbol.rb +4 -12
  104. data/lib/mongoid/criteria/queryable/extensions/time.rb +6 -1
  105. data/lib/mongoid/criteria/queryable/extensions/time_with_zone.rb +6 -1
  106. data/lib/mongoid/criteria/queryable/key.rb +4 -4
  107. data/lib/mongoid/criteria/queryable/mergeable.rb +1 -1
  108. data/lib/mongoid/criteria/queryable/optional.rb +11 -17
  109. data/lib/mongoid/criteria/queryable/options.rb +2 -2
  110. data/lib/mongoid/criteria/queryable/pipeline.rb +1 -1
  111. data/lib/mongoid/criteria/queryable/selectable.rb +47 -38
  112. data/lib/mongoid/criteria/queryable/selector.rb +92 -7
  113. data/lib/mongoid/criteria/queryable/smash.rb +40 -7
  114. data/lib/mongoid/criteria/queryable.rb +12 -7
  115. data/lib/mongoid/criteria/scopable.rb +2 -2
  116. data/lib/mongoid/criteria/translator.rb +45 -0
  117. data/lib/mongoid/criteria.rb +20 -40
  118. data/lib/mongoid/deprecable.rb +36 -0
  119. data/lib/mongoid/deprecation.rb +25 -0
  120. data/lib/mongoid/document.rb +127 -35
  121. data/lib/mongoid/equality.rb +8 -8
  122. data/lib/mongoid/errors/create_collection_failure.rb +33 -0
  123. data/lib/mongoid/errors/document_not_found.rb +10 -6
  124. data/lib/mongoid/errors/drop_collection_failure.rb +27 -0
  125. data/lib/mongoid/errors/immutable_attribute.rb +26 -0
  126. data/lib/mongoid/errors/invalid_async_query_executor.rb +25 -0
  127. data/lib/mongoid/errors/invalid_config_option.rb +1 -1
  128. data/lib/mongoid/errors/invalid_dependent_strategy.rb +1 -1
  129. data/lib/mongoid/errors/invalid_dot_dollar_assignment.rb +23 -0
  130. data/lib/mongoid/errors/invalid_field.rb +6 -2
  131. data/lib/mongoid/errors/invalid_field_type.rb +26 -0
  132. data/lib/mongoid/errors/invalid_global_executor_concurrency.rb +22 -0
  133. data/lib/mongoid/errors/invalid_relation.rb +1 -1
  134. data/lib/mongoid/errors/invalid_relation_option.rb +1 -1
  135. data/lib/mongoid/errors/invalid_session_use.rb +1 -1
  136. data/lib/mongoid/errors/invalid_storage_options.rb +1 -1
  137. data/lib/mongoid/errors/invalid_storage_parent.rb +2 -0
  138. data/lib/mongoid/errors/mongoid_error.rb +3 -3
  139. data/lib/mongoid/errors/nested_attributes_metadata_not_found.rb +1 -1
  140. data/lib/mongoid/errors/no_client_database.rb +1 -1
  141. data/lib/mongoid/errors/no_client_hosts.rb +1 -1
  142. data/lib/mongoid/errors/readonly_attribute.rb +1 -1
  143. data/lib/mongoid/errors/too_many_nested_attribute_records.rb +1 -1
  144. data/lib/mongoid/errors/unknown_attribute.rb +1 -1
  145. data/lib/mongoid/errors.rb +6 -3
  146. data/lib/mongoid/extensions/array.rb +9 -7
  147. data/lib/mongoid/extensions/big_decimal.rb +33 -10
  148. data/lib/mongoid/extensions/binary.rb +42 -0
  149. data/lib/mongoid/extensions/boolean.rb +8 -2
  150. data/lib/mongoid/extensions/date.rb +26 -20
  151. data/lib/mongoid/extensions/date_time.rb +1 -1
  152. data/lib/mongoid/extensions/false_class.rb +1 -1
  153. data/lib/mongoid/extensions/float.rb +7 -4
  154. data/lib/mongoid/extensions/hash.rb +19 -8
  155. data/lib/mongoid/extensions/integer.rb +7 -4
  156. data/lib/mongoid/extensions/module.rb +1 -1
  157. data/lib/mongoid/extensions/object.rb +10 -8
  158. data/lib/mongoid/extensions/range.rb +41 -10
  159. data/lib/mongoid/extensions/regexp.rb +11 -4
  160. data/lib/mongoid/extensions/set.rb +11 -4
  161. data/lib/mongoid/extensions/string.rb +11 -22
  162. data/lib/mongoid/extensions/symbol.rb +4 -15
  163. data/lib/mongoid/extensions/time.rb +29 -16
  164. data/lib/mongoid/extensions/time_with_zone.rb +1 -2
  165. data/lib/mongoid/extensions/true_class.rb +1 -1
  166. data/lib/mongoid/extensions.rb +1 -0
  167. data/lib/mongoid/factory.rb +55 -7
  168. data/lib/mongoid/fields/foreign_key.rb +11 -4
  169. data/lib/mongoid/fields/localized.rb +19 -4
  170. data/lib/mongoid/fields/standard.rb +17 -7
  171. data/lib/mongoid/fields/validators/macro.rb +3 -9
  172. data/lib/mongoid/fields.rb +129 -20
  173. data/lib/mongoid/findable.rb +54 -24
  174. data/lib/mongoid/indexable/specification.rb +2 -2
  175. data/lib/mongoid/indexable/validators/options.rb +6 -2
  176. data/lib/mongoid/interceptable.rb +76 -15
  177. data/lib/mongoid/matchable.rb +1 -1
  178. data/lib/mongoid/matcher/eq_impl.rb +1 -1
  179. data/lib/mongoid/matcher/type.rb +1 -1
  180. data/lib/mongoid/matcher.rb +33 -13
  181. data/lib/mongoid/persistable/creatable.rb +19 -9
  182. data/lib/mongoid/persistable/deletable.rb +2 -2
  183. data/lib/mongoid/persistable/destroyable.rb +1 -1
  184. data/lib/mongoid/persistable/savable.rb +14 -2
  185. data/lib/mongoid/persistable/unsettable.rb +2 -2
  186. data/lib/mongoid/persistable/updatable.rb +69 -12
  187. data/lib/mongoid/persistable/upsertable.rb +21 -2
  188. data/lib/mongoid/persistable.rb +6 -3
  189. data/lib/mongoid/persistence_context.rb +6 -4
  190. data/lib/mongoid/query_cache.rb +13 -261
  191. data/lib/mongoid/railties/controller_runtime.rb +1 -1
  192. data/lib/mongoid/railties/database.rake +7 -2
  193. data/lib/mongoid/reloadable.rb +10 -8
  194. data/lib/mongoid/scopable.rb +15 -13
  195. data/lib/mongoid/selectable.rb +1 -2
  196. data/lib/mongoid/serializable.rb +10 -6
  197. data/lib/mongoid/stateful.rb +57 -10
  198. data/lib/mongoid/tasks/database.rake +12 -0
  199. data/lib/mongoid/tasks/database.rb +20 -2
  200. data/lib/mongoid/threaded/lifecycle.rb +5 -5
  201. data/lib/mongoid/threaded.rb +42 -12
  202. data/lib/mongoid/timestamps/created.rb +1 -1
  203. data/lib/mongoid/timestamps/updated.rb +2 -2
  204. data/lib/mongoid/touchable.rb +2 -3
  205. data/lib/mongoid/traversable.rb +5 -4
  206. data/lib/mongoid/utils.rb +22 -0
  207. data/lib/mongoid/validatable/localizable.rb +1 -1
  208. data/lib/mongoid/validatable/macros.rb +5 -7
  209. data/lib/mongoid/validatable/presence.rb +2 -2
  210. data/lib/mongoid/validatable/uniqueness.rb +9 -8
  211. data/lib/mongoid/validatable.rb +9 -6
  212. data/lib/mongoid/version.rb +1 -1
  213. data/lib/mongoid/warnings.rb +19 -4
  214. data/lib/mongoid.rb +17 -3
  215. data/spec/config/mongoid.yml +16 -0
  216. data/spec/integration/app_spec.rb +10 -14
  217. data/spec/integration/associations/belongs_to_spec.rb +18 -0
  218. data/spec/integration/associations/embedded_spec.rb +15 -0
  219. data/spec/integration/associations/embeds_many_spec.rb +15 -2
  220. data/spec/integration/associations/embeds_one_spec.rb +18 -0
  221. data/spec/integration/associations/foreign_key_spec.rb +9 -0
  222. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +21 -0
  223. data/spec/integration/associations/has_one_spec.rb +97 -1
  224. data/spec/integration/associations/scope_option_spec.rb +1 -1
  225. data/spec/integration/callbacks_models.rb +132 -1
  226. data/spec/integration/callbacks_spec.rb +360 -4
  227. data/spec/integration/criteria/range_spec.rb +95 -1
  228. data/spec/integration/discriminator_key_spec.rb +118 -80
  229. data/spec/integration/dots_and_dollars_spec.rb +277 -0
  230. data/spec/integration/i18n_fallbacks_spec.rb +3 -32
  231. data/spec/integration/matcher_examples_spec.rb +20 -13
  232. data/spec/integration/matcher_operator_data/type_decimal.yml +3 -2
  233. data/spec/integration/matcher_operator_spec.rb +3 -5
  234. data/spec/integration/persistence/range_field_spec.rb +350 -0
  235. data/spec/mongoid/association/counter_cache_spec.rb +1 -1
  236. data/spec/mongoid/association/depending_spec.rb +9 -9
  237. data/spec/mongoid/association/eager_spec.rb +2 -1
  238. data/spec/mongoid/association/embedded/embedded_in/binding_spec.rb +2 -1
  239. data/spec/mongoid/association/embedded/embedded_in/buildable_spec.rb +54 -0
  240. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +96 -9
  241. data/spec/mongoid/association/embedded/embeds_many/buildable_spec.rb +112 -0
  242. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +255 -65
  243. data/spec/mongoid/association/embedded/embeds_many_models.rb +37 -0
  244. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +12 -0
  245. data/spec/mongoid/association/embedded/embeds_many_spec.rb +68 -0
  246. data/spec/mongoid/association/embedded/embeds_one/buildable_spec.rb +25 -0
  247. data/spec/mongoid/association/embedded/embeds_one/proxy_spec.rb +15 -2
  248. data/spec/mongoid/association/embedded/embeds_one_models.rb +19 -0
  249. data/spec/mongoid/association/embedded/embeds_one_spec.rb +28 -0
  250. data/spec/mongoid/association/referenced/belongs_to/binding_spec.rb +2 -1
  251. data/spec/mongoid/association/referenced/belongs_to/buildable_spec.rb +54 -0
  252. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +15 -0
  253. data/spec/mongoid/association/referenced/belongs_to_models.rb +11 -0
  254. data/spec/mongoid/association/referenced/belongs_to_spec.rb +4 -20
  255. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +186 -229
  256. data/spec/mongoid/association/referenced/has_and_belongs_to_many_models.rb +25 -0
  257. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +35 -2
  258. data/spec/mongoid/association/referenced/has_many/buildable_spec.rb +109 -0
  259. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +2 -56
  260. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +173 -177
  261. data/spec/mongoid/association/referenced/has_many_models.rb +3 -1
  262. data/spec/mongoid/association/referenced/has_many_spec.rb +25 -0
  263. data/spec/mongoid/association/referenced/has_one/buildable_spec.rb +2 -2
  264. data/spec/mongoid/association/referenced/has_one/proxy_spec.rb +107 -1
  265. data/spec/mongoid/association/referenced/has_one_models.rb +16 -0
  266. data/spec/mongoid/association/syncable_spec.rb +15 -1
  267. data/spec/mongoid/atomic/paths_spec.rb +0 -14
  268. data/spec/mongoid/attributes/nested_spec.rb +80 -11
  269. data/spec/mongoid/attributes/nested_spec_models.rb +48 -0
  270. data/spec/mongoid/attributes/projector_spec.rb +1 -5
  271. data/spec/mongoid/attributes_spec.rb +510 -33
  272. data/spec/mongoid/changeable_spec.rb +429 -37
  273. data/spec/mongoid/clients/factory_spec.rb +23 -30
  274. data/spec/mongoid/clients/sessions_spec.rb +0 -38
  275. data/spec/mongoid/clients_spec.rb +149 -15
  276. data/spec/mongoid/collection_configurable_spec.rb +158 -0
  277. data/spec/mongoid/config/defaults_spec.rb +160 -0
  278. data/spec/mongoid/config_spec.rb +214 -31
  279. data/spec/mongoid/contextual/aggregable/memory_spec.rb +396 -158
  280. data/spec/mongoid/contextual/aggregable/memory_table.yml +88 -0
  281. data/spec/mongoid/contextual/aggregable/memory_table_spec.rb +62 -0
  282. data/spec/mongoid/contextual/map_reduce_spec.rb +2 -16
  283. data/spec/mongoid/contextual/memory_spec.rb +850 -88
  284. data/spec/mongoid/contextual/mongo/documents_loader_spec.rb +187 -0
  285. data/spec/mongoid/contextual/mongo_spec.rb +1572 -435
  286. data/spec/mongoid/contextual/none_spec.rb +60 -21
  287. data/spec/mongoid/copyable_spec.rb +453 -11
  288. data/spec/mongoid/criteria/findable_spec.rb +86 -210
  289. data/spec/mongoid/criteria/includable_spec.rb +1492 -0
  290. data/spec/mongoid/criteria/includable_spec_models.rb +54 -0
  291. data/spec/mongoid/criteria/marshalable_spec.rb +18 -1
  292. data/spec/mongoid/criteria/queryable/extensions/array_spec.rb +7 -19
  293. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +134 -26
  294. data/spec/mongoid/criteria/queryable/extensions/date_spec.rb +11 -0
  295. data/spec/mongoid/criteria/queryable/extensions/date_time_spec.rb +11 -0
  296. data/spec/mongoid/criteria/queryable/extensions/hash_spec.rb +0 -15
  297. data/spec/mongoid/criteria/queryable/extensions/numeric_spec.rb +73 -7
  298. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +4 -69
  299. data/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb +0 -59
  300. data/spec/mongoid/criteria/queryable/extensions/time_spec.rb +11 -0
  301. data/spec/mongoid/criteria/queryable/extensions/time_with_zone_spec.rb +11 -0
  302. data/spec/mongoid/criteria/queryable/optional_spec.rb +15 -484
  303. data/spec/mongoid/criteria/queryable/options_spec.rb +1 -1
  304. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +469 -0
  305. data/spec/mongoid/criteria/queryable/selectable_spec.rb +78 -86
  306. data/spec/mongoid/criteria/queryable/selector_spec.rb +15 -3
  307. data/spec/mongoid/criteria/translator_spec.rb +132 -0
  308. data/spec/mongoid/criteria_projection_spec.rb +1 -5
  309. data/spec/mongoid/criteria_spec.rb +469 -1205
  310. data/spec/mongoid/document_fields_spec.rb +173 -24
  311. data/spec/mongoid/document_spec.rb +32 -41
  312. data/spec/mongoid/errors/document_not_found_spec.rb +29 -2
  313. data/spec/mongoid/errors/invalid_field_spec.rb +1 -1
  314. data/spec/mongoid/errors/invalid_field_type_spec.rb +55 -0
  315. data/spec/mongoid/errors/mongoid_error_spec.rb +3 -1
  316. data/spec/mongoid/errors/no_environment_spec.rb +3 -3
  317. data/spec/mongoid/errors/readonly_document_spec.rb +2 -2
  318. data/spec/mongoid/errors/too_many_nested_attribute_records_spec.rb +1 -1
  319. data/spec/mongoid/extensions/array_spec.rb +16 -2
  320. data/spec/mongoid/extensions/big_decimal_spec.rb +712 -212
  321. data/spec/mongoid/extensions/binary_spec.rb +44 -9
  322. data/spec/mongoid/extensions/boolean_spec.rb +68 -82
  323. data/spec/mongoid/extensions/date_class_mongoize_spec.rb +7 -3
  324. data/spec/mongoid/extensions/date_spec.rb +71 -1
  325. data/spec/mongoid/extensions/date_time_spec.rb +15 -9
  326. data/spec/mongoid/extensions/float_spec.rb +53 -74
  327. data/spec/mongoid/extensions/hash_spec.rb +33 -3
  328. data/spec/mongoid/extensions/integer_spec.rb +50 -64
  329. data/spec/mongoid/extensions/range_spec.rb +255 -54
  330. data/spec/mongoid/extensions/regexp_spec.rb +58 -33
  331. data/spec/mongoid/extensions/set_spec.rb +106 -0
  332. data/spec/mongoid/extensions/string_spec.rb +53 -25
  333. data/spec/mongoid/extensions/symbol_spec.rb +18 -25
  334. data/spec/mongoid/extensions/time_spec.rb +639 -106
  335. data/spec/mongoid/extensions/time_with_zone_spec.rb +24 -83
  336. data/spec/mongoid/factory_spec.rb +61 -1
  337. data/spec/mongoid/fields/localized_spec.rb +80 -37
  338. data/spec/mongoid/fields_spec.rb +500 -84
  339. data/spec/mongoid/findable_spec.rb +450 -58
  340. data/spec/mongoid/indexable/specification_spec.rb +2 -2
  341. data/spec/mongoid/indexable_spec.rb +55 -30
  342. data/spec/mongoid/interceptable_spec.rb +599 -8
  343. data/spec/mongoid/interceptable_spec_models.rb +235 -4
  344. data/spec/mongoid/matcher/extract_attribute_spec.rb +1 -5
  345. data/spec/mongoid/mongoizable_spec.rb +285 -0
  346. data/spec/mongoid/persistable/creatable_spec.rb +2 -2
  347. data/spec/mongoid/persistable/deletable_spec.rb +28 -8
  348. data/spec/mongoid/persistable/destroyable_spec.rb +28 -8
  349. data/spec/mongoid/persistable/incrementable_spec.rb +37 -0
  350. data/spec/mongoid/persistable/logical_spec.rb +37 -0
  351. data/spec/mongoid/persistable/poppable_spec.rb +36 -0
  352. data/spec/mongoid/persistable/pullable_spec.rb +72 -0
  353. data/spec/mongoid/persistable/pushable_spec.rb +72 -0
  354. data/spec/mongoid/persistable/renamable_spec.rb +36 -0
  355. data/spec/mongoid/persistable/savable_spec.rb +96 -0
  356. data/spec/mongoid/persistable/settable_spec.rb +37 -0
  357. data/spec/mongoid/persistable/unsettable_spec.rb +36 -0
  358. data/spec/mongoid/persistable/updatable_spec.rb +20 -28
  359. data/spec/mongoid/persistable/upsertable_spec.rb +89 -1
  360. data/spec/mongoid/persistence_context_spec.rb +31 -57
  361. data/spec/mongoid/query_cache_middleware_spec.rb +0 -18
  362. data/spec/mongoid/query_cache_spec.rb +56 -215
  363. data/spec/mongoid/reloadable_spec.rb +83 -6
  364. data/spec/mongoid/scopable_spec.rb +91 -1
  365. data/spec/mongoid/serializable_spec.rb +9 -30
  366. data/spec/mongoid/shardable_spec.rb +4 -4
  367. data/spec/mongoid/stateful_spec.rb +150 -8
  368. data/spec/mongoid/tasks/database_rake_spec.rb +74 -0
  369. data/spec/mongoid/tasks/database_spec.rb +127 -0
  370. data/spec/mongoid/timestamps_spec.rb +392 -4
  371. data/spec/mongoid/timestamps_spec_models.rb +67 -0
  372. data/spec/mongoid/touchable_spec.rb +390 -2
  373. data/spec/mongoid/touchable_spec_models.rb +14 -8
  374. data/spec/mongoid/traversable_spec.rb +13 -35
  375. data/spec/mongoid/validatable/presence_spec.rb +1 -1
  376. data/spec/mongoid/validatable/uniqueness_spec.rb +58 -31
  377. data/spec/mongoid/warnings_spec.rb +35 -0
  378. data/spec/mongoid_spec.rb +34 -16
  379. data/spec/rails/controller_extension/controller_runtime_spec.rb +2 -2
  380. data/spec/rails/mongoid_spec.rb +4 -16
  381. data/spec/spec_helper.rb +5 -0
  382. data/spec/support/constraints.rb +24 -0
  383. data/spec/support/immutable_ids.rb +118 -0
  384. data/spec/support/macros.rb +78 -0
  385. data/spec/support/models/artist.rb +0 -1
  386. data/spec/support/models/augmentation.rb +12 -0
  387. data/spec/support/models/band.rb +4 -0
  388. data/spec/support/models/book.rb +1 -0
  389. data/spec/support/models/building.rb +2 -0
  390. data/spec/support/models/catalog.rb +24 -0
  391. data/spec/support/models/circus.rb +3 -0
  392. data/spec/support/models/cover.rb +10 -0
  393. data/spec/support/models/fanatic.rb +8 -0
  394. data/spec/support/models/implant.rb +9 -0
  395. data/spec/support/models/label.rb +2 -0
  396. data/spec/support/models/passport.rb +9 -0
  397. data/spec/support/models/person.rb +2 -0
  398. data/spec/support/models/player.rb +2 -0
  399. data/spec/support/models/powerup.rb +12 -0
  400. data/spec/support/models/product.rb +1 -0
  401. data/spec/support/models/purse.rb +9 -0
  402. data/spec/support/models/registry.rb +1 -0
  403. data/spec/support/models/school.rb +14 -0
  404. data/spec/support/models/shield.rb +18 -0
  405. data/spec/support/models/student.rb +14 -0
  406. data/spec/support/models/weapon.rb +12 -0
  407. data.tar.gz.sig +0 -0
  408. metadata +93 -15
  409. metadata.gz.sig +0 -0
  410. data/lib/mongoid/errors/eager_load.rb +0 -23
  411. data/lib/mongoid/errors/invalid_value.rb +0 -17
  412. data/spec/mongoid/criteria/queryable/extensions/bignum_spec.rb +0 -60
  413. data/spec/mongoid/criteria/queryable/extensions/fixnum_spec.rb +0 -60
  414. 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