mongoid 7.4.3 → 8.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +3 -3
  4. data/Rakefile +25 -0
  5. data/lib/config/locales/en.yml +52 -28
  6. data/lib/mongoid/association/accessors.rb +38 -9
  7. data/lib/mongoid/association/bindable.rb +50 -2
  8. data/lib/mongoid/association/builders.rb +4 -2
  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 +33 -10
  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/proxy.rb +2 -2
  15. data/lib/mongoid/association/embedded/embedded_in.rb +3 -2
  16. data/lib/mongoid/association/embedded/embeds_many/binding.rb +1 -0
  17. data/lib/mongoid/association/embedded/embeds_many/buildable.rb +1 -1
  18. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +65 -41
  19. data/lib/mongoid/association/embedded/embeds_many.rb +2 -2
  20. data/lib/mongoid/association/embedded/embeds_one/buildable.rb +18 -4
  21. data/lib/mongoid/association/embedded/embeds_one/proxy.rb +23 -4
  22. data/lib/mongoid/association/embedded/embeds_one.rb +3 -3
  23. data/lib/mongoid/association/macros.rb +28 -1
  24. data/lib/mongoid/association/many.rb +11 -7
  25. data/lib/mongoid/association/nested/many.rb +5 -4
  26. data/lib/mongoid/association/nested/nested_buildable.rb +4 -4
  27. data/lib/mongoid/association/nested/one.rb +5 -5
  28. data/lib/mongoid/association/one.rb +2 -2
  29. data/lib/mongoid/association/options.rb +9 -9
  30. data/lib/mongoid/association/proxy.rb +14 -3
  31. data/lib/mongoid/association/referenced/auto_save.rb +4 -3
  32. data/lib/mongoid/association/referenced/belongs_to/binding.rb +1 -0
  33. data/lib/mongoid/association/referenced/belongs_to/buildable.rb +1 -1
  34. data/lib/mongoid/association/referenced/belongs_to/proxy.rb +5 -6
  35. data/lib/mongoid/association/referenced/belongs_to.rb +2 -2
  36. data/lib/mongoid/association/referenced/counter_cache.rb +10 -10
  37. data/lib/mongoid/association/referenced/eager.rb +2 -2
  38. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +70 -13
  39. data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +6 -3
  40. data/lib/mongoid/association/referenced/has_many/enumerable.rb +20 -24
  41. data/lib/mongoid/association/referenced/has_many/proxy.rb +26 -16
  42. data/lib/mongoid/association/referenced/has_many.rb +3 -3
  43. data/lib/mongoid/association/referenced/has_one/buildable.rb +1 -1
  44. data/lib/mongoid/association/referenced/has_one/nested_builder.rb +5 -5
  45. data/lib/mongoid/association/referenced/has_one/proxy.rb +9 -12
  46. data/lib/mongoid/association/referenced/has_one.rb +3 -3
  47. data/lib/mongoid/association/referenced/syncable.rb +4 -4
  48. data/lib/mongoid/association/reflections.rb +2 -2
  49. data/lib/mongoid/association/relatable.rb +44 -10
  50. data/lib/mongoid/association.rb +5 -5
  51. data/lib/mongoid/atomic/modifiers.rb +2 -2
  52. data/lib/mongoid/atomic.rb +7 -0
  53. data/lib/mongoid/attributes/dynamic.rb +3 -3
  54. data/lib/mongoid/attributes/nested.rb +5 -5
  55. data/lib/mongoid/attributes/processing.rb +37 -6
  56. data/lib/mongoid/attributes/projector.rb +1 -1
  57. data/lib/mongoid/attributes/readonly.rb +2 -2
  58. data/lib/mongoid/attributes.rb +43 -40
  59. data/lib/mongoid/cacheable.rb +2 -2
  60. data/lib/mongoid/changeable.rb +43 -10
  61. data/lib/mongoid/clients/options.rb +5 -1
  62. data/lib/mongoid/clients/sessions.rb +2 -14
  63. data/lib/mongoid/clients/validators/storage.rb +3 -3
  64. data/lib/mongoid/config/options.rb +3 -0
  65. data/lib/mongoid/config/validators/client.rb +6 -6
  66. data/lib/mongoid/config.rb +62 -17
  67. data/lib/mongoid/contextual/aggregable/memory.rb +24 -16
  68. data/lib/mongoid/contextual/aggregable/mongo.rb +5 -5
  69. data/lib/mongoid/contextual/aggregable/none.rb +1 -1
  70. data/lib/mongoid/contextual/atomic.rb +1 -1
  71. data/lib/mongoid/contextual/geo_near.rb +7 -7
  72. data/lib/mongoid/contextual/map_reduce.rb +2 -2
  73. data/lib/mongoid/contextual/memory.rb +180 -21
  74. data/lib/mongoid/contextual/mongo.rb +260 -217
  75. data/lib/mongoid/contextual/none.rb +67 -5
  76. data/lib/mongoid/contextual/queryable.rb +1 -1
  77. data/lib/mongoid/contextual.rb +2 -2
  78. data/lib/mongoid/copyable.rb +32 -8
  79. data/lib/mongoid/criteria/findable.rb +7 -4
  80. data/lib/mongoid/criteria/includable.rb +24 -20
  81. data/lib/mongoid/criteria/marshalable.rb +10 -2
  82. data/lib/mongoid/criteria/permission.rb +1 -1
  83. data/lib/mongoid/criteria/queryable/aggregable.rb +2 -2
  84. data/lib/mongoid/criteria/queryable/extensions/array.rb +3 -14
  85. data/lib/mongoid/criteria/queryable/extensions/big_decimal.rb +25 -4
  86. data/lib/mongoid/criteria/queryable/extensions/boolean.rb +2 -2
  87. data/lib/mongoid/criteria/queryable/extensions/date.rb +6 -1
  88. data/lib/mongoid/criteria/queryable/extensions/date_time.rb +6 -1
  89. data/lib/mongoid/criteria/queryable/extensions/hash.rb +1 -15
  90. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -9
  91. data/lib/mongoid/criteria/queryable/extensions/object.rb +2 -1
  92. data/lib/mongoid/criteria/queryable/extensions/range.rb +13 -5
  93. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +3 -3
  94. data/lib/mongoid/criteria/queryable/extensions/set.rb +1 -1
  95. data/lib/mongoid/criteria/queryable/extensions/string.rb +4 -14
  96. data/lib/mongoid/criteria/queryable/extensions/symbol.rb +4 -12
  97. data/lib/mongoid/criteria/queryable/extensions/time.rb +6 -1
  98. data/lib/mongoid/criteria/queryable/extensions/time_with_zone.rb +6 -1
  99. data/lib/mongoid/criteria/queryable/key.rb +3 -3
  100. data/lib/mongoid/criteria/queryable/mergeable.rb +21 -0
  101. data/lib/mongoid/criteria/queryable/optional.rb +5 -11
  102. data/lib/mongoid/criteria/queryable/options.rb +2 -2
  103. data/lib/mongoid/criteria/queryable/pipeline.rb +1 -1
  104. data/lib/mongoid/criteria/queryable/selectable.rb +31 -37
  105. data/lib/mongoid/criteria/queryable/selector.rb +93 -8
  106. data/lib/mongoid/criteria/queryable/smash.rb +40 -7
  107. data/lib/mongoid/criteria/queryable/storable.rb +1 -1
  108. data/lib/mongoid/criteria/queryable.rb +12 -7
  109. data/lib/mongoid/criteria/scopable.rb +2 -2
  110. data/lib/mongoid/criteria/translator.rb +45 -0
  111. data/lib/mongoid/criteria.rb +16 -35
  112. data/lib/mongoid/deprecable.rb +37 -0
  113. data/lib/mongoid/deprecation.rb +25 -0
  114. data/lib/mongoid/document.rb +135 -34
  115. data/lib/mongoid/equality.rb +8 -8
  116. data/lib/mongoid/errors/document_not_found.rb +33 -12
  117. data/lib/mongoid/errors/invalid_config_option.rb +1 -1
  118. data/lib/mongoid/errors/invalid_dependent_strategy.rb +1 -1
  119. data/lib/mongoid/errors/invalid_dot_dollar_assignment.rb +23 -0
  120. data/lib/mongoid/errors/invalid_field.rb +6 -2
  121. data/lib/mongoid/errors/invalid_field_type.rb +26 -0
  122. data/lib/mongoid/errors/invalid_relation.rb +1 -1
  123. data/lib/mongoid/errors/invalid_relation_option.rb +1 -1
  124. data/lib/mongoid/errors/invalid_session_use.rb +1 -1
  125. data/lib/mongoid/errors/invalid_storage_options.rb +1 -1
  126. data/lib/mongoid/errors/mongoid_error.rb +3 -3
  127. data/lib/mongoid/errors/nested_attributes_metadata_not_found.rb +1 -1
  128. data/lib/mongoid/errors/no_client_database.rb +1 -1
  129. data/lib/mongoid/errors/no_client_hosts.rb +1 -1
  130. data/lib/mongoid/errors/readonly_attribute.rb +1 -1
  131. data/lib/mongoid/errors/too_many_nested_attribute_records.rb +1 -1
  132. data/lib/mongoid/errors/unknown_attribute.rb +1 -1
  133. data/lib/mongoid/errors.rb +2 -2
  134. data/lib/mongoid/extensions/array.rb +9 -7
  135. data/lib/mongoid/extensions/big_decimal.rb +33 -10
  136. data/lib/mongoid/extensions/binary.rb +42 -0
  137. data/lib/mongoid/extensions/boolean.rb +8 -2
  138. data/lib/mongoid/extensions/date.rb +26 -20
  139. data/lib/mongoid/extensions/date_time.rb +1 -1
  140. data/lib/mongoid/extensions/false_class.rb +1 -1
  141. data/lib/mongoid/extensions/float.rb +7 -4
  142. data/lib/mongoid/extensions/hash.rb +37 -8
  143. data/lib/mongoid/extensions/integer.rb +7 -4
  144. data/lib/mongoid/extensions/module.rb +1 -1
  145. data/lib/mongoid/extensions/object.rb +8 -6
  146. data/lib/mongoid/extensions/range.rb +41 -10
  147. data/lib/mongoid/extensions/regexp.rb +11 -4
  148. data/lib/mongoid/extensions/set.rb +11 -4
  149. data/lib/mongoid/extensions/string.rb +11 -22
  150. data/lib/mongoid/extensions/symbol.rb +4 -15
  151. data/lib/mongoid/extensions/time.rb +27 -16
  152. data/lib/mongoid/extensions/time_with_zone.rb +1 -2
  153. data/lib/mongoid/extensions/true_class.rb +1 -1
  154. data/lib/mongoid/extensions.rb +1 -0
  155. data/lib/mongoid/factory.rb +55 -7
  156. data/lib/mongoid/fields/foreign_key.rb +11 -4
  157. data/lib/mongoid/fields/localized.rb +9 -4
  158. data/lib/mongoid/fields/standard.rb +7 -7
  159. data/lib/mongoid/fields/validators/macro.rb +3 -9
  160. data/lib/mongoid/fields.rb +233 -40
  161. data/lib/mongoid/findable.rb +34 -13
  162. data/lib/mongoid/indexable/specification.rb +2 -2
  163. data/lib/mongoid/indexable/validators/options.rb +6 -2
  164. data/lib/mongoid/interceptable.rb +185 -16
  165. data/lib/mongoid/matchable.rb +1 -1
  166. data/lib/mongoid/matcher.rb +33 -13
  167. data/lib/mongoid/persistable/creatable.rb +18 -9
  168. data/lib/mongoid/persistable/deletable.rb +1 -1
  169. data/lib/mongoid/persistable/destroyable.rb +1 -1
  170. data/lib/mongoid/persistable/savable.rb +2 -2
  171. data/lib/mongoid/persistable/unsettable.rb +1 -1
  172. data/lib/mongoid/persistable/updatable.rb +19 -12
  173. data/lib/mongoid/persistable/upsertable.rb +1 -1
  174. data/lib/mongoid/persistable.rb +3 -3
  175. data/lib/mongoid/persistence_context.rb +63 -10
  176. data/lib/mongoid/query_cache.rb +8 -260
  177. data/lib/mongoid/railties/controller_runtime.rb +1 -1
  178. data/lib/mongoid/reloadable.rb +10 -8
  179. data/lib/mongoid/scopable.rb +26 -22
  180. data/lib/mongoid/selectable.rb +1 -2
  181. data/lib/mongoid/serializable.rb +10 -6
  182. data/lib/mongoid/shardable.rb +35 -11
  183. data/lib/mongoid/stateful.rb +35 -9
  184. data/lib/mongoid/tasks/database.rb +0 -2
  185. data/lib/mongoid/threaded/lifecycle.rb +5 -5
  186. data/lib/mongoid/threaded.rb +42 -12
  187. data/lib/mongoid/timestamps/created.rb +1 -1
  188. data/lib/mongoid/timestamps/updated.rb +2 -2
  189. data/lib/mongoid/touchable.rb +2 -3
  190. data/lib/mongoid/traversable.rb +5 -4
  191. data/lib/mongoid/validatable/localizable.rb +1 -1
  192. data/lib/mongoid/validatable/macros.rb +0 -2
  193. data/lib/mongoid/validatable/presence.rb +2 -2
  194. data/lib/mongoid/validatable/uniqueness.rb +9 -8
  195. data/lib/mongoid/validatable.rb +6 -6
  196. data/lib/mongoid/version.rb +1 -1
  197. data/lib/mongoid/warnings.rb +28 -0
  198. data/lib/mongoid.rb +2 -0
  199. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +4 -3
  200. data/spec/config/mongoid.yml +16 -0
  201. data/spec/integration/app_spec.rb +8 -12
  202. data/spec/integration/associations/belongs_to_spec.rb +18 -0
  203. data/spec/integration/associations/embedded_spec.rb +15 -0
  204. data/spec/integration/associations/embeds_many_spec.rb +15 -2
  205. data/spec/integration/associations/embeds_one_spec.rb +18 -0
  206. data/spec/integration/associations/foreign_key_spec.rb +9 -0
  207. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +21 -0
  208. data/spec/integration/associations/has_one_spec.rb +97 -1
  209. data/spec/integration/associations/scope_option_spec.rb +1 -1
  210. data/spec/integration/callbacks_models.rb +95 -1
  211. data/spec/integration/callbacks_spec.rb +246 -4
  212. data/spec/integration/criteria/range_spec.rb +95 -1
  213. data/spec/integration/discriminator_key_spec.rb +115 -76
  214. data/spec/integration/dots_and_dollars_spec.rb +277 -0
  215. data/spec/integration/i18n_fallbacks_spec.rb +1 -17
  216. data/spec/integration/matcher_examples_spec.rb +20 -13
  217. data/spec/integration/matcher_operator_data/type_decimal.yml +3 -2
  218. data/spec/integration/matcher_operator_spec.rb +3 -5
  219. data/spec/integration/persistence/range_field_spec.rb +350 -0
  220. data/spec/mongoid/association/counter_cache_spec.rb +1 -1
  221. data/spec/mongoid/association/depending_spec.rb +9 -9
  222. data/spec/mongoid/association/eager_spec.rb +2 -1
  223. data/spec/mongoid/association/embedded/embedded_in/binding_spec.rb +2 -1
  224. data/spec/mongoid/association/embedded/embedded_in/buildable_spec.rb +54 -0
  225. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +69 -9
  226. data/spec/mongoid/association/embedded/embeds_many/buildable_spec.rb +112 -0
  227. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +235 -40
  228. data/spec/mongoid/association/embedded/embeds_many_models.rb +36 -0
  229. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +12 -0
  230. data/spec/mongoid/association/embedded/embeds_many_spec.rb +68 -0
  231. data/spec/mongoid/association/embedded/embeds_one/buildable_spec.rb +25 -0
  232. data/spec/mongoid/association/embedded/embeds_one_models.rb +19 -0
  233. data/spec/mongoid/association/embedded/embeds_one_spec.rb +28 -0
  234. data/spec/mongoid/association/referenced/belongs_to/binding_spec.rb +2 -1
  235. data/spec/mongoid/association/referenced/belongs_to/buildable_spec.rb +54 -0
  236. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +15 -0
  237. data/spec/mongoid/association/referenced/belongs_to_models.rb +11 -0
  238. data/spec/mongoid/association/referenced/belongs_to_spec.rb +2 -2
  239. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +202 -201
  240. data/spec/mongoid/association/referenced/has_and_belongs_to_many_models.rb +25 -0
  241. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +35 -2
  242. data/spec/mongoid/association/referenced/has_many/buildable_spec.rb +109 -0
  243. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +8 -8
  244. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +160 -119
  245. data/spec/mongoid/association/referenced/has_many_models.rb +3 -1
  246. data/spec/mongoid/association/referenced/has_many_spec.rb +25 -0
  247. data/spec/mongoid/association/referenced/has_one/buildable_spec.rb +2 -2
  248. data/spec/mongoid/association/referenced/has_one/proxy_spec.rb +107 -1
  249. data/spec/mongoid/association/referenced/has_one_models.rb +16 -0
  250. data/spec/mongoid/association/syncable_spec.rb +14 -0
  251. data/spec/mongoid/atomic/paths_spec.rb +0 -14
  252. data/spec/mongoid/attributes/nested_spec.rb +80 -11
  253. data/spec/mongoid/attributes/nested_spec_models.rb +48 -0
  254. data/spec/mongoid/attributes/projector_spec.rb +1 -5
  255. data/spec/mongoid/attributes_spec.rb +551 -27
  256. data/spec/mongoid/cacheable_spec.rb +3 -3
  257. data/spec/mongoid/changeable_spec.rb +130 -13
  258. data/spec/mongoid/clients/factory_spec.rb +23 -30
  259. data/spec/mongoid/clients/options_spec.rb +1 -0
  260. data/spec/mongoid/clients/sessions_spec.rb +0 -38
  261. data/spec/mongoid/clients_spec.rb +57 -2
  262. data/spec/mongoid/config_spec.rb +78 -18
  263. data/spec/mongoid/contextual/aggregable/memory_spec.rb +396 -158
  264. data/spec/mongoid/contextual/aggregable/memory_table.yml +88 -0
  265. data/spec/mongoid/contextual/aggregable/memory_table_spec.rb +62 -0
  266. data/spec/mongoid/contextual/map_reduce_spec.rb +2 -16
  267. data/spec/mongoid/contextual/memory_spec.rb +1336 -69
  268. data/spec/mongoid/contextual/mongo_spec.rb +1253 -247
  269. data/spec/mongoid/contextual/none_spec.rb +38 -0
  270. data/spec/mongoid/copyable_spec.rb +451 -1
  271. data/spec/mongoid/criteria/findable_spec.rb +86 -210
  272. data/spec/mongoid/criteria/includable_spec.rb +1492 -0
  273. data/spec/mongoid/criteria/includable_spec_models.rb +54 -0
  274. data/spec/mongoid/criteria/marshalable_spec.rb +18 -1
  275. data/spec/mongoid/criteria/queryable/extensions/array_spec.rb +7 -19
  276. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +134 -26
  277. data/spec/mongoid/criteria/queryable/extensions/date_spec.rb +11 -0
  278. data/spec/mongoid/criteria/queryable/extensions/date_time_spec.rb +11 -0
  279. data/spec/mongoid/criteria/queryable/extensions/hash_spec.rb +0 -15
  280. data/spec/mongoid/criteria/queryable/extensions/numeric_spec.rb +73 -7
  281. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +0 -59
  282. data/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb +0 -59
  283. data/spec/mongoid/criteria/queryable/extensions/time_spec.rb +11 -0
  284. data/spec/mongoid/criteria/queryable/extensions/time_with_zone_spec.rb +11 -0
  285. data/spec/mongoid/criteria/queryable/optional_spec.rb +15 -484
  286. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +50 -0
  287. data/spec/mongoid/criteria/queryable/selectable_spec.rb +289 -124
  288. data/spec/mongoid/criteria/queryable/selector_spec.rb +89 -4
  289. data/spec/mongoid/criteria/queryable/storable_spec.rb +72 -0
  290. data/spec/mongoid/criteria/translator_spec.rb +132 -0
  291. data/spec/mongoid/criteria_projection_spec.rb +0 -1
  292. data/spec/mongoid/criteria_spec.rb +475 -1199
  293. data/spec/mongoid/document_fields_spec.rb +173 -24
  294. data/spec/mongoid/document_spec.rb +32 -41
  295. data/spec/mongoid/errors/document_not_found_spec.rb +76 -0
  296. data/spec/mongoid/errors/invalid_field_spec.rb +1 -1
  297. data/spec/mongoid/errors/invalid_field_type_spec.rb +55 -0
  298. data/spec/mongoid/errors/mongoid_error_spec.rb +3 -1
  299. data/spec/mongoid/errors/no_environment_spec.rb +3 -3
  300. data/spec/mongoid/errors/too_many_nested_attribute_records_spec.rb +1 -1
  301. data/spec/mongoid/extensions/array_spec.rb +16 -2
  302. data/spec/mongoid/extensions/big_decimal_spec.rb +712 -212
  303. data/spec/mongoid/extensions/binary_spec.rb +44 -9
  304. data/spec/mongoid/extensions/boolean_spec.rb +68 -82
  305. data/spec/mongoid/extensions/date_class_mongoize_spec.rb +7 -3
  306. data/spec/mongoid/extensions/date_spec.rb +71 -1
  307. data/spec/mongoid/extensions/date_time_spec.rb +15 -9
  308. data/spec/mongoid/extensions/float_spec.rb +53 -74
  309. data/spec/mongoid/extensions/hash_spec.rb +33 -3
  310. data/spec/mongoid/extensions/integer_spec.rb +50 -64
  311. data/spec/mongoid/extensions/range_spec.rb +255 -54
  312. data/spec/mongoid/extensions/regexp_spec.rb +58 -33
  313. data/spec/mongoid/extensions/set_spec.rb +106 -0
  314. data/spec/mongoid/extensions/string_spec.rb +53 -25
  315. data/spec/mongoid/extensions/symbol_spec.rb +18 -25
  316. data/spec/mongoid/extensions/time_spec.rb +634 -66
  317. data/spec/mongoid/extensions/time_with_zone_spec.rb +17 -31
  318. data/spec/mongoid/factory_spec.rb +61 -1
  319. data/spec/mongoid/fields/localized_spec.rb +37 -12
  320. data/spec/mongoid/fields_spec.rb +364 -50
  321. data/spec/mongoid/findable_spec.rb +80 -15
  322. data/spec/mongoid/indexable/specification_spec.rb +2 -2
  323. data/spec/mongoid/indexable_spec.rb +39 -20
  324. data/spec/mongoid/interceptable_spec.rb +807 -27
  325. data/spec/mongoid/interceptable_spec_models.rb +235 -4
  326. data/spec/mongoid/matcher/extract_attribute_spec.rb +1 -5
  327. data/spec/mongoid/mongoizable_spec.rb +285 -0
  328. data/spec/mongoid/persistable/creatable_spec.rb +2 -2
  329. data/spec/mongoid/persistable/deletable_spec.rb +2 -2
  330. data/spec/mongoid/persistable/destroyable_spec.rb +2 -2
  331. data/spec/mongoid/persistable/upsertable_spec.rb +14 -0
  332. data/spec/mongoid/persistence_context_spec.rb +50 -1
  333. data/spec/mongoid/query_cache_middleware_spec.rb +0 -18
  334. data/spec/mongoid/query_cache_spec.rb +0 -154
  335. data/spec/mongoid/reloadable_spec.rb +59 -2
  336. data/spec/mongoid/scopable_spec.rb +54 -16
  337. data/spec/mongoid/shardable_models.rb +14 -0
  338. data/spec/mongoid/shardable_spec.rb +157 -51
  339. data/spec/mongoid/stateful_spec.rb +28 -0
  340. data/spec/mongoid/timestamps_spec.rb +390 -0
  341. data/spec/mongoid/timestamps_spec_models.rb +67 -0
  342. data/spec/mongoid/touchable_spec.rb +116 -0
  343. data/spec/mongoid/touchable_spec_models.rb +12 -8
  344. data/spec/mongoid/traversable_spec.rb +4 -11
  345. data/spec/mongoid/validatable/presence_spec.rb +1 -1
  346. data/spec/mongoid/validatable/uniqueness_spec.rb +59 -31
  347. data/spec/mongoid/warnings_spec.rb +35 -0
  348. data/spec/mongoid_spec.rb +1 -1
  349. data/spec/rails/controller_extension/controller_runtime_spec.rb +2 -2
  350. data/spec/rails/mongoid_spec.rb +4 -16
  351. data/spec/shared/lib/mrss/docker_runner.rb +7 -0
  352. data/spec/shared/lib/mrss/lite_constraints.rb +10 -2
  353. data/spec/shared/lib/mrss/server_version_registry.rb +16 -23
  354. data/spec/shared/lib/mrss/utils.rb +28 -6
  355. data/spec/shared/share/Dockerfile.erb +36 -40
  356. data/spec/shared/shlib/server.sh +32 -8
  357. data/spec/shared/shlib/set_env.sh +4 -4
  358. data/spec/support/constraints.rb +24 -0
  359. data/spec/support/macros.rb +46 -0
  360. data/spec/support/models/augmentation.rb +12 -0
  361. data/spec/support/models/band.rb +3 -0
  362. data/spec/support/models/catalog.rb +24 -0
  363. data/spec/support/models/circus.rb +3 -0
  364. data/spec/support/models/code.rb +2 -0
  365. data/spec/support/models/fanatic.rb +8 -0
  366. data/spec/support/models/implant.rb +9 -0
  367. data/spec/support/models/label.rb +2 -0
  368. data/spec/support/models/passport.rb +9 -0
  369. data/spec/support/models/person.rb +2 -0
  370. data/spec/support/models/player.rb +2 -0
  371. data/spec/support/models/powerup.rb +12 -0
  372. data/spec/support/models/purse.rb +9 -0
  373. data/spec/support/models/registry.rb +1 -0
  374. data/spec/support/models/school.rb +14 -0
  375. data/spec/support/models/shield.rb +18 -0
  376. data/spec/support/models/student.rb +14 -0
  377. data/spec/support/models/weapon.rb +12 -0
  378. data.tar.gz.sig +4 -1
  379. metadata +682 -635
  380. metadata.gz.sig +0 -0
  381. data/lib/mongoid/errors/eager_load.rb +0 -23
  382. data/lib/mongoid/errors/invalid_value.rb +0 -17
  383. data/spec/mongoid/errors/eager_load_spec.rb +0 -31
