mongoid 7.5.1 → 8.1.4

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