@@ -17,6 +17,8 @@ module Mongoid
17
17
  include Association::EagerLoadable
18
18
  include Queryable
19
19
 
20
+ Mongoid.deprecate(self, :geo_near)
21
+
20
22
  # Options constant.
21
23
  OPTIONS = [ :hint,
22
24
  :limit,
@@ -35,16 +37,6 @@ module Mongoid
35
37
  # @attribute [r] view The Mongo collection view.
36
38
  attr_reader :view
37
39
 
38
- # Is the context cached?
39
- #
40
- # @example Is the context cached?
41
- # context.cached?
42
- #
43
- # @return [ true, false ] If the context is cached.
44
- def cached?
45
- !!@cache
46
- end
47
-
48
40
  # Get the number of documents matching the query.
49
41
  #
50
42
  # @example Get the number of matching documents.
@@ -64,7 +56,12 @@ module Mongoid
64
56
  # @return [ Integer ] The number of matches.
65
57
  def count(options = {}, &block)
66
58
  return super(&block) if block_given?
67
- try_cache(:count) { view.count_documents(options) }
59
+
60
+ if valid_for_count_documents?
61
+ view.count_documents(options)
62
+ else
63
+ view.count(options)
64
+ end
68
65
  end
69
66
 
70
67
  # Get the estimated number of documents matching the query.
@@ -83,7 +80,7 @@ module Mongoid
83
80
  unless self.criteria.selector.empty?
84
81
  raise Mongoid::Errors::InvalidEstimatedCountCriteria.new(self.klass)
85
82
  end
86
- try_cache(:estimated_count) { view.estimated_document_count(options) }
83
+ view.estimated_document_count(options)
87
84
  end
88
85
 
89
86
  # Delete all documents in the database that match the selector.
@@ -117,7 +114,7 @@ module Mongoid
117
114
  # @example Get the distinct values.
118
115
  # context.distinct(:name)
119
116
  #
120
- # @param [ String, Symbol ] field The name of the field.
117
+ # @param [ String | Symbol ] field The name of the field.
121
118
  #
122
119
  # @return [ Array<Object> ] The distinct values for the field.
123
120
  def distinct(field)
@@ -151,7 +148,6 @@ module Mongoid
151
148
  documents_for_iteration.each do |doc|
152
149
  yield_document(doc, &block)
153
150
  end
154
- @cache_loaded = true
155
151
  self
156
152
  else
157
153
  to_enum
@@ -164,17 +160,11 @@ module Mongoid
164
160
  # context.exists?
165
161
  #
166
162
  # @note We don't use count here since Mongo does not use counted
167
- # b-tree indexes, unless a count is already cached then that is
168
- # used to determine the value.
163
+ # b-tree indexes.
169
164
  #
170
- # @return [ true, false ] If the count is more than zero.
165
+ # @return [ true | false ] If the count is more than zero.
171
166
  def exists?
172
- return !documents.empty? if cached? && cache_loaded?
173
- return @count > 0 if instance_variable_defined?(:@count)
174
-
175
- try_cache(:exists) do
176
- !!(view.projection(_id: 1).limit(1).first)
177
- end
167
+ !!(view.projection(_id: 1).limit(1).first)
178
168
  end
179
169
 
180
170
  # Run an explain on the criteria.
@@ -196,9 +186,9 @@ module Mongoid
196
186
  # @param [ Hash ] update The updates.
197
187
  # @param [ Hash ] options The command options.
198
188
  #
199
- # @option options [ :before, :after ] :return_document Return the updated document
189
+ # @option options [ :before | :after ] :return_document Return the updated document
200
190
  # from before or after update.
201
- # @option options [ true, false ] :upsert Create the document if it doesn't exist.
191
+ # @option options [ true | false ] :upsert Create the document if it doesn't exist.
202
192
  #
203
193
  # @return [ Document ] The result of the command.
204
194
  def find_one_and_update(update, options = {})
@@ -216,9 +206,9 @@ module Mongoid
216
206
  # @param [ Hash ] replacement The replacement.
217
207
  # @param [ Hash ] options The command options.
218
208
  #
219
- # @option options [ :before, :after ] :return_document Return the updated document
209
+ # @option options [ :before | :after ] :return_document Return the updated document
220
210
  # from before or after update.
221
- # @option options [ true, false ] :upsert Create the document if it doesn't exist.
211
+ # @option options [ true | false ] :upsert Create the document if it doesn't exist.
222
212
  #
223
213
  # @return [ Document ] The result of the command.
224
214
  def find_one_and_replace(replacement, options = {})
@@ -248,29 +238,16 @@ module Mongoid
248
238
  # @note Automatically adding a sort on _id when no other sort is
249
239
  # defined on the criteria has the potential to cause bad performance issues.
250
240
  # If you experience unexpected poor performance when using #first or #last
251
- # and have no sort defined on the criteria, use the option { id_sort: :none }.
252
- # Be aware that #first/#last won't guarantee order in this case.
253
- #
254
- # @param [ Hash ] opts The options for the query returning the first document.
241
+ # and have no sort defined on the criteria, use #take instead.
242
+ # Be aware that #take won't guarantee order.
255
243
  #
256
- # @option opts [ :none ] :id_sort Don't apply a sort on _id if no other sort
257
- # is defined on the criteria.
244
+ # @param [ Integer ] limit The number of documents to return.
258
245
  #
259
246
  # @return [ Document ] The first document.
260
- def first(opts = {})
261
- return documents.first if cached? && cache_loaded?
262
- try_cache(:first) do
263
- if sort = view.sort || ({ _id: 1 } unless opts[:id_sort] == :none)
264
- if raw_doc = view.sort(sort).limit(1).first
265
- doc = Factory.from_db(klass, raw_doc, criteria)
266
- eager_load([doc]).first
267
- end
268
- else
269
- if raw_doc = view.limit(1).first
270
- doc = Factory.from_db(klass, raw_doc, criteria)
271
- eager_load([doc]).first
272
- end
273
- end
247
+ def first(limit = nil)
248
+ sort = view.sort || { _id: 1 }
249
+ if raw_docs = view.sort(sort).limit(limit || 1).to_a
250
+ process_raw_docs(raw_docs, limit)
274
251
  end
275
252
  end
276
253
  alias :one :first
@@ -279,7 +256,6 @@ module Mongoid
279
256
  #
280
257
  # @api private
281
258
  def find_first
282
- return documents.first if cached? && cache_loaded?
283
259
  if raw_doc = view.first
284
260
  doc = Factory.from_db(klass, raw_doc, criteria)
285
261
  eager_load([doc]).first
@@ -309,29 +285,6 @@ module Mongoid
309
285
  GeoNear.new(collection, criteria, coordinates)
310
286
  end
311
287
 
312
- # Invoke the block for each element of Contextual. Create a new array
313
- # containing the values returned by the block.
314
- #
315
- # If the symbol field name is passed instead of the block, additional
316
- # optimizations would be used.
317
- #
318
- # @example Map by some field.
319
- # context.map(:field1)
320
- #
321
- # @example Map with block.
322
- # context.map(&:field1)
323
- #
324
- # @param [ Symbol ] field The field name.
325
- #
326
- # @return [ Array ] The result of mapping.
327
- def map(field = nil, &block)
328
- if block_given?
329
- super(&block)
330
- else
331
- criteria.pluck(field)
332
- end
333
- end
334
-
335
288
  # Create the new Mongo context. This delegates operations to the
336
289
  # underlying driver.
337
290
  #
@@ -340,7 +293,7 @@ module Mongoid
340
293
  #
341
294
  # @param [ Criteria ] criteria The criteria.
342
295
  def initialize(criteria)
343
- @criteria, @klass, @cache = criteria, criteria.klass, criteria.options[:cache]
296
+ @criteria, @klass = criteria, criteria.klass
344
297
  @collection = @klass.collection
345
298
  criteria.send(:merge_type_selection)
346
299
  @view = collection.find(criteria.selector, session: _session)
@@ -357,32 +310,26 @@ module Mongoid
357
310
  # @note Automatically adding a sort on _id when no other sort is
358
311
  # defined on the criteria has the potential to cause bad performance issues.
359
312
  # If you experience unexpected poor performance when using #first or #last
360
- # and have no sort defined on the criteria, use the option { id_sort: :none }.
361
- # Be aware that #first/#last won't guarantee order in this case.
362
- #
363
- # @param [ Hash ] opts The options for the query returning the first document.
364
- #
365
- # @option opts [ :none ] :id_sort Don't apply a sort on _id if no other sort
366
- # is defined on the criteria.
367
- def last(opts = {})
368
- try_cache(:last) do
369
- with_inverse_sorting(opts) do
370
- if raw_doc = view.limit(1).first
371
- doc = Factory.from_db(klass, raw_doc, criteria)
372
- eager_load([doc]).first
373
- end
374
- end
375
- end
313
+ # and have no sort defined on the criteria, use #take instead.
314
+ # Be aware that #take won't guarantee order.
315
+ #
316
+ # @param [ Integer ] limit The number of documents to return.
317
+ #
318
+ # @return [ Document ] The last document.
319
+ def last(limit = nil)
320
+ raw_docs = view.sort(inverse_sorting).limit(limit || 1).to_a.reverse
321
+ process_raw_docs(raw_docs, limit)
376
322
  end
377
323
 
378
- # Get's the number of documents matching the query selector.
324
+ # Returns the number of documents in the database matching
325
+ # the query selector.
379
326
  #
380
327
  # @example Get the length.
381
328
  # context.length
382
329
  #
383
330
  # @return [ Integer ] The number of documents.
384
331
  def length
385
- @length ||= self.count
332
+ self.count
386
333
  end
387
334
  alias :size :length
388
335
 
@@ -398,6 +345,44 @@ module Mongoid
398
345
  @view = view.limit(value) and self
399
346
  end
400
347
 
348
+ # Take the given number of documents from the database.
349
+ #
350
+ # @example Take 10 documents
351
+ # context.take(10)
352
+ #
353
+ # @param [ Integer | nil ] limit The number of documents to return or nil.
354
+ #
355
+ # @return [ Document | Array<Document> ] The list of documents, or one
356
+ # document if no value was given.
357
+ def take(limit = nil)
358
+ if limit
359
+ limit(limit).to_a
360
+ else
361
+ # Do to_a first so that the Mongo#first method is not used and the
362
+ # result is not sorted.
363
+ limit(1).to_a.first
364
+ end
365
+ end
366
+
367
+ # Take one document from the database and raise an error if there are none.
368
+ #
369
+ # @example Take a document
370
+ # context.take!
371
+ #
372
+ # @return [ Document ] The document.
373
+ #
374
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
375
+ # documents to take.
376
+ def take!
377
+ # Do to_a first so that the Mongo#first method is not used and the
378
+ # result is not sorted.
379
+ if fst = limit(1).to_a.first
380
+ fst
381
+ else
382
+ raise Errors::DocumentNotFound.new(klass, nil, nil)
383
+ end
384
+ end
385
+
401
386
  # Initiate a map/reduce operation from the context.
402
387
  #
403
388
  # @example Initiate a map/reduce.
@@ -417,12 +402,9 @@ module Mongoid
417
402
  # @example Pluck a field.
418
403
  # context.pluck(:_id)
419
404
  #
420
- # @note This method will return the raw db values - it performs no custom
421
- # serialization.
405
+ # @param [ String | Symbol ] *fields Field(s) to pluck.
422
406
  #
423
- # @param [ String, Symbol, Array ] fields Fields to pluck.
424
- #
425
- # @return [ Array<Object, Array> ] The plucked values.
407
+ # @return [ Array<Object> | Array<Array<Object>> ] The plucked values.
426
408
  def pluck(*fields)
427
409
  # Multiple fields can map to the same field name. For example, plucking
428
410
  # a field and its _translations field map to the same field in the database.
@@ -452,6 +434,87 @@ module Mongoid
452
434
  end
453
435
  end
454
436
 
437
+ # Pick the single field values from the database.
438
+ #
439
+ # @example Pick a field.
440
+ # context.pick(:_id)
441
+ #
442
+ # @param [ String | Symbol ] *fields Field(s) to pick.
443
+ #
444
+ # @return [ Object | Array<Object> ] The picked values.
445
+ def pick(*fields)
446
+ limit(1).pluck(*fields).first
447
+ end
448
+
449
+ # Get a hash of counts for the values of a single field. For example,
450
+ # if the following documents were in the database:
451
+ #
452
+ # { _id: 1, age: 21 }
453
+ # { _id: 2, age: 21 }
454
+ # { _id: 3, age: 22 }
455
+ #
456
+ # Model.tally("age")
457
+ #
458
+ # would yield the following result:
459
+ #
460
+ # { 21 => 2, 22 => 1 }
461
+ #
462
+ # When tallying a field inside an array or embeds_many association:
463
+ #
464
+ # { _id: 1, array: [ { x: 1 }, { x: 2 } ] }
465
+ # { _id: 2, array: [ { x: 1 }, { x: 2 } ] }
466
+ # { _id: 3, array: [ { x: 1 }, { x: 3 } ] }
467
+ #
468
+ # Model.tally("array.x")
469
+ #
470
+ # The keys of the resulting hash are arrays:
471
+ #
472
+ # { [ 1, 2 ] => 2, [ 1, 3 ] => 1 }
473
+ #
474
+ # Note that if tallying an element in an array of hashes, and the key
475
+ # doesn't exist in some of the hashes, tally will not include those
476
+ # nil keys in the resulting hash:
477
+ #
478
+ # { _id: 1, array: [ { x: 1 }, { x: 2 }, { y: 3 } ] }
479
+ #
480
+ # Model.tally("array.x")
481
+ # # => { [ 1, 2 ] => 1 }
482
+ #
483
+ # @param [ String | Symbol ] field The field name.
484
+ #
485
+ # @return [ Hash ] The hash of counts.
486
+ def tally(field)
487
+ name = klass.cleanse_localized_field_names(field)
488
+
489
+ fld = klass.traverse_association_tree(name)
490
+ pipeline = [ { "$group" => { _id: "$#{name}", counts: { "$sum": 1 } } } ]
491
+ pipeline.unshift("$match" => view.filter) unless view.filter.blank?
492
+
493
+ collection.aggregate(pipeline).reduce({}) do |tallies, doc|
494
+ is_translation = "#{name}_translations" == field.to_s
495
+ val = doc["_id"]
496
+
497
+ key = if val.is_a?(Array)
498
+ val.map do |v|
499
+ demongoize_with_field(fld, v, is_translation)
500
+ end
501
+ else
502
+ demongoize_with_field(fld, val, is_translation)
503
+ end
504
+
505
+ # The only time where a key will already exist in the tallies hash
506
+ # is when the values are stored differently in the database, but
507
+ # demongoize to the same value. A good example of when this happens
508
+ # is when using localized fields. While the server query won't group
509
+ # together hashes that have other values in different languages, the
510
+ # demongoized value is just the translation in the current locale,
511
+ # which can be the same across multiple of those unequal hashes.
512
+ tallies[key] ||= 0
513
+ tallies[key] += doc["counts"]
514
+ tallies
515
+ end
516
+ end
517
+
455
518
  # Skips the provided number of documents.
456
519
  #
457
520
  # @example Skip the documents.
@@ -518,22 +581,6 @@ module Mongoid
518
581
 
519
582
  private
520
583
 
521
- # yield the block given or return the cached value
522
- #
523
- # @param [ String, Symbol ] key The instance variable name
524
- #
525
- # @return the result of the block
526
- def try_cache(key, &block)
527
- unless cached?
528
- yield
529
- else
530
- unless ret = instance_variable_get("@#{key}")
531
- instance_variable_set("@#{key}", ret = yield)
532
- end
533
- ret
534
- end
535
- end
536
-
537
584
  # Update the documents for the provided method.
538
585
  #
539
586
  # @api private
@@ -544,7 +591,7 @@ module Mongoid
544
591
  # @param [ Hash ] attributes The updates.
545
592
  # @param [ Symbol ] method The method to use.
546
593
  #
547
- # @return [ true, false ] If the update succeeded.
594
+ # @return [ true | false ] If the update succeeded.
548
595
  def update_documents(attributes, method = :update_one, opts = {})
549
596
  return false unless attributes
550
597
  attributes = Hash[attributes.map { |k, v| [klass.database_field_name(k.to_s), v] }]
@@ -594,55 +641,9 @@ module Mongoid
594
641
  # Map the inverse sort symbols to the correct MongoDB values.
595
642
  #
596
643
  # @api private
597
- #
598
- # @example Apply the inverse sorting params to the given block
599
- # context.with_inverse_sorting
600
- def with_inverse_sorting(opts = {})
601
- begin
602
- if sort = criteria.options[:sort] || ( { _id: 1 } unless opts[:id_sort] == :none )
603
- @view = view.sort(Hash[sort.map{|k, v| [k, -1*v]}])
604
- end
605
- yield
606
- ensure
607
- apply_option(:sort)
608
- end
609
- end
610
-
611
- # Is the cache able to be added to?
612
- #
613
- # @api private
614
- #
615
- # @example Is the context cacheable?
616
- # context.cacheable?
617
- #
618
- # @return [ true, false ] If caching, and the cache isn't loaded.
619
- def cacheable?
620
- cached? && !cache_loaded?
621
- end
622
-
623
- # Is the cache fully loaded? Will be true if caching after one full
624
- # iteration.
625
- #
626
- # @api private
627
- #
628
- # @example Is the cache loaded?
629
- # context.cache_loaded?
630
- #
631
- # @return [ true, false ] If the cache is loaded.
632
- def cache_loaded?
633
- !!@cache_loaded
634
- end
635
-
636
- # Get the documents for cached queries.
637
- #
638
- # @api private
639
- #
640
- # @example Get the cached documents.
641
- # context.documents
642
- #
643
- # @return [ Array<Document> ] The documents.
644
- def documents
645
- @documents ||= []
644
+ def inverse_sorting
645
+ sort = view.sort || { _id: 1 }
646
+ Hash[sort.map{|k, v| [k, -1*v]}]
646
647
  end
647
648
 
648
649
  # Get the documents the context should iterate. This follows 3 rules:
@@ -658,9 +659,8 @@ module Mongoid
658
659
  # @example Get the documents for iteration.
659
660
  # context.documents_for_iteration
660
661
  #
661
- # @return [ Array<Document>, Mongo::Collection::View ] The docs to iterate.
662
+ # @return [ Array<Document> | Mongo::Collection::View ] The docs to iterate.
662
663
  def documents_for_iteration
663
- return documents if cached? && !documents.empty?
664
664
  return view unless eager_loadable?
665
665
  docs = view.map{ |doc| Factory.from_db(klass, doc, criteria) }
666
666
  eager_load(docs)
@@ -680,7 +680,6 @@ module Mongoid
680
680
  doc = document.respond_to?(:_id) ?
681
681
  document : Factory.from_db(klass, document, criteria)
682
682
  yield(doc)
683
- documents.push(doc) if cacheable?
684
683
  end
685
684
 
686
685
  private
@@ -693,6 +692,26 @@ module Mongoid
693
692
  collection.write_concern.nil? || collection.write_concern.acknowledged?
694
693
  end
695
694
 
695
+ # Fetch the element from the given hash and demongoize it using the
696
+ # given field. If the obj is an array, map over it and call this method
697
+ # on all of its elements.
698
+ #
699
+ # @param [ Hash | Array<Hash> ] obj The hash or array of hashes to fetch from.
700
+ # @param [ String ] meth The key to fetch from the hash.
701
+ # @param [ Field ] field The field to use for demongoization.
702
+ #
703
+ # @return [ Object ] The demongoized value.
704
+ #
705
+ # @api private
706
+ def fetch_and_demongoize(obj, meth, field)
707
+ if obj.is_a?(Array)
708
+ obj.map { |doc| fetch_and_demongoize(doc, meth, field) }
709
+ else
710
+ res = obj.try(:fetch, meth, nil)
711
+ field ? field.demongoize(res) : res.class.demongoize(res)
712
+ end
713
+ end
714
+
696
715
  # Extracts the value for the given field name from the given attribute
697
716
  # hash.
698
717
  #
@@ -701,24 +720,18 @@ module Mongoid
701
720
  #
702
721
  # @param [ Object ] The value for the given field name
703
722
  def extract_value(attrs, field_name)
704
- def fetch_and_demongoize(d, meth, klass)
705
- res = d.try(:fetch, meth, nil)
706
- if field = klass.fields[meth]
707
- field.demongoize(res)
708
- else
709
- res.class.demongoize(res)
710
- end
711
- end
723
+ i = 1
724
+ num_meths = field_name.count('.') + 1
725
+ curr = attrs.dup
712
726
 
713
- k = klass
714
- meths = field_name.split('.')
715
- meths.each_with_index.inject(attrs) do |curr, (meth, i)|
727
+ klass.traverse_association_tree(field_name) do |meth, obj, is_field|
728
+ field = obj if is_field
716
729
  is_translation = false
717
- if !k.fields.key?(meth) && !k.relations.key?(meth)
718
- if tr = meth.match(/(.*)_translations\z/)&.captures&.first
719
- is_translation = true
720
- meth = tr
721
- end
730
+ # If no association or field was found, check if the meth is an
731
+ # _translations field.
732
+ if obj.nil? & tr = meth.match(/(.*)_translations\z/)&.captures&.first
733
+ is_translation = true
734
+ meth = tr
722
735
  end
723
736
 
724
737
  # 1. If curr is an array fetch from all elements in the array.
@@ -731,31 +744,24 @@ module Mongoid
731
744
  # 3. If the meth is an _translations field, do not demongoize the
732
745
  # value so the full hash is returned.
733
746
  # 4. Otherwise, fetch and demongoize the value for the key meth.
734
- if curr.is_a? Array
735
- res = curr.map { |x| fetch_and_demongoize(x, meth, k) }
747
+ curr = if curr.is_a? Array
748
+ res = fetch_and_demongoize(curr, meth, field)
736
749
  res.empty? ? nil : res
737
- elsif !is_translation && k.fields[meth]&.localized?
738
- if i < meths.length-1
750
+ elsif !is_translation && field&.localized?
751
+ if i < num_meths
739
752
  curr.try(:fetch, meth, nil)
740
753
  else
741
- fetch_and_demongoize(curr, meth, k)
754
+ fetch_and_demongoize(curr, meth, field)
742
755
  end
743
756
  elsif is_translation
744
757
  curr.try(:fetch, meth, nil)
745
758
  else
746
- fetch_and_demongoize(curr, meth, k)
747
- end.tap do
748
- if as = k.try(:aliased_associations)
749
- if a = as.fetch(meth, nil)
750
- meth = a
751
- end
752
- end
753
-
754
- if relation = k.relations[meth]
755
- k = relation.klass
756
- end
759
+ fetch_and_demongoize(curr, meth, field)
757
760
  end
761
+
762
+ i += 1
758
763
  end
764
+ curr
759
765
  end
760
766
 
761
767
  # Recursively demongoize the given value. This method recursively traverses
@@ -763,35 +769,72 @@ module Mongoid
763
769
  #
764
770
  # @param [ String ] field_name The name of the field to demongoize.
765
771
  # @param [ Object ] value The value to demongoize.
766
- # @param [ Boolean ] is_translation The field we are retrieving is an
772
+ # @param [ true | false ] is_translation The field we are retrieving is an
767
773
  # _translations field.
768
774
  #
769
775
  # @return [ Object ] The demongoized value.
770
776
  def recursive_demongoize(field_name, value, is_translation)
771
- k = klass
772
- field_name.split('.').each do |meth|
773
- if as = k.try(:aliased_associations)
774
- if a = as.fetch(meth, nil)
775
- meth = a.to_s
776
- end
777
- end
777
+ field = klass.traverse_association_tree(field_name)
778
+ demongoize_with_field(field, value, is_translation)
779
+ end
778
780
 
779
- if relation = k.relations[meth]
780
- k = relation.klass
781
- elsif field = k.fields[meth]
782
- # If it's a localized field that's not a hash, don't demongoize
783
- # again, we already have the translation. If it's an _translation
784
- # field, don't demongoize, we want the full hash not just a
785
- # specific translation.
786
- if field.localized? && (!value.is_a?(Hash) || is_translation)
787
- return value.class.demongoize(value)
788
- else
789
- return field.demongoize(value)
790
- end
781
+ # Demongoize the value for the given field. If the field is nil or the
782
+ # field is a translations field, the value is demongoized using its class.
783
+ #
784
+ # @param [ Field ] field The field to use to demongoize.
785
+ # @param [ Object ] value The value to demongoize.
786
+ # @param [ true | false ] is_translation The field we are retrieving is an
787
+ # _translations field.
788
+ #
789
+ # @return [ Object ] The demongoized value.
790
+ #
791
+ # @api private
792
+ def demongoize_with_field(field, value, is_translation)
793
+ if field
794
+ # If it's a localized field that's not a hash, don't demongoize
795
+ # again, we already have the translation. If it's an _translations
796
+ # field, don't demongoize, we want the full hash not just a
797
+ # specific translation.
798
+ # If it is a hash, and it's not a translations field, we need to
799
+ # demongoize to get the correct translation.
800
+ if field.localized? && (!value.is_a?(Hash) || is_translation)
801
+ value.class.demongoize(value)
791
802
  else
792
- return value.class.demongoize(value)
803
+ field.demongoize(value)
793
804
  end
805
+ else
806
+ value.class.demongoize(value)
807
+ end
808
+ end
809
+
810
+ # Process the raw documents retrieved for #first/#last.
811
+ #
812
+ # @return [ Array<Document> | Document ] The list of documents or a
813
+ # single document.
814
+ def process_raw_docs(raw_docs, limit)
815
+ docs = raw_docs.map do |d|
816
+ Factory.from_db(klass, d, criteria)
794
817
  end
818
+ docs = eager_load(docs)
819
+ limit ? docs : docs.first
820
+ end
821
+
822
+ # Queries whether the current context is valid for use with
823
+ # the #count_documents? predicate. A context is valid if it
824
+ # does not include a `$where` operator.
825
+ #
826
+ # @return [ true | false ] whether or not the current context
827
+ # excludes a `$where` operator.
828
+ def valid_for_count_documents?(hash = view.filter)
829
+ # Note that `view.filter` is a BSON::Document, and all keys in a
830
+ # BSON::Document are strings; we don't need to worry about symbol
831
+ # representations of `$where`.
832
+ hash.keys.each do |key|
833
+ return false if key == '$where'
834
+ return false if hash[key].is_a?(Hash) && !valid_for_count_documents?(hash[key])
835
+ end
836
+
837
+ true
795
838
  end
796
839
  end
797
840
  end