mongoid 7.5.4 → 8.1.9

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 (446) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -3
  3. data/README.md +6 -6
  4. data/Rakefile +44 -46
  5. data/lib/config/locales/en.yml +92 -43
  6. data/lib/mongoid/association/accessors.rb +44 -11
  7. data/lib/mongoid/association/bindable.rb +50 -2
  8. data/lib/mongoid/association/builders.rb +5 -3
  9. data/lib/mongoid/association/constrainable.rb +0 -1
  10. data/lib/mongoid/association/eager_loadable.rb +32 -7
  11. data/lib/mongoid/association/embedded/batchable.rb +34 -11
  12. data/lib/mongoid/association/embedded/cyclic.rb +1 -1
  13. data/lib/mongoid/association/embedded/embedded_in/binding.rb +24 -2
  14. data/lib/mongoid/association/embedded/embedded_in/buildable.rb +2 -2
  15. data/lib/mongoid/association/embedded/embedded_in/proxy.rb +4 -3
  16. data/lib/mongoid/association/embedded/embedded_in.rb +3 -2
  17. data/lib/mongoid/association/embedded/embeds_many/binding.rb +1 -0
  18. data/lib/mongoid/association/embedded/embeds_many/buildable.rb +4 -3
  19. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +85 -46
  20. data/lib/mongoid/association/embedded/embeds_many.rb +2 -2
  21. data/lib/mongoid/association/embedded/embeds_one/buildable.rb +19 -5
  22. data/lib/mongoid/association/embedded/embeds_one/proxy.rb +24 -5
  23. data/lib/mongoid/association/embedded/embeds_one.rb +3 -3
  24. data/lib/mongoid/association/macros.rb +8 -1
  25. data/lib/mongoid/association/many.rb +11 -7
  26. data/lib/mongoid/association/nested/many.rb +5 -4
  27. data/lib/mongoid/association/nested/nested_buildable.rb +4 -4
  28. data/lib/mongoid/association/nested/one.rb +45 -7
  29. data/lib/mongoid/association/one.rb +2 -2
  30. data/lib/mongoid/association/options.rb +9 -9
  31. data/lib/mongoid/association/proxy.rb +15 -4
  32. data/lib/mongoid/association/referenced/auto_save.rb +4 -3
  33. data/lib/mongoid/association/referenced/belongs_to/binding.rb +1 -0
  34. data/lib/mongoid/association/referenced/belongs_to/buildable.rb +1 -1
  35. data/lib/mongoid/association/referenced/belongs_to/proxy.rb +5 -6
  36. data/lib/mongoid/association/referenced/belongs_to.rb +2 -2
  37. data/lib/mongoid/association/referenced/counter_cache.rb +10 -10
  38. data/lib/mongoid/association/referenced/eager.rb +2 -2
  39. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +70 -13
  40. data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +6 -3
  41. data/lib/mongoid/association/referenced/has_many/enumerable.rb +22 -30
  42. data/lib/mongoid/association/referenced/has_many/proxy.rb +40 -21
  43. data/lib/mongoid/association/referenced/has_many.rb +3 -3
  44. data/lib/mongoid/association/referenced/has_one/buildable.rb +1 -1
  45. data/lib/mongoid/association/referenced/has_one/nested_builder.rb +5 -5
  46. data/lib/mongoid/association/referenced/has_one/proxy.rb +9 -12
  47. data/lib/mongoid/association/referenced/has_one.rb +3 -3
  48. data/lib/mongoid/association/referenced/syncable.rb +4 -4
  49. data/lib/mongoid/association/reflections.rb +4 -4
  50. data/lib/mongoid/association/relatable.rb +44 -10
  51. data/lib/mongoid/association.rb +5 -5
  52. data/lib/mongoid/atomic/modifiers.rb +2 -2
  53. data/lib/mongoid/atomic.rb +16 -7
  54. data/lib/mongoid/attributes/dynamic.rb +4 -4
  55. data/lib/mongoid/attributes/nested.rb +6 -6
  56. data/lib/mongoid/attributes/processing.rb +37 -6
  57. data/lib/mongoid/attributes/projector.rb +2 -2
  58. data/lib/mongoid/attributes/readonly.rb +11 -6
  59. data/lib/mongoid/attributes.rb +51 -42
  60. data/lib/mongoid/changeable.rb +147 -14
  61. data/lib/mongoid/clients/options.rb +5 -1
  62. data/lib/mongoid/clients/sessions.rb +2 -14
  63. data/lib/mongoid/clients/storage_options.rb +2 -5
  64. data/lib/mongoid/clients/validators/storage.rb +3 -15
  65. data/lib/mongoid/collection_configurable.rb +58 -0
  66. data/lib/mongoid/composable.rb +2 -0
  67. data/lib/mongoid/config/defaults.rb +60 -0
  68. data/lib/mongoid/config/options.rb +3 -0
  69. data/lib/mongoid/config/validators/async_query_executor.rb +24 -0
  70. data/lib/mongoid/config/validators/client.rb +6 -6
  71. data/lib/mongoid/config/validators.rb +1 -0
  72. data/lib/mongoid/config.rb +153 -18
  73. data/lib/mongoid/contextual/aggregable/memory.rb +24 -16
  74. data/lib/mongoid/contextual/aggregable/mongo.rb +5 -5
  75. data/lib/mongoid/contextual/aggregable/none.rb +1 -1
  76. data/lib/mongoid/contextual/atomic.rb +1 -1
  77. data/lib/mongoid/contextual/geo_near.rb +7 -7
  78. data/lib/mongoid/contextual/map_reduce.rb +2 -2
  79. data/lib/mongoid/contextual/memory.rb +285 -58
  80. data/lib/mongoid/contextual/mongo/documents_loader.rb +177 -0
  81. data/lib/mongoid/contextual/mongo.rb +540 -346
  82. data/lib/mongoid/contextual/none.rb +193 -20
  83. data/lib/mongoid/contextual/queryable.rb +1 -1
  84. data/lib/mongoid/contextual.rb +14 -2
  85. data/lib/mongoid/copyable.rb +32 -8
  86. data/lib/mongoid/criteria/findable.rb +8 -5
  87. data/lib/mongoid/criteria/includable.rb +27 -22
  88. data/lib/mongoid/criteria/marshalable.rb +10 -2
  89. data/lib/mongoid/criteria/permission.rb +1 -1
  90. data/lib/mongoid/criteria/queryable/aggregable.rb +2 -2
  91. data/lib/mongoid/criteria/queryable/extensions/array.rb +3 -16
  92. data/lib/mongoid/criteria/queryable/extensions/big_decimal.rb +25 -4
  93. data/lib/mongoid/criteria/queryable/extensions/boolean.rb +2 -2
  94. data/lib/mongoid/criteria/queryable/extensions/date.rb +6 -1
  95. data/lib/mongoid/criteria/queryable/extensions/date_time.rb +6 -1
  96. data/lib/mongoid/criteria/queryable/extensions/hash.rb +1 -17
  97. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +15 -9
  98. data/lib/mongoid/criteria/queryable/extensions/object.rb +2 -1
  99. data/lib/mongoid/criteria/queryable/extensions/range.rb +13 -5
  100. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +3 -3
  101. data/lib/mongoid/criteria/queryable/extensions/set.rb +1 -1
  102. data/lib/mongoid/criteria/queryable/extensions/string.rb +4 -14
  103. data/lib/mongoid/criteria/queryable/extensions/symbol.rb +4 -12
  104. data/lib/mongoid/criteria/queryable/extensions/time.rb +6 -1
  105. data/lib/mongoid/criteria/queryable/extensions/time_with_zone.rb +6 -1
  106. data/lib/mongoid/criteria/queryable/key.rb +4 -4
  107. data/lib/mongoid/criteria/queryable/mergeable.rb +1 -1
  108. data/lib/mongoid/criteria/queryable/optional.rb +11 -17
  109. data/lib/mongoid/criteria/queryable/options.rb +2 -2
  110. data/lib/mongoid/criteria/queryable/pipeline.rb +1 -1
  111. data/lib/mongoid/criteria/queryable/selectable.rb +48 -39
  112. data/lib/mongoid/criteria/queryable/selector.rb +92 -7
  113. data/lib/mongoid/criteria/queryable/smash.rb +40 -7
  114. data/lib/mongoid/criteria/queryable.rb +12 -7
  115. data/lib/mongoid/criteria/scopable.rb +2 -2
  116. data/lib/mongoid/criteria/translator.rb +45 -0
  117. data/lib/mongoid/criteria.rb +20 -40
  118. data/lib/mongoid/deprecable.rb +37 -0
  119. data/lib/mongoid/deprecation.rb +25 -0
  120. data/lib/mongoid/document.rb +135 -36
  121. data/lib/mongoid/equality.rb +9 -8
  122. data/lib/mongoid/errors/create_collection_failure.rb +33 -0
  123. data/lib/mongoid/errors/document_not_found.rb +10 -6
  124. data/lib/mongoid/errors/drop_collection_failure.rb +27 -0
  125. data/lib/mongoid/errors/immutable_attribute.rb +26 -0
  126. data/lib/mongoid/errors/invalid_async_query_executor.rb +25 -0
  127. data/lib/mongoid/errors/invalid_config_option.rb +1 -1
  128. data/lib/mongoid/errors/invalid_dependent_strategy.rb +1 -1
  129. data/lib/mongoid/errors/invalid_dot_dollar_assignment.rb +23 -0
  130. data/lib/mongoid/errors/invalid_field.rb +6 -2
  131. data/lib/mongoid/errors/invalid_field_type.rb +26 -0
  132. data/lib/mongoid/errors/invalid_global_executor_concurrency.rb +22 -0
  133. data/lib/mongoid/errors/invalid_relation.rb +1 -1
  134. data/lib/mongoid/errors/invalid_relation_option.rb +1 -1
  135. data/lib/mongoid/errors/invalid_session_use.rb +1 -1
  136. data/lib/mongoid/errors/invalid_storage_options.rb +1 -1
  137. data/lib/mongoid/errors/invalid_storage_parent.rb +2 -0
  138. data/lib/mongoid/errors/mongoid_error.rb +3 -3
  139. data/lib/mongoid/errors/nested_attributes_metadata_not_found.rb +1 -1
  140. data/lib/mongoid/errors/no_client_database.rb +1 -1
  141. data/lib/mongoid/errors/no_client_hosts.rb +1 -1
  142. data/lib/mongoid/errors/readonly_attribute.rb +1 -1
  143. data/lib/mongoid/errors/too_many_nested_attribute_records.rb +1 -1
  144. data/lib/mongoid/errors/unknown_attribute.rb +1 -1
  145. data/lib/mongoid/errors.rb +6 -3
  146. data/lib/mongoid/extensions/array.rb +9 -7
  147. data/lib/mongoid/extensions/big_decimal.rb +33 -10
  148. data/lib/mongoid/extensions/binary.rb +42 -0
  149. data/lib/mongoid/extensions/boolean.rb +8 -2
  150. data/lib/mongoid/extensions/date.rb +26 -20
  151. data/lib/mongoid/extensions/date_time.rb +1 -1
  152. data/lib/mongoid/extensions/false_class.rb +1 -1
  153. data/lib/mongoid/extensions/float.rb +7 -4
  154. data/lib/mongoid/extensions/hash.rb +38 -9
  155. data/lib/mongoid/extensions/integer.rb +7 -4
  156. data/lib/mongoid/extensions/module.rb +1 -1
  157. data/lib/mongoid/extensions/object.rb +10 -8
  158. data/lib/mongoid/extensions/range.rb +41 -10
  159. data/lib/mongoid/extensions/regexp.rb +11 -4
  160. data/lib/mongoid/extensions/set.rb +11 -4
  161. data/lib/mongoid/extensions/string.rb +11 -22
  162. data/lib/mongoid/extensions/symbol.rb +4 -15
  163. data/lib/mongoid/extensions/time.rb +29 -16
  164. data/lib/mongoid/extensions/time_with_zone.rb +1 -2
  165. data/lib/mongoid/extensions/true_class.rb +1 -1
  166. data/lib/mongoid/extensions.rb +1 -0
  167. data/lib/mongoid/factory.rb +55 -7
  168. data/lib/mongoid/fields/foreign_key.rb +11 -4
  169. data/lib/mongoid/fields/localized.rb +19 -4
  170. data/lib/mongoid/fields/standard.rb +17 -7
  171. data/lib/mongoid/fields/validators/macro.rb +3 -9
  172. data/lib/mongoid/fields.rb +142 -28
  173. data/lib/mongoid/findable.rb +54 -24
  174. data/lib/mongoid/indexable/specification.rb +2 -2
  175. data/lib/mongoid/indexable/validators/options.rb +6 -2
  176. data/lib/mongoid/interceptable.rb +187 -16
  177. data/lib/mongoid/matchable.rb +1 -1
  178. data/lib/mongoid/matcher/eq_impl.rb +1 -1
  179. data/lib/mongoid/matcher/type.rb +1 -1
  180. data/lib/mongoid/matcher.rb +48 -14
  181. data/lib/mongoid/persistable/creatable.rb +19 -9
  182. data/lib/mongoid/persistable/deletable.rb +2 -2
  183. data/lib/mongoid/persistable/destroyable.rb +1 -1
  184. data/lib/mongoid/persistable/savable.rb +14 -2
  185. data/lib/mongoid/persistable/unsettable.rb +2 -2
  186. data/lib/mongoid/persistable/updatable.rb +69 -12
  187. data/lib/mongoid/persistable/upsertable.rb +21 -2
  188. data/lib/mongoid/persistable.rb +6 -3
  189. data/lib/mongoid/persistence_context.rb +6 -4
  190. data/lib/mongoid/query_cache.rb +13 -261
  191. data/lib/mongoid/railties/controller_runtime.rb +1 -1
  192. data/lib/mongoid/railties/database.rake +7 -2
  193. data/lib/mongoid/reloadable.rb +10 -8
  194. data/lib/mongoid/scopable.rb +15 -13
  195. data/lib/mongoid/selectable.rb +1 -2
  196. data/lib/mongoid/serializable.rb +17 -13
  197. data/lib/mongoid/stateful.rb +57 -10
  198. data/lib/mongoid/tasks/database.rake +12 -0
  199. data/lib/mongoid/tasks/database.rb +20 -2
  200. data/lib/mongoid/threaded/lifecycle.rb +5 -5
  201. data/lib/mongoid/threaded.rb +42 -12
  202. data/lib/mongoid/timestamps/created.rb +9 -2
  203. data/lib/mongoid/timestamps/updated.rb +2 -2
  204. data/lib/mongoid/touchable.rb +3 -4
  205. data/lib/mongoid/traversable.rb +41 -5
  206. data/lib/mongoid/utils.rb +22 -0
  207. data/lib/mongoid/validatable/associated.rb +98 -17
  208. data/lib/mongoid/validatable/localizable.rb +1 -1
  209. data/lib/mongoid/validatable/macros.rb +5 -7
  210. data/lib/mongoid/validatable/presence.rb +2 -2
  211. data/lib/mongoid/validatable/uniqueness.rb +9 -8
  212. data/lib/mongoid/validatable.rb +17 -6
  213. data/lib/mongoid/version.rb +1 -1
  214. data/lib/mongoid/warnings.rb +19 -4
  215. data/lib/mongoid.rb +17 -3
  216. data/spec/config/mongoid.yml +16 -0
  217. data/spec/integration/app_spec.rb +24 -19
  218. data/spec/integration/associations/belongs_to_spec.rb +18 -0
  219. data/spec/integration/associations/embedded_spec.rb +15 -0
  220. data/spec/integration/associations/embeds_many_spec.rb +15 -2
  221. data/spec/integration/associations/embeds_one_spec.rb +18 -0
  222. data/spec/integration/associations/foreign_key_spec.rb +9 -0
  223. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +61 -0
  224. data/spec/integration/associations/has_one_spec.rb +97 -1
  225. data/spec/integration/associations/scope_option_spec.rb +1 -1
  226. data/spec/integration/callbacks_models.rb +132 -1
  227. data/spec/integration/callbacks_spec.rb +381 -4
  228. data/spec/integration/criteria/range_spec.rb +95 -1
  229. data/spec/integration/discriminator_key_spec.rb +118 -80
  230. data/spec/integration/dots_and_dollars_spec.rb +277 -0
  231. data/spec/integration/i18n_fallbacks_spec.rb +3 -32
  232. data/spec/integration/matcher_examples_spec.rb +20 -13
  233. data/spec/integration/matcher_operator_data/type_decimal.yml +3 -2
  234. data/spec/integration/matcher_operator_spec.rb +3 -5
  235. data/spec/integration/persistence/range_field_spec.rb +350 -0
  236. data/spec/mongoid/association/counter_cache_spec.rb +1 -1
  237. data/spec/mongoid/association/depending_spec.rb +9 -9
  238. data/spec/mongoid/association/eager_spec.rb +26 -3
  239. data/spec/mongoid/association/embedded/embedded_in/binding_spec.rb +2 -1
  240. data/spec/mongoid/association/embedded/embedded_in/buildable_spec.rb +54 -0
  241. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +96 -9
  242. data/spec/mongoid/association/embedded/embeds_many/buildable_spec.rb +112 -0
  243. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +290 -65
  244. data/spec/mongoid/association/embedded/embeds_many_models.rb +37 -0
  245. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +16 -0
  246. data/spec/mongoid/association/embedded/embeds_many_spec.rb +68 -0
  247. data/spec/mongoid/association/embedded/embeds_one/buildable_spec.rb +25 -0
  248. data/spec/mongoid/association/embedded/embeds_one/proxy_spec.rb +15 -2
  249. data/spec/mongoid/association/embedded/embeds_one_models.rb +19 -0
  250. data/spec/mongoid/association/embedded/embeds_one_spec.rb +28 -0
  251. data/spec/mongoid/association/referenced/belongs_to/binding_spec.rb +2 -1
  252. data/spec/mongoid/association/referenced/belongs_to/buildable_spec.rb +54 -0
  253. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +19 -0
  254. data/spec/mongoid/association/referenced/belongs_to_models.rb +11 -0
  255. data/spec/mongoid/association/referenced/belongs_to_spec.rb +4 -20
  256. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +186 -229
  257. data/spec/mongoid/association/referenced/has_and_belongs_to_many_models.rb +25 -0
  258. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +35 -2
  259. data/spec/mongoid/association/referenced/has_many/buildable_spec.rb +109 -0
  260. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +2 -56
  261. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +215 -177
  262. data/spec/mongoid/association/referenced/has_many_models.rb +3 -1
  263. data/spec/mongoid/association/referenced/has_many_spec.rb +25 -0
  264. data/spec/mongoid/association/referenced/has_one/buildable_spec.rb +2 -2
  265. data/spec/mongoid/association/referenced/has_one/proxy_spec.rb +107 -1
  266. data/spec/mongoid/association/referenced/has_one_models.rb +16 -0
  267. data/spec/mongoid/association/syncable_spec.rb +15 -1
  268. data/spec/mongoid/association_spec.rb +60 -0
  269. data/spec/mongoid/atomic/paths_spec.rb +0 -14
  270. data/spec/mongoid/attributes/nested_spec.rb +80 -11
  271. data/spec/mongoid/attributes/nested_spec_models.rb +48 -0
  272. data/spec/mongoid/attributes/projector_spec.rb +1 -5
  273. data/spec/mongoid/attributes/readonly_spec.rb +19 -0
  274. data/spec/mongoid/attributes_spec.rb +526 -33
  275. data/spec/mongoid/changeable_spec.rb +429 -37
  276. data/spec/mongoid/clients/factory_spec.rb +23 -30
  277. data/spec/mongoid/clients/sessions_spec.rb +0 -38
  278. data/spec/mongoid/clients_spec.rb +149 -15
  279. data/spec/mongoid/collection_configurable_spec.rb +158 -0
  280. data/spec/mongoid/config/defaults_spec.rb +160 -0
  281. data/spec/mongoid/config_spec.rb +214 -31
  282. data/spec/mongoid/contextual/aggregable/memory_spec.rb +396 -158
  283. data/spec/mongoid/contextual/aggregable/memory_table.yml +88 -0
  284. data/spec/mongoid/contextual/aggregable/memory_table_spec.rb +62 -0
  285. data/spec/mongoid/contextual/map_reduce_spec.rb +2 -16
  286. data/spec/mongoid/contextual/memory_spec.rb +850 -88
  287. data/spec/mongoid/contextual/mongo/documents_loader_spec.rb +187 -0
  288. data/spec/mongoid/contextual/mongo_spec.rb +2256 -1005
  289. data/spec/mongoid/contextual/none_spec.rb +60 -21
  290. data/spec/mongoid/copyable_spec.rb +453 -11
  291. data/spec/mongoid/criteria/findable_spec.rb +86 -210
  292. data/spec/mongoid/criteria/includable_spec.rb +1492 -0
  293. data/spec/mongoid/criteria/includable_spec_models.rb +54 -0
  294. data/spec/mongoid/criteria/marshalable_spec.rb +18 -1
  295. data/spec/mongoid/criteria/queryable/extensions/array_spec.rb +7 -19
  296. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +134 -26
  297. data/spec/mongoid/criteria/queryable/extensions/date_spec.rb +11 -0
  298. data/spec/mongoid/criteria/queryable/extensions/date_time_spec.rb +11 -0
  299. data/spec/mongoid/criteria/queryable/extensions/hash_spec.rb +0 -15
  300. data/spec/mongoid/criteria/queryable/extensions/numeric_spec.rb +73 -7
  301. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +4 -69
  302. data/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb +0 -59
  303. data/spec/mongoid/criteria/queryable/extensions/time_spec.rb +11 -0
  304. data/spec/mongoid/criteria/queryable/extensions/time_with_zone_spec.rb +11 -0
  305. data/spec/mongoid/criteria/queryable/optional_spec.rb +15 -484
  306. data/spec/mongoid/criteria/queryable/options_spec.rb +1 -1
  307. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +469 -0
  308. data/spec/mongoid/criteria/queryable/selectable_spec.rb +107 -86
  309. data/spec/mongoid/criteria/queryable/selector_spec.rb +15 -3
  310. data/spec/mongoid/criteria/translator_spec.rb +132 -0
  311. data/spec/mongoid/criteria_projection_spec.rb +1 -5
  312. data/spec/mongoid/criteria_spec.rb +469 -1205
  313. data/spec/mongoid/document_fields_spec.rb +173 -24
  314. data/spec/mongoid/document_spec.rb +59 -41
  315. data/spec/mongoid/equality_spec.rb +6 -0
  316. data/spec/mongoid/errors/document_not_found_spec.rb +29 -2
  317. data/spec/mongoid/errors/invalid_field_spec.rb +1 -1
  318. data/spec/mongoid/errors/invalid_field_type_spec.rb +55 -0
  319. data/spec/mongoid/errors/mongoid_error_spec.rb +3 -1
  320. data/spec/mongoid/errors/no_environment_spec.rb +3 -3
  321. data/spec/mongoid/errors/readonly_document_spec.rb +2 -2
  322. data/spec/mongoid/errors/too_many_nested_attribute_records_spec.rb +1 -1
  323. data/spec/mongoid/extensions/array_spec.rb +16 -2
  324. data/spec/mongoid/extensions/big_decimal_spec.rb +712 -212
  325. data/spec/mongoid/extensions/binary_spec.rb +44 -9
  326. data/spec/mongoid/extensions/boolean_spec.rb +68 -82
  327. data/spec/mongoid/extensions/date_class_mongoize_spec.rb +7 -3
  328. data/spec/mongoid/extensions/date_spec.rb +71 -1
  329. data/spec/mongoid/extensions/date_time_spec.rb +15 -9
  330. data/spec/mongoid/extensions/float_spec.rb +53 -74
  331. data/spec/mongoid/extensions/hash_spec.rb +33 -3
  332. data/spec/mongoid/extensions/integer_spec.rb +50 -64
  333. data/spec/mongoid/extensions/range_spec.rb +255 -54
  334. data/spec/mongoid/extensions/regexp_spec.rb +58 -33
  335. data/spec/mongoid/extensions/set_spec.rb +106 -0
  336. data/spec/mongoid/extensions/string_spec.rb +53 -25
  337. data/spec/mongoid/extensions/symbol_spec.rb +18 -25
  338. data/spec/mongoid/extensions/time_spec.rb +639 -106
  339. data/spec/mongoid/extensions/time_with_zone_spec.rb +24 -83
  340. data/spec/mongoid/factory_spec.rb +61 -1
  341. data/spec/mongoid/fields/localized_spec.rb +80 -37
  342. data/spec/mongoid/fields_spec.rb +503 -87
  343. data/spec/mongoid/findable_spec.rb +450 -58
  344. data/spec/mongoid/indexable/specification_spec.rb +2 -2
  345. data/spec/mongoid/indexable_spec.rb +55 -30
  346. data/spec/mongoid/interceptable_spec.rb +904 -22
  347. data/spec/mongoid/interceptable_spec_models.rb +189 -22
  348. data/spec/mongoid/matcher/extract_attribute_spec.rb +1 -5
  349. data/spec/mongoid/mongoizable_spec.rb +285 -0
  350. data/spec/mongoid/persistable/creatable_spec.rb +2 -2
  351. data/spec/mongoid/persistable/deletable_spec.rb +28 -8
  352. data/spec/mongoid/persistable/destroyable_spec.rb +28 -8
  353. data/spec/mongoid/persistable/incrementable_spec.rb +37 -0
  354. data/spec/mongoid/persistable/logical_spec.rb +37 -0
  355. data/spec/mongoid/persistable/poppable_spec.rb +36 -0
  356. data/spec/mongoid/persistable/pullable_spec.rb +72 -0
  357. data/spec/mongoid/persistable/pushable_spec.rb +72 -0
  358. data/spec/mongoid/persistable/renamable_spec.rb +36 -0
  359. data/spec/mongoid/persistable/savable_spec.rb +96 -0
  360. data/spec/mongoid/persistable/settable_spec.rb +37 -0
  361. data/spec/mongoid/persistable/unsettable_spec.rb +36 -0
  362. data/spec/mongoid/persistable/updatable_spec.rb +20 -28
  363. data/spec/mongoid/persistable/upsertable_spec.rb +89 -1
  364. data/spec/mongoid/persistence_context_spec.rb +31 -57
  365. data/spec/mongoid/query_cache_middleware_spec.rb +0 -18
  366. data/spec/mongoid/query_cache_spec.rb +56 -215
  367. data/spec/mongoid/reloadable_spec.rb +83 -6
  368. data/spec/mongoid/scopable_spec.rb +91 -1
  369. data/spec/mongoid/serializable_spec.rb +25 -39
  370. data/spec/mongoid/shardable_spec.rb +4 -4
  371. data/spec/mongoid/stateful_spec.rb +150 -8
  372. data/spec/mongoid/tasks/database_rake_spec.rb +74 -0
  373. data/spec/mongoid/tasks/database_spec.rb +127 -0
  374. data/spec/mongoid/timestamps/created_spec.rb +23 -0
  375. data/spec/mongoid/timestamps_spec.rb +392 -4
  376. data/spec/mongoid/timestamps_spec_models.rb +67 -0
  377. data/spec/mongoid/touchable_spec.rb +390 -2
  378. data/spec/mongoid/touchable_spec_models.rb +14 -8
  379. data/spec/mongoid/traversable_spec.rb +13 -35
  380. data/spec/mongoid/validatable/associated_spec.rb +27 -34
  381. data/spec/mongoid/validatable/presence_spec.rb +1 -1
  382. data/spec/mongoid/validatable/uniqueness_spec.rb +58 -31
  383. data/spec/mongoid/warnings_spec.rb +35 -0
  384. data/spec/mongoid_spec.rb +34 -16
  385. data/spec/rails/controller_extension/controller_runtime_spec.rb +2 -2
  386. data/spec/rails/mongoid_spec.rb +4 -16
  387. data/spec/spec_helper.rb +5 -0
  388. data/spec/support/constraints.rb +24 -0
  389. data/spec/support/immutable_ids.rb +118 -0
  390. data/spec/support/macros.rb +78 -0
  391. data/spec/support/models/artist.rb +0 -1
  392. data/spec/support/models/augmentation.rb +12 -0
  393. data/spec/support/models/band.rb +5 -0
  394. data/spec/support/models/book.rb +1 -0
  395. data/spec/support/models/building.rb +2 -0
  396. data/spec/support/models/catalog.rb +24 -0
  397. data/spec/support/models/circus.rb +3 -0
  398. data/spec/support/models/cover.rb +10 -0
  399. data/spec/support/models/fanatic.rb +8 -0
  400. data/spec/support/models/implant.rb +9 -0
  401. data/spec/support/models/label.rb +2 -0
  402. data/spec/support/models/lat_lng.rb +6 -0
  403. data/spec/support/models/name.rb +10 -0
  404. data/spec/support/models/passport.rb +9 -0
  405. data/spec/support/models/person.rb +2 -0
  406. data/spec/support/models/player.rb +2 -0
  407. data/spec/support/models/powerup.rb +12 -0
  408. data/spec/support/models/product.rb +1 -0
  409. data/spec/support/models/purse.rb +9 -0
  410. data/spec/support/models/registry.rb +1 -0
  411. data/spec/support/models/school.rb +14 -0
  412. data/spec/support/models/shield.rb +18 -0
  413. data/spec/support/models/student.rb +14 -0
  414. data/spec/support/models/weapon.rb +12 -0
  415. metadata +98 -96
  416. checksums.yaml.gz.sig +0 -0
  417. data/lib/mongoid/errors/eager_load.rb +0 -23
  418. data/lib/mongoid/errors/invalid_value.rb +0 -17
  419. data/spec/mongoid/criteria/queryable/extensions/bignum_spec.rb +0 -60
  420. data/spec/mongoid/criteria/queryable/extensions/fixnum_spec.rb +0 -60
  421. data/spec/mongoid/errors/eager_load_spec.rb +0 -31
  422. data/spec/shared/LICENSE +0 -20
  423. data/spec/shared/bin/get-mongodb-download-url +0 -17
  424. data/spec/shared/bin/s3-copy +0 -45
  425. data/spec/shared/bin/s3-upload +0 -69
  426. data/spec/shared/lib/mrss/child_process_helper.rb +0 -80
  427. data/spec/shared/lib/mrss/cluster_config.rb +0 -231
  428. data/spec/shared/lib/mrss/constraints.rb +0 -378
  429. data/spec/shared/lib/mrss/docker_runner.rb +0 -291
  430. data/spec/shared/lib/mrss/eg_config_utils.rb +0 -51
  431. data/spec/shared/lib/mrss/event_subscriber.rb +0 -210
  432. data/spec/shared/lib/mrss/lite_constraints.rb +0 -238
  433. data/spec/shared/lib/mrss/server_version_registry.rb +0 -120
  434. data/spec/shared/lib/mrss/session_registry.rb +0 -69
  435. data/spec/shared/lib/mrss/session_registry_legacy.rb +0 -60
  436. data/spec/shared/lib/mrss/spec_organizer.rb +0 -179
  437. data/spec/shared/lib/mrss/utils.rb +0 -15
  438. data/spec/shared/share/Dockerfile.erb +0 -325
  439. data/spec/shared/share/haproxy-1.conf +0 -16
  440. data/spec/shared/share/haproxy-2.conf +0 -17
  441. data/spec/shared/shlib/config.sh +0 -27
  442. data/spec/shared/shlib/distro.sh +0 -74
  443. data/spec/shared/shlib/server.sh +0 -392
  444. data/spec/shared/shlib/set_env.sh +0 -169
  445. data.tar.gz.sig +0 -0
  446. metadata.gz.sig +0 -3
@@ -44,39 +44,6 @@ describe Mongoid::Contextual::Mongo do
44
44
  end
45
45
  end
46
46
 
47
- describe "#cached?" do
48
-
49
- context "when the criteria is cached" do
50
-
51
- let(:criteria) do
52
- Band.all.cache
53
- end
54
-
55
- let(:context) do
56
- described_class.new(criteria)
57
- end
58
-
59
- it "returns true" do
60
- expect(context).to be_cached
61
- end
62
- end
63
-
64
- context "when the criteria is not cached" do
65
-
66
- let(:criteria) do
67
- Band.all
68
- end
69
-
70
- let(:context) do
71
- described_class.new(criteria)
72
- end
73
-
74
- it "returns false" do
75
- expect(context).to_not be_cached
76
- end
77
- end
78
- end
79
-
80
47
  describe "#count" do
81
48
 
82
49
  let!(:depeche) do
@@ -102,15 +69,17 @@ describe Mongoid::Contextual::Mongo do
102
69
  end
103
70
  end
104
71
 
105
- context "when context is cached" do
72
+ context "when the query cache is enabled" do
73
+ query_cache_enabled
106
74
 
107
75
  let(:context) do
108
- described_class.new(criteria.cache)
76
+ described_class.new(criteria)
109
77
  end
110
78
 
111
- it "returns the count cached value after first call" do
112
- expect(context.view).to receive(:count_documents).once.and_return(1)
113
- 2.times { expect(context.count).to eq(1) }
79
+ it "only executes the count query once" do
80
+ expect_query(1) do
81
+ 2.times { expect(context.count).to eq(1) }
82
+ end
114
83
  end
115
84
  end
116
85
 
@@ -189,6 +158,16 @@ describe Mongoid::Contextual::Mongo do
189
158
  end
190
159
  end
191
160
  end
161
+
162
+ context 'when for_js is present' do
163
+ let(:context) do
164
+ Band.for_js('this.name == "Depeche Mode"')
165
+ end
166
+
167
+ it 'counts the expected records' do
168
+ expect(context.count).to eq(1)
169
+ end
170
+ end
192
171
  end
193
172
 
194
173
  describe "#estimated_count" do
@@ -217,16 +196,18 @@ describe Mongoid::Contextual::Mongo do
217
196
  end
218
197
  end
219
198
 
220
- context "when context is cached" do
199
+ context "when the query cache is enabled" do
200
+ query_cache_enabled
221
201
 
222
202
  let(:context) do
223
- described_class.new(criteria.cache)
203
+ described_class.new(criteria)
224
204
  end
225
205
 
226
- it "returns the count cached value after first call" do
227
- expect(context.view).to receive(:estimated_document_count).once.and_return(1)
228
- 2.times do
229
- context.estimated_count
206
+ it "the results are not cached" do
207
+ expect_query(2) do
208
+ 2.times do
209
+ context.estimated_count
210
+ end
230
211
  end
231
212
  end
232
213
  end
@@ -561,8 +542,22 @@ describe Mongoid::Contextual::Mongo do
561
542
  context "when legacy_pluck_distinct is set" do
562
543
  config_override :legacy_pluck_distinct, true
563
544
 
564
- it "returns the non-demongoized distinct field values" do
565
- expect(context.distinct(:sales).sort).to eq([ "1E2", "2E3" ])
545
+ context 'when storing BigDecimal as string' do
546
+ config_override :map_big_decimal_to_decimal128, false
547
+
548
+ it "returns the non-demongoized distinct field values" do
549
+ expect(context.distinct(:sales).sort).to eq([ "1E2", "2E3" ])
550
+ end
551
+ end
552
+
553
+ context 'when storing BigDecimal as decimal128' do
554
+ config_override :map_big_decimal_to_decimal128, true
555
+ min_bson_version '4.15.0'
556
+ max_bson_version '4.99.99'
557
+
558
+ it "returns the non-demongoized distinct field values" do
559
+ expect(context.distinct(:sales).sort).to eq([ BSON::Decimal128.new("1E2"), BSON::Decimal128.new("2E3") ])
560
+ end
566
561
  end
567
562
  end
568
563
 
@@ -576,6 +571,8 @@ describe Mongoid::Contextual::Mongo do
576
571
  end
577
572
 
578
573
  context "when getting a localized field" do
574
+ with_default_i18n_configs
575
+
579
576
  before do
580
577
  I18n.locale = :en
581
578
  d = Dictionary.create!(description: 'english-text')
@@ -584,10 +581,6 @@ describe Mongoid::Contextual::Mongo do
584
581
  d.save!
585
582
  end
586
583
 
587
- after do
588
- I18n.locale = :en
589
- end
590
-
591
584
  let(:criteria) do
592
585
  Dictionary.criteria
593
586
  end
@@ -680,16 +673,10 @@ describe Mongoid::Contextual::Mongo do
680
673
 
681
674
  context 'when fallbacks are enabled with a locale list' do
682
675
  require_fallbacks
676
+ with_default_i18n_configs
683
677
 
684
- around(:all) do |example|
685
- prev_fallbacks = I18n.fallbacks.dup
678
+ before do
686
679
  I18n.fallbacks[:he] = [ :en ]
687
- example.run
688
- I18n.fallbacks = prev_fallbacks
689
- end
690
-
691
- after do
692
- I18n.locale = :en
693
680
  end
694
681
 
695
682
  let(:distinct) do
@@ -709,7 +696,7 @@ describe Mongoid::Contextual::Mongo do
709
696
 
710
697
  it "correctly uses the fallback" do
711
698
  I18n.locale = :en
712
- d = Dictionary.create!(description: 'english-text')
699
+ Dictionary.create!(description: 'english-text')
713
700
  I18n.locale = :he
714
701
  distinct.should == "english-text"
715
702
  end
@@ -717,6 +704,8 @@ describe Mongoid::Contextual::Mongo do
717
704
  end
718
705
 
719
706
  context "when the localized field is embedded" do
707
+ with_default_i18n_configs
708
+
720
709
  before do
721
710
  p = Passport.new
722
711
  I18n.locale = :en
@@ -727,10 +716,6 @@ describe Mongoid::Contextual::Mongo do
727
716
  Person.create!(passport: p, employer_id: 12345)
728
717
  end
729
718
 
730
- after do
731
- I18n.locale = :en
732
- end
733
-
734
719
  let(:criteria) do
735
720
  Person.where(employer_id: 12345)
736
721
  end
@@ -794,9 +779,11 @@ describe Mongoid::Contextual::Mongo do
794
779
 
795
780
  context "when legacy_pluck_distinct is set" do
796
781
  config_override :legacy_pluck_distinct, true
782
+ config_override :map_big_decimal_to_decimal128, true
783
+ max_bson_version '4.99.99'
797
784
 
798
785
  it "returns the distinct matching fields" do
799
- expect(context.distinct("label.sales")).to eq([ "1E2" ])
786
+ expect(context.distinct("label.sales")).to eq([ BSON::Decimal128.new('1E+2') ])
800
787
  end
801
788
  end
802
789
 
@@ -809,484 +796,860 @@ describe Mongoid::Contextual::Mongo do
809
796
  end
810
797
  end
811
798
 
812
- describe "#each" do
799
+ describe "#tally" do
800
+ let(:fans1) { [ Fanatic.new(age:1), Fanatic.new(age:2) ] }
801
+ let(:fans2) { [ Fanatic.new(age:1), Fanatic.new(age:2) ] }
802
+ let(:fans3) { [ Fanatic.new(age:1), Fanatic.new(age:3) ] }
813
803
 
814
- before do
815
- Band.create!(name: "Depeche Mode")
816
- end
804
+ let(:genres1) { [ { x: 1, y: { z: 1 } }, { x: 2, y: { z: 2 } }, { y: 3 } ]}
805
+ let(:genres2) { [ { x: 1, y: { z: 1 } }, { x: 2, y: { z: 2 } }, { y: 4 } ]}
806
+ let(:genres3) { [ { x: 1, y: { z: 1 } }, { x: 3, y: { z: 3 } }, { y: 5 } ]}
817
807
 
818
- let(:criteria) do
819
- Band.where(name: "Depeche Mode")
820
- end
808
+ let(:label1) { Label.new(name: "Atlantic") }
809
+ let(:label2) { Label.new(name: "Atlantic") }
810
+ let(:label3) { Label.new(name: "Columbia") }
821
811
 
822
- let(:context) do
823
- described_class.new(criteria)
812
+ before do
813
+ Band.create!(origin: "tally", name: "Depeche Mode", years: 30, sales: "1E2", label: label1, genres: genres1)
814
+ Band.create!(origin: "tally", name: "New Order", years: 30, sales: "2E3", label: label2, genres: genres2)
815
+ Band.create!(origin: "tally", name: "10,000 Maniacs", years: 30, sales: "1E2", label: label3, genres: genres3)
816
+ Band.create!(origin: "tally2", fanatics: fans1, genres: [1, 2])
817
+ Band.create!(origin: "tally2", fanatics: fans2, genres: [1, 2])
818
+ Band.create!(origin: "tally2", fanatics: fans3, genres: [1, 3])
824
819
  end
825
820
 
826
- context 'when the criteria has a collation' do
827
- min_server_version '3.4'
821
+ let(:criteria) { Band.where(origin: "tally") }
828
822
 
829
- let(:criteria) do
830
- Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2)
823
+ context "when tallying a string" do
824
+ let(:tally) do
825
+ criteria.tally(:name)
831
826
  end
832
827
 
833
- it "yields mongoid documents to the block" do
834
- context.each do |doc|
835
- expect(doc).to be_a(Mongoid::Document)
836
- end
828
+ it "returns the correct hash" do
829
+ expect(tally).to eq("Depeche Mode" => 1, "New Order" => 1, "10,000 Maniacs" => 1)
837
830
  end
831
+ end
838
832
 
839
- it "iterates over the matching documents" do
840
- context.each do |doc|
841
- expect(doc.name).to eq("Depeche Mode")
842
- end
833
+ context "using an aliased field" do
834
+ let(:tally) do
835
+ criteria.tally(:years)
843
836
  end
844
837
 
845
- it "returns self" do
846
- expect(context.each{}).to be(context)
838
+ it "returns the correct hash" do
839
+ expect(tally).to eq(30 => 3)
847
840
  end
848
841
  end
849
842
 
850
- context "when providing a block" do
851
-
852
- it "yields mongoid documents to the block" do
853
- context.each do |doc|
854
- expect(doc).to be_a(Mongoid::Document)
855
- end
856
- end
857
-
858
- it "iterates over the matching documents" do
859
- context.each do |doc|
860
- expect(doc.name).to eq("Depeche Mode")
861
- end
843
+ context "when tallying a demongoizable field" do
844
+ let(:tally) do
845
+ criteria.tally(:sales)
862
846
  end
863
847
 
864
- it "returns self" do
865
- expect(context.each{}).to be(context)
848
+ it "returns the correct hash" do
849
+ expect(tally).to eq(BigDecimal("1E2") => 2, BigDecimal("2E3") => 1)
866
850
  end
867
851
  end
868
852
 
869
- context "when no block is provided" do
853
+ context "when tallying a localized field" do
854
+ with_default_i18n_configs
870
855
 
871
- let(:enum) do
872
- context.each
856
+ before do
857
+ I18n.locale = :en
858
+ d1 = Dictionary.create!(description: 'en1')
859
+ d2 = Dictionary.create!(description: 'en1')
860
+ d3 = Dictionary.create!(description: 'en1')
861
+ d4 = Dictionary.create!(description: 'en2')
862
+ I18n.locale = :de
863
+ d1.description = 'de1'
864
+ d2.description = 'de1'
865
+ d3.description = 'de2'
866
+ d4.description = 'de3'
867
+ d1.save!
868
+ d2.save!
869
+ d3.save!
870
+ d4.save!
871
+ I18n.locale = :en
873
872
  end
874
873
 
875
- it "returns an enumerator" do
876
- expect(enum).to be_a(Enumerator)
874
+ context "when getting the demongoized field" do
875
+ let(:tallied) do
876
+ Dictionary.tally(:description)
877
+ end
878
+
879
+ it "returns the translation for the current locale" do
880
+ expect(tallied).to eq("en1" => 3, "en2" => 1)
881
+ end
877
882
  end
878
883
 
879
- context "when iterating over the enumerator" do
884
+ context "when getting a specific locale" do
885
+ let(:tallied) do
886
+ Dictionary.tally("description.de")
887
+ end
880
888
 
881
- context "when iterating with each" do
889
+ it "returns the translation for the the specific locale" do
890
+ expect(tallied).to eq("de1" => 2, "de2" => 1, "de3" => 1)
891
+ end
892
+ end
882
893
 
883
- it "yields mongoid documents to the block" do
884
- enum.each do |doc|
885
- expect(doc).to be_a(Mongoid::Document)
886
- end
887
- end
894
+ context "when getting the full hash" do
895
+ let(:tallied) do
896
+ Dictionary.tally("description_translations")
888
897
  end
889
898
 
890
- context "when iterating with next" do
899
+ it "returns the correct hash" do
900
+ expect(tallied).to eq(
901
+ {"de" => "de1", "en" => "en1" } => 2,
902
+ {"de" => "de2", "en" => "en1" } => 1,
903
+ {"de" => "de3", "en" => "en2" } => 1
904
+ )
905
+ end
906
+ end
907
+ end
891
908
 
892
- before do
893
- 10.times { |i| Band.create!(name: "Test #{i}") }
894
- end
909
+ context "when tallying an embedded localized field" do
910
+ with_default_i18n_configs
895
911
 
896
- let(:criteria) do
897
- Band.batch_size(5)
898
- end
912
+ before do
913
+ I18n.locale = :en
914
+ address1a = Address.new(name: "en1")
915
+ address1b = Address.new(name: "en2")
916
+ address2a = Address.new(name: "en1")
917
+ address2b = Address.new(name: "en3")
918
+ I18n.locale = :de
919
+ address1a.name = "de1"
920
+ address1b.name = "de2"
921
+ address2a.name = "de1"
922
+ address2b.name = "de3"
923
+ Person.create!(addresses: [ address1a, address1b ])
924
+ Person.create!(addresses: [ address2a, address2b ])
925
+ I18n.locale = :en
926
+ end
899
927
 
900
- it "yields mongoid documents" do
901
- expect(enum.next).to be_a(Mongoid::Document)
902
- end
928
+ context "when getting the demongoized field" do
929
+ let(:tallied) do
930
+ Person.tally("addresses.name")
931
+ end
903
932
 
904
- it "does not load all documents" do
905
- subscriber = Mrss::EventSubscriber.new
906
- context.view.client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
933
+ it "returns the translation for the current locale" do
934
+ expect(tallied).to eq(
935
+ [ "en1", "en2" ] => 1,
936
+ [ "en1", "en3" ] => 1,
937
+ )
938
+ end
939
+ end
907
940
 
908
- enum.next
941
+ context "when getting a specific locale" do
942
+ let(:tallied) do
943
+ Person.tally("addresses.name.de")
944
+ end
909
945
 
910
- find_events = subscriber.all_events.select do |evt|
911
- evt.command_name == 'find'
912
- end
913
- expect(find_events.length).to be(2)
914
- get_more_events = subscriber.all_events.select do |evt|
915
- evt.command_name == 'getMore'
916
- end
917
- expect(get_more_events.length).to be(0)
918
- ensure
919
- context.view.client.unsubscribe(Mongo::Monitoring::COMMAND, subscriber)
920
- end
946
+ it "returns the translation for the the specific locale" do
947
+ expect(tallied).to eq(
948
+ [ "de1", "de2" ] => 1,
949
+ [ "de1", "de3" ] => 1,
950
+ )
921
951
  end
922
952
  end
923
- end
924
953
 
925
- context 'when the criteria has a parent document' do
954
+ context "when getting the full hash" do
955
+ let(:tallied) do
956
+ Person.tally("addresses.name_translations")
957
+ end
926
958
 
927
- before do
928
- Post.create!(person: person)
929
- Post.create!(person: person)
930
- Post.create!(person: person)
959
+ it "returns the correct hash" do
960
+ expect(tallied).to eq(
961
+ [{ "de" => "de1", "en" => "en1" }, { "de" => "de2", "en" => "en2" }] => 1,
962
+ [{ "de" => "de1", "en" => "en1" }, { "de" => "de3", "en" => "en3" }] => 1,
963
+ )
964
+ end
931
965
  end
932
966
 
933
- let(:person) do
934
- Person.new
967
+ end
968
+
969
+ context "when tallying an embedded field" do
970
+ let(:tally) do
971
+ criteria.tally("label.name")
935
972
  end
936
973
 
937
- let(:criteria) do
938
- person.posts.all
974
+ it "returns the correct hash" do
975
+ expect(tally).to eq("Atlantic" => 2, "Columbia" => 1)
939
976
  end
977
+ end
940
978
 
941
- let(:persons) do
942
- criteria.collect(&:person)
979
+ context "when tallying an element in an embeds_many field" do
980
+ let(:criteria) { Band.where(origin: "tally2") }
981
+
982
+ let(:tally) do
983
+ criteria.tally("fanatics.age")
943
984
  end
944
985
 
945
- it 'sets the same parent object on each related object' do
946
- expect(persons.uniq.size).to eq(1)
986
+ it "returns the correct hash" do
987
+ expect(tally).to eq(
988
+ [1, 2] => 2,
989
+ [1, 3] => 1
990
+ )
947
991
  end
948
992
  end
949
- end
950
993
 
951
- describe "#eager_load" do
994
+ context "when tallying an embeds_many field" do
995
+ let(:criteria) { Band.where(origin: "tally2") }
952
996
 
953
- let(:criteria) do
954
- Person.includes(:game)
955
- end
997
+ let(:tally) do
998
+ criteria.tally("fanatics")
999
+ end
956
1000
 
957
- let(:context) do
958
- described_class.new(criteria)
1001
+ it "returns the correct hash" do
1002
+ expect(tally).to eq(
1003
+ fans1.map(&:attributes) => 1,
1004
+ fans2.map(&:attributes) => 1,
1005
+ fans3.map(&:attributes) => 1,
1006
+ )
1007
+ end
959
1008
  end
960
1009
 
961
- context "when no documents are returned" do
1010
+ context "when tallying a field of type array" do
1011
+ let(:criteria) { Band.where(origin: "tally2") }
962
1012
 
963
- let(:game_association) do
964
- Person.reflect_on_association(:game)
1013
+ let(:tally) do
1014
+ criteria.tally("genres")
965
1015
  end
966
1016
 
967
- it "does not make any additional database queries" do
968
- expect(game_association).to receive(:eager_load).never
969
- context.send(:eager_load, [])
1017
+ it "returns the correct hash" do
1018
+ expect(tally).to eq(
1019
+ [1, 2] => 2,
1020
+ [1, 3] => 1
1021
+ )
970
1022
  end
971
1023
  end
972
- end
973
1024
 
974
- describe "#exists?" do
1025
+ context "when tallying an element from an array of hashes" do
1026
+ let(:criteria) { Band.where(origin: "tally") }
975
1027
 
976
- before do
977
- Band.create!(name: "Depeche Mode")
1028
+ let(:tally) do
1029
+ criteria.tally("genres.x")
1030
+ end
1031
+
1032
+ it "returns the correct hash without the nil keys" do
1033
+ expect(tally).to eq(
1034
+ [1, 2] => 2,
1035
+ [1, 3] => 1
1036
+ )
1037
+ end
978
1038
  end
979
1039
 
980
- context "when the count is zero" do
1040
+ context "when tallying an element from an array of hashes; with duplicate" do
981
1041
 
982
- let(:criteria) do
983
- Band.where(name: "New Order")
1042
+ before do
1043
+ Band.create!(origin: "tally", genres: [ { x: 1 }, {x: 1} ] )
984
1044
  end
985
1045
 
986
- let(:context) do
987
- described_class.new(criteria)
1046
+ let(:criteria) { Band.where(origin: "tally") }
1047
+
1048
+ let(:tally) do
1049
+ criteria.tally("genres.x")
988
1050
  end
989
1051
 
990
- it "returns false" do
991
- expect(context).to_not be_exists
1052
+ it "returns the correct hash without the nil keys" do
1053
+ expect(tally).to eq(
1054
+ [1, 2] => 2,
1055
+ [1, 3] => 1,
1056
+ [1, 1] => 1,
1057
+ )
992
1058
  end
993
1059
  end
994
1060
 
995
- context "when the count is greater than zero" do
1061
+ context "when tallying an aliased field of type array" do
996
1062
 
997
- let(:criteria) do
998
- Band.where(name: "Depeche Mode")
1063
+ before do
1064
+ Person.create!(array: [ 1, 2 ])
1065
+ Person.create!(array: [ 1, 3 ])
999
1066
  end
1000
1067
 
1001
- let(:context) do
1002
- described_class.new(criteria)
1068
+ let(:tally) do
1069
+ Person.tally("array")
1003
1070
  end
1004
1071
 
1005
- it "returns true" do
1006
- expect(context).to be_exists
1072
+ it "returns the correct hash" do
1073
+ expect(tally).to eq(
1074
+ [1, 2] => 1,
1075
+ [1, 3] => 1
1076
+ )
1007
1077
  end
1008
1078
  end
1009
1079
 
1010
- context "when caching is not enabled" do
1080
+ context "when going multiple levels deep in arrays" do
1081
+ let(:criteria) { Band.where(origin: "tally") }
1011
1082
 
1012
- let(:criteria) do
1013
- Band.where(name: "Depeche Mode")
1083
+ let(:tally) do
1084
+ criteria.tally("genres.y.z")
1014
1085
  end
1015
1086
 
1016
- let(:context) do
1017
- described_class.new(criteria)
1087
+ it "returns the correct hash" do
1088
+ expect(tally).to eq(
1089
+ [1, 2] => 2,
1090
+ [1, 3] => 1
1091
+ )
1018
1092
  end
1093
+ end
1019
1094
 
1020
- context "when exists? already called" do
1095
+ context "when going multiple levels deep in an array" do
1096
+ let(:criteria) { Band.where(origin: "tally") }
1021
1097
 
1022
- before do
1023
- context.exists?
1024
- end
1098
+ let(:tally) do
1099
+ criteria.tally("genres.y.z")
1100
+ end
1025
1101
 
1026
- it "hits the database again" do
1027
- expect(context).to receive(:view).once.and_call_original
1028
- expect(context).to be_exists
1029
- end
1102
+ it "returns the correct hash" do
1103
+ expect(tally).to eq(
1104
+ [1, 2] => 2,
1105
+ [1, 3] => 1
1106
+ )
1030
1107
  end
1031
1108
  end
1032
1109
 
1033
- context "when caching is enabled" do
1110
+ context "when tallying deeply nested arrays/embedded associations" do
1034
1111
 
1035
- let(:criteria) do
1036
- Band.where(name: "Depeche Mode").cache
1112
+ before do
1113
+ Person.create!(addresses: [ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 2 } } ]))) ])
1114
+ Person.create!(addresses: [ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 2 } } ]))) ])
1115
+ Person.create!(addresses: [ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 3 } } ]))) ])
1037
1116
  end
1038
1117
 
1039
- let(:context) do
1040
- described_class.new(criteria)
1118
+ let(:tally) do
1119
+ Person.tally("addresses.code.deepest.array.y.z")
1041
1120
  end
1042
1121
 
1043
- context "when the cache is loaded" do
1044
-
1045
- before do
1046
- context.to_a
1047
- end
1048
-
1049
- it "does not hit the database" do
1050
- expect(context).to receive(:view).never
1051
- expect(context).to be_exists
1052
- end
1122
+ it "returns the correct hash" do
1123
+ expect(tally).to eq(
1124
+ [ [ 1, 2 ] ] => 2,
1125
+ [ [ 1, 3 ] ] => 1
1126
+ )
1053
1127
  end
1128
+ end
1054
1129
 
1055
- context "when the cache is not loaded" do
1130
+ context "when tallying deeply nested arrays/embedded associations" do
1056
1131
 
1057
- context "when a count has been executed" do
1132
+ before do
1133
+ Person.create!(addresses: [ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 2 } } ]))),
1134
+ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 2 } } ]))) ])
1135
+ Person.create!(addresses: [ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 2 } } ]))),
1136
+ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 2 } } ]))) ])
1137
+ Person.create!(addresses: [ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 3 } } ]))),
1138
+ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 3 } } ]))) ])
1139
+ end
1058
1140
 
1059
- before do
1060
- context.count
1061
- end
1141
+ let(:tally) do
1142
+ Person.tally("addresses.code.deepest.array.y.z")
1143
+ end
1062
1144
 
1063
- it "does not hit the database" do
1064
- expect(context).to receive(:view).never
1065
- expect(context).to be_exists
1066
- end
1067
- end
1145
+ it "returns the correct hash" do
1146
+ expect(tally).to eq(
1147
+ [ [ 1, 2 ], [ 1, 2 ] ] => 2,
1148
+ [ [ 1, 3 ], [ 1, 3 ] ] => 1
1149
+ )
1068
1150
  end
1069
1151
  end
1070
- end
1071
1152
 
1072
- describe "#explain" do
1153
+ context "when some keys are missing" do
1154
+ before do
1155
+ 3.times { Band.create!(origin: "tally") }
1156
+ end
1073
1157
 
1074
- let(:criteria) do
1075
- Band.where(name: "Depeche Mode")
1076
- end
1158
+ let(:tally) do
1159
+ criteria.tally(:name)
1160
+ end
1077
1161
 
1078
- let(:context) do
1079
- described_class.new(criteria)
1080
- end
1081
-
1082
- it "returns the criteria explain path" do
1083
- expect(context.explain).to_not be_empty
1162
+ it "returns the correct hash" do
1163
+ expect(tally).to eq(
1164
+ "Depeche Mode" => 1,
1165
+ "New Order" => 1,
1166
+ "10,000 Maniacs" => 1,
1167
+ nil => 3
1168
+ )
1169
+ end
1084
1170
  end
1085
- end
1086
1171
 
1087
- describe "#find_one_and_replace" do
1172
+ context "when the first element is an embeds_one" do
1173
+ before do
1174
+ Person.create!(name: Name.new(translations: [ Translation.new(language: 1), Translation.new(language: 2) ]))
1175
+ Person.create!(name: Name.new(translations: [ Translation.new(language: 1), Translation.new(language: 2) ]))
1176
+ Person.create!(name: Name.new(translations: [ Translation.new(language: 1), Translation.new(language: 3) ]))
1177
+ end
1088
1178
 
1089
- let!(:depeche) do
1090
- Band.create!(name: "Depeche Mode")
1091
- end
1179
+ let(:tally) do
1180
+ Person.tally("name.translations.language")
1181
+ end
1092
1182
 
1093
- let!(:tool) do
1094
- Band.create!(name: "Tool")
1183
+ it "returns the correct hash" do
1184
+ expect(tally).to eq(
1185
+ [1, 2] => 2,
1186
+ [1, 3] => 1
1187
+ )
1188
+ end
1095
1189
  end
1096
1190
 
1097
- context "when the selector matches" do
1098
-
1099
- context "when not providing options" do
1191
+ context "when tallying demongoizable values from typeless fields" do
1100
1192
 
1101
- let(:criteria) do
1102
- Band.where(name: "Depeche Mode")
1103
- end
1193
+ let!(:person1) { Person.create!(ssn: /hello/) }
1194
+ let!(:person2) { Person.create!(ssn: BSON::Decimal128.new("1")) }
1195
+ let(:tally) { Person.tally("ssn") }
1104
1196
 
1105
- let(:context) do
1106
- described_class.new(criteria)
1197
+ let(:tallied_classes) do
1198
+ tally.keys.map(&:class).sort do |a, b|
1199
+ a.to_s.casecmp(b.to_s)
1107
1200
  end
1201
+ end
1108
1202
 
1109
- let!(:result) do
1110
- context.find_one_and_replace(name: 'FKA Twigs')
1111
- end
1203
+ context "< BSON 5" do
1204
+ max_bson_version '4.99.99'
1112
1205
 
1113
- it "returns the first matching document" do
1114
- expect(result).to eq(depeche)
1206
+ it "stores the correct types in the database" do
1207
+ expect(Person.find(person1.id).attributes["ssn"]).to be_a BSON::Regexp::Raw
1208
+ expect(Person.find(person2.id).attributes["ssn"]).to be_a BSON::Decimal128
1115
1209
  end
1116
1210
 
1117
- it "updates the document in the database" do
1118
- expect(depeche.reload.name).to eq('FKA Twigs')
1211
+ it "tallies the correct type" do
1212
+ expect(tallied_classes).to be == [ BSON::Decimal128, BSON::Regexp::Raw ]
1119
1213
  end
1120
1214
  end
1121
1215
 
1122
- context "when sorting" do
1216
+ context '>= BSON 5' do
1217
+ min_bson_version "5.0"
1123
1218
 
1124
- let(:criteria) do
1125
- Band.desc(:name)
1219
+ it "stores the correct types in the database" do
1220
+ expect(Person.find(person1.id).ssn).to be_a BSON::Regexp::Raw
1221
+ expect(Person.find(person2.id).ssn).to be_a BigDecimal
1126
1222
  end
1127
1223
 
1128
- let(:context) do
1129
- described_class.new(criteria)
1224
+ it "tallies the correct type" do
1225
+ expect(tallied_classes).to be == [ BigDecimal, BSON::Regexp::Raw ]
1130
1226
  end
1227
+ end
1131
1228
 
1132
- let!(:result) do
1133
- context.find_one_and_replace(likes: 1)
1134
- end
1229
+ context '>= BSON 5 with decimal128 allowed' do
1230
+ min_bson_version "5.0"
1231
+ config_override :allow_bson5_decimal128, true
1135
1232
 
1136
- it "returns the first matching document" do
1137
- expect(result).to eq(tool)
1233
+ it "stores the correct types in the database" do
1234
+ expect(Person.find(person1.id).ssn).to be_a BSON::Regexp::Raw
1235
+ expect(Person.find(person2.id).ssn).to be_a BSON::Decimal128
1138
1236
  end
1139
1237
 
1140
- it "updates the document in the database" do
1141
- expect(tool.reload.likes).to eq(1)
1142
- expect(tool.reload.name).to be_nil
1238
+ it "tallies the correct type" do
1239
+ expect(tallied_classes).to be == [ BSON::Decimal128, BSON::Regexp::Raw ]
1143
1240
  end
1144
1241
  end
1242
+ end
1243
+ end
1145
1244
 
1146
- context "when limiting fields" do
1245
+ describe "#each" do
1147
1246
 
1148
- let(:criteria) do
1149
- Band.only(:_id)
1150
- end
1247
+ before do
1248
+ Band.create!(name: "Depeche Mode")
1249
+ end
1151
1250
 
1152
- let(:context) do
1153
- described_class.new(criteria)
1154
- end
1251
+ let(:criteria) do
1252
+ Band.where(name: "Depeche Mode")
1253
+ end
1155
1254
 
1156
- let!(:result) do
1157
- context.find_one_and_replace(name: 'FKA Twigs', likes: 1)
1158
- end
1255
+ let(:context) do
1256
+ described_class.new(criteria)
1257
+ end
1159
1258
 
1160
- it "returns the first matching document" do
1161
- expect(result).to eq(depeche)
1162
- end
1259
+ context 'when the criteria has a collation' do
1260
+ min_server_version '3.4'
1163
1261
 
1164
- it "limits the returned fields" do
1165
- expect(result.name).to be_nil
1262
+ let(:criteria) do
1263
+ Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2)
1264
+ end
1265
+
1266
+ it "yields mongoid documents to the block" do
1267
+ context.each do |doc|
1268
+ expect(doc).to be_a(Mongoid::Document)
1166
1269
  end
1270
+ end
1167
1271
 
1168
- it "updates the document in the database" do
1169
- expect(depeche.reload.likes).to eq(1)
1272
+ it "iterates over the matching documents" do
1273
+ context.each do |doc|
1274
+ expect(doc.name).to eq("Depeche Mode")
1170
1275
  end
1171
1276
  end
1172
1277
 
1173
- context "when returning new" do
1278
+ it "returns self" do
1279
+ expect(context.each{}).to be(context)
1280
+ end
1281
+ end
1174
1282
 
1175
- let(:criteria) do
1176
- Band.where(name: "Depeche Mode")
1177
- end
1283
+ context "when providing a block" do
1178
1284
 
1179
- let(:context) do
1180
- described_class.new(criteria)
1285
+ it "yields mongoid documents to the block" do
1286
+ context.each do |doc|
1287
+ expect(doc).to be_a(Mongoid::Document)
1181
1288
  end
1289
+ end
1182
1290
 
1183
- let!(:result) do
1184
- context.find_one_and_replace({ likes: 1 }, return_document: :after)
1291
+ it "iterates over the matching documents" do
1292
+ context.each do |doc|
1293
+ expect(doc.name).to eq("Depeche Mode")
1185
1294
  end
1295
+ end
1186
1296
 
1187
- it "returns the first matching document" do
1188
- expect(result).to eq(depeche)
1189
- end
1297
+ it "returns self" do
1298
+ expect(context.each{}).to be(context)
1299
+ end
1300
+ end
1190
1301
 
1191
- it "returns the updated document" do
1192
- expect(result.name).to be_nil
1193
- expect(result.likes).to eq(1)
1194
- end
1302
+ context "when no block is provided" do
1303
+
1304
+ let(:enum) do
1305
+ context.each
1195
1306
  end
1196
1307
 
1197
- context 'when a collation is specified on the criteria' do
1198
- min_server_version '3.4'
1308
+ it "returns an enumerator" do
1309
+ expect(enum).to be_a(Enumerator)
1310
+ end
1199
1311
 
1200
- let(:criteria) do
1201
- Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2)
1202
- end
1312
+ context "when iterating over the enumerator" do
1203
1313
 
1204
- let(:context) do
1205
- described_class.new(criteria)
1206
- end
1314
+ context "when iterating with each" do
1207
1315
 
1208
- let!(:result) do
1209
- context.find_one_and_replace({ likes: 1 }, return_document: :after)
1316
+ it "yields mongoid documents to the block" do
1317
+ enum.each do |doc|
1318
+ expect(doc).to be_a(Mongoid::Document)
1319
+ end
1320
+ end
1210
1321
  end
1211
1322
 
1212
- it "returns the first matching document" do
1213
- expect(result).to eq(depeche)
1214
- end
1323
+ context "when iterating with next" do
1215
1324
 
1216
- it "returns the updated document" do
1217
- expect(result.likes).to eq(1)
1218
- expect(result.name).to be_nil
1325
+ before do
1326
+ 10.times { |i| Band.create!(name: "Test #{i}") }
1327
+ end
1328
+
1329
+ let(:criteria) do
1330
+ Band.batch_size(5)
1331
+ end
1332
+
1333
+ it "yields mongoid documents" do
1334
+ expect(enum.next).to be_a(Mongoid::Document)
1335
+ end
1336
+
1337
+ it "does not load all documents" do
1338
+ subscriber = Mrss::EventSubscriber.new
1339
+ context.view.client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
1340
+
1341
+ enum.next
1342
+
1343
+ find_events = subscriber.all_events.select do |evt|
1344
+ evt.command_name == 'find'
1345
+ end
1346
+ expect(find_events.length).to be(2)
1347
+ get_more_events = subscriber.all_events.select do |evt|
1348
+ evt.command_name == 'getMore'
1349
+ end
1350
+ expect(get_more_events.length).to be(0)
1351
+ ensure
1352
+ context.view.client.unsubscribe(Mongo::Monitoring::COMMAND, subscriber)
1353
+ end
1219
1354
  end
1220
1355
  end
1221
1356
  end
1222
1357
 
1223
- context "when the selector does not match" do
1358
+ context 'when the criteria has a parent document' do
1224
1359
 
1225
- let(:criteria) do
1226
- Band.where(name: "DEPECHE MODE")
1360
+ before do
1361
+ Post.create!(person: person)
1362
+ Post.create!(person: person)
1363
+ Post.create!(person: person)
1227
1364
  end
1228
1365
 
1229
- let(:context) do
1230
- described_class.new(criteria)
1366
+ let(:person) do
1367
+ Person.new
1231
1368
  end
1232
1369
 
1233
- let(:result) do
1234
- context.find_one_and_replace(name: 'FKA Twigs')
1370
+ let(:criteria) do
1371
+ person.posts.all
1235
1372
  end
1236
1373
 
1237
- it "returns nil" do
1238
- expect(result).to be_nil
1374
+ let(:persons) do
1375
+ criteria.collect(&:person)
1376
+ end
1377
+
1378
+ it 'sets the same parent object on each related object' do
1379
+ expect(persons.uniq.size).to eq(1)
1239
1380
  end
1240
1381
  end
1241
1382
  end
1242
1383
 
1243
- describe "#find_one_and_update" do
1384
+ describe "#eager_load" do
1244
1385
 
1245
- let!(:depeche) do
1246
- Band.create!(name: "Depeche Mode")
1386
+ let(:criteria) do
1387
+ Person.includes(:game)
1247
1388
  end
1248
1389
 
1249
- let!(:tool) do
1250
- Band.create!(name: "Tool")
1390
+ let(:context) do
1391
+ described_class.new(criteria)
1251
1392
  end
1252
1393
 
1253
- context "when the selector matches" do
1394
+ context "when no documents are returned" do
1254
1395
 
1255
- context "when not providing options" do
1396
+ let(:game_association) do
1397
+ Person.reflect_on_association(:game)
1398
+ end
1399
+
1400
+ it "does not make any additional database queries" do
1401
+ expect(game_association).to receive(:eager_load).never
1402
+ context.send(:eager_load, [])
1403
+ end
1404
+ end
1405
+ end
1406
+
1407
+ describe "#exists?" do
1408
+
1409
+ let!(:band) do
1410
+ Band.create!(name: "Depeche Mode", active: true)
1411
+ end
1412
+
1413
+ context "when not passing options" do
1414
+
1415
+ context "when the count is zero" do
1256
1416
 
1257
1417
  let(:criteria) do
1258
- Band.where(name: "Depeche Mode")
1418
+ Band.where(name: "New Order")
1259
1419
  end
1260
1420
 
1261
1421
  let(:context) do
1262
1422
  described_class.new(criteria)
1263
1423
  end
1264
1424
 
1265
- let!(:result) do
1266
- context.find_one_and_update("$inc" => { likes: 1 })
1267
- end
1268
-
1269
- it "returns the first matching document" do
1270
- expect(result).to eq(depeche)
1271
- end
1272
-
1273
- it "updates the document in the database" do
1274
- expect(depeche.reload.likes).to eq(1)
1425
+ it "returns false" do
1426
+ expect(context).to_not be_exists
1275
1427
  end
1276
1428
  end
1277
1429
 
1278
- context "when sorting" do
1430
+ context "when the count is greater than zero" do
1279
1431
 
1280
1432
  let(:criteria) do
1281
- Band.desc(:name)
1433
+ Band.where(name: "Depeche Mode")
1282
1434
  end
1283
1435
 
1284
1436
  let(:context) do
1285
1437
  described_class.new(criteria)
1286
1438
  end
1287
1439
 
1288
- let!(:result) do
1289
- context.find_one_and_update("$inc" => { likes: 1 })
1440
+ it "returns true" do
1441
+ expect(context).to be_exists
1442
+ end
1443
+ end
1444
+
1445
+ context "when caching is not enabled" do
1446
+
1447
+ let(:criteria) do
1448
+ Band.where(name: "Depeche Mode")
1449
+ end
1450
+
1451
+ let(:context) do
1452
+ described_class.new(criteria)
1453
+ end
1454
+
1455
+ context "when exists? already called and query cache is enabled" do
1456
+ query_cache_enabled
1457
+
1458
+ before do
1459
+ context.exists?
1460
+ end
1461
+
1462
+ it "does not hit the database again" do
1463
+ expect_no_queries do
1464
+ expect(context).to be_exists
1465
+ end
1466
+ end
1467
+ end
1468
+ end
1469
+ end
1470
+
1471
+ context "when passing an _id" do
1472
+
1473
+ context "when its of type BSON::ObjectId" do
1474
+
1475
+ context "when calling it on the class" do
1476
+
1477
+ it "returns true" do
1478
+ expect(Band.exists?(band._id)).to be true
1479
+ end
1480
+ end
1481
+
1482
+ context "when calling it on a criteria that includes the object" do
1483
+
1484
+ it "returns true" do
1485
+ expect(Band.where(name: band.name).exists?(band._id)).to be true
1486
+ end
1487
+ end
1488
+
1489
+ context "when calling it on a criteria that does not include the object" do
1490
+
1491
+ it "returns false" do
1492
+ expect(Band.where(name: "bogus").exists?(band._id)).to be false
1493
+ end
1494
+ end
1495
+
1496
+ context "when the id does not exist" do
1497
+
1498
+ it "returns false" do
1499
+ expect(Band.exists?(BSON::ObjectId.new)).to be false
1500
+ end
1501
+ end
1502
+ end
1503
+
1504
+ context "when its of type String" do
1505
+
1506
+ context "when the id exists" do
1507
+
1508
+ it "returns true" do
1509
+ expect(Band.exists?(band._id.to_s)).to be true
1510
+ end
1511
+ end
1512
+
1513
+ context "when the id does not exist" do
1514
+
1515
+ it "returns false" do
1516
+ expect(Band.exists?(BSON::ObjectId.new.to_s)).to be false
1517
+ end
1518
+ end
1519
+ end
1520
+ end
1521
+
1522
+ context "when passing a hash" do
1523
+
1524
+ context "when calling it on the class" do
1525
+
1526
+ it "returns true" do
1527
+ expect(Band.exists?(name: band.name)).to be true
1528
+ end
1529
+ end
1530
+
1531
+ context "when calling it on a criteria that includes the object" do
1532
+
1533
+ it "returns true" do
1534
+ expect(Band.where(active: true).exists?(name: band.name)).to be true
1535
+ end
1536
+ end
1537
+
1538
+ context "when calling it on a criteria that does not include the object" do
1539
+
1540
+ it "returns false" do
1541
+ expect(Band.where(active: false).exists?(name: band.name)).to be false
1542
+ end
1543
+ end
1544
+
1545
+ context "when the conditions don't match" do
1546
+
1547
+ it "returns false" do
1548
+ expect(Band.exists?(name: "bogus")).to be false
1549
+ end
1550
+ end
1551
+ end
1552
+
1553
+ context "when passing false" do
1554
+
1555
+ it "returns false" do
1556
+ expect(Band.exists?(false)).to be false
1557
+ end
1558
+ end
1559
+
1560
+ context "when passing nil" do
1561
+
1562
+ it "returns false" do
1563
+ expect(Band.exists?(nil)).to be false
1564
+ end
1565
+ end
1566
+
1567
+ context "when the limit is 0" do
1568
+
1569
+ it "returns false" do
1570
+ expect(Band.limit(0).exists?).to be false
1571
+ end
1572
+ end
1573
+
1574
+ context "when the criteria limit is 0" do
1575
+
1576
+ it "returns false" do
1577
+ expect(Band.criteria.limit(0).exists?).to be false
1578
+ end
1579
+ end
1580
+ end
1581
+
1582
+ describe "#explain" do
1583
+
1584
+ let(:criteria) do
1585
+ Band.where(name: "Depeche Mode")
1586
+ end
1587
+
1588
+ let(:context) do
1589
+ described_class.new(criteria)
1590
+ end
1591
+
1592
+ it "returns the criteria explain path" do
1593
+ explain = context.explain
1594
+ expect(explain).to_not be_empty
1595
+ expect(explain.keys).to include("queryPlanner", "executionStats", "serverInfo")
1596
+ end
1597
+
1598
+ it "respects options passed to explain" do
1599
+ explain = context.explain(verbosity: :query_planner)
1600
+ expect(explain).to_not be_empty
1601
+ expect(explain.keys).to include("queryPlanner", "serverInfo")
1602
+ expect(explain.keys).not_to include("executionStats")
1603
+ end
1604
+ end
1605
+
1606
+ describe "#find_one_and_replace" do
1607
+
1608
+ let!(:depeche) do
1609
+ Band.create!(name: "Depeche Mode")
1610
+ end
1611
+
1612
+ let!(:tool) do
1613
+ Band.create!(name: "Tool")
1614
+ end
1615
+
1616
+ context "when the selector matches" do
1617
+
1618
+ context "when not providing options" do
1619
+
1620
+ let(:criteria) do
1621
+ Band.where(name: "Depeche Mode")
1622
+ end
1623
+
1624
+ let(:context) do
1625
+ described_class.new(criteria)
1626
+ end
1627
+
1628
+ let!(:result) do
1629
+ context.find_one_and_replace(name: 'FKA Twigs')
1630
+ end
1631
+
1632
+ it "returns the first matching document" do
1633
+ expect(result).to eq(depeche)
1634
+ end
1635
+
1636
+ it "updates the document in the database" do
1637
+ expect(depeche.reload.name).to eq('FKA Twigs')
1638
+ end
1639
+ end
1640
+
1641
+ context "when sorting" do
1642
+
1643
+ let(:criteria) do
1644
+ Band.desc(:name)
1645
+ end
1646
+
1647
+ let(:context) do
1648
+ described_class.new(criteria)
1649
+ end
1650
+
1651
+ let!(:result) do
1652
+ context.find_one_and_replace(likes: 1)
1290
1653
  end
1291
1654
 
1292
1655
  it "returns the first matching document" do
@@ -1295,6 +1658,7 @@ describe Mongoid::Contextual::Mongo do
1295
1658
 
1296
1659
  it "updates the document in the database" do
1297
1660
  expect(tool.reload.likes).to eq(1)
1661
+ expect(tool.reload.name).to be_nil
1298
1662
  end
1299
1663
  end
1300
1664
 
@@ -1309,7 +1673,7 @@ describe Mongoid::Contextual::Mongo do
1309
1673
  end
1310
1674
 
1311
1675
  let!(:result) do
1312
- context.find_one_and_update("$inc" => { likes: 1 })
1676
+ context.find_one_and_replace(name: 'FKA Twigs', likes: 1)
1313
1677
  end
1314
1678
 
1315
1679
  it "returns the first matching document" do
@@ -1336,7 +1700,7 @@ describe Mongoid::Contextual::Mongo do
1336
1700
  end
1337
1701
 
1338
1702
  let!(:result) do
1339
- context.find_one_and_update({ "$inc" => { likes: 1 }}, return_document: :after)
1703
+ context.find_one_and_replace({ likes: 1 }, return_document: :after)
1340
1704
  end
1341
1705
 
1342
1706
  it "returns the first matching document" do
@@ -1344,6 +1708,7 @@ describe Mongoid::Contextual::Mongo do
1344
1708
  end
1345
1709
 
1346
1710
  it "returns the updated document" do
1711
+ expect(result.name).to be_nil
1347
1712
  expect(result.likes).to eq(1)
1348
1713
  end
1349
1714
  end
@@ -1360,7 +1725,7 @@ describe Mongoid::Contextual::Mongo do
1360
1725
  end
1361
1726
 
1362
1727
  let!(:result) do
1363
- context.find_one_and_update({ "$inc" => { likes: 1 }}, return_document: :after)
1728
+ context.find_one_and_replace({ likes: 1 }, return_document: :after)
1364
1729
  end
1365
1730
 
1366
1731
  it "returns the first matching document" do
@@ -1369,6 +1734,7 @@ describe Mongoid::Contextual::Mongo do
1369
1734
 
1370
1735
  it "returns the updated document" do
1371
1736
  expect(result.likes).to eq(1)
1737
+ expect(result.name).to be_nil
1372
1738
  end
1373
1739
  end
1374
1740
  end
@@ -1376,7 +1742,7 @@ describe Mongoid::Contextual::Mongo do
1376
1742
  context "when the selector does not match" do
1377
1743
 
1378
1744
  let(:criteria) do
1379
- Band.where(name: "Placebo")
1745
+ Band.where(name: "DEPECHE MODE")
1380
1746
  end
1381
1747
 
1382
1748
  let(:context) do
@@ -1384,7 +1750,7 @@ describe Mongoid::Contextual::Mongo do
1384
1750
  end
1385
1751
 
1386
1752
  let(:result) do
1387
- context.find_one_and_update("$inc" => { likes: 1 })
1753
+ context.find_one_and_replace(name: 'FKA Twigs')
1388
1754
  end
1389
1755
 
1390
1756
  it "returns nil" do
@@ -1393,41 +1759,22 @@ describe Mongoid::Contextual::Mongo do
1393
1759
  end
1394
1760
  end
1395
1761
 
1396
- describe "#find_one_and_delete" do
1762
+ describe "#find_one_and_update" do
1397
1763
 
1398
1764
  let!(:depeche) do
1399
1765
  Band.create!(name: "Depeche Mode")
1400
1766
  end
1401
1767
 
1402
- let(:criteria) do
1403
- Band.where(name: "Depeche Mode")
1404
- end
1405
-
1406
- let(:context) do
1407
- described_class.new(criteria)
1408
- end
1409
-
1410
- let!(:result) do
1411
- context.find_one_and_delete
1768
+ let!(:tool) do
1769
+ Band.create!(name: "Tool")
1412
1770
  end
1413
1771
 
1414
- context 'when the selector matches a document' do
1415
-
1416
- it "returns the first matching document" do
1417
- expect(result).to eq(depeche)
1418
- end
1419
-
1420
- it "deletes the document from the database" do
1421
- expect {
1422
- depeche.reload
1423
- }.to raise_error(Mongoid::Errors::DocumentNotFound)
1424
- end
1772
+ context "when the selector matches" do
1425
1773
 
1426
- context 'when a collation is specified on the criteria' do
1427
- min_server_version '3.4'
1774
+ context "when not providing options" do
1428
1775
 
1429
1776
  let(:criteria) do
1430
- Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2)
1777
+ Band.where(name: "Depeche Mode")
1431
1778
  end
1432
1779
 
1433
1780
  let(:context) do
@@ -1435,72 +1782,244 @@ describe Mongoid::Contextual::Mongo do
1435
1782
  end
1436
1783
 
1437
1784
  let!(:result) do
1438
- context.find_one_and_delete
1785
+ context.find_one_and_update("$inc" => { likes: 1 })
1439
1786
  end
1440
1787
 
1441
1788
  it "returns the first matching document" do
1442
1789
  expect(result).to eq(depeche)
1443
1790
  end
1444
1791
 
1445
- it "deletes the document from the database" do
1446
- expect {
1447
- depeche.reload
1448
- }.to raise_error(Mongoid::Errors::DocumentNotFound)
1792
+ it "updates the document in the database" do
1793
+ expect(depeche.reload.likes).to eq(1)
1449
1794
  end
1450
1795
  end
1451
- end
1452
-
1453
- context 'when the selector does not match a document' do
1454
-
1455
- let(:criteria) do
1456
- Band.where(name: "Placebo")
1457
- end
1458
-
1459
- let(:context) do
1460
- described_class.new(criteria)
1461
- end
1462
-
1463
- let(:result) do
1464
- context.find_one_and_delete
1465
- end
1466
-
1467
- it "returns nil" do
1468
- expect(result).to be_nil
1469
- end
1470
- end
1471
- end
1472
-
1473
- [ :first, :one ].each do |method|
1474
-
1475
- describe "##{method}" do
1476
-
1477
- let!(:depeche_mode) do
1478
- Band.create!(name: "Depeche Mode")
1479
- end
1480
-
1481
- let!(:new_order) do
1482
- Band.create!(name: "New Order")
1483
- end
1484
-
1485
- let!(:rolling_stones) do
1486
- Band.create!(name: "The Rolling Stones")
1487
- end
1488
1796
 
1489
- context "when the context is not cached" do
1797
+ context "when sorting" do
1490
1798
 
1491
1799
  let(:criteria) do
1492
- Band.where(name: "Depeche Mode")
1800
+ Band.desc(:name)
1493
1801
  end
1494
1802
 
1495
1803
  let(:context) do
1496
1804
  described_class.new(criteria)
1497
1805
  end
1498
1806
 
1499
- it "returns the first matching document" do
1500
- expect(context.send(method)).to eq(depeche_mode)
1807
+ let!(:result) do
1808
+ context.find_one_and_update("$inc" => { likes: 1 })
1501
1809
  end
1502
1810
 
1503
- context 'when the criteria has a collation' do
1811
+ it "returns the first matching document" do
1812
+ expect(result).to eq(tool)
1813
+ end
1814
+
1815
+ it "updates the document in the database" do
1816
+ expect(tool.reload.likes).to eq(1)
1817
+ end
1818
+ end
1819
+
1820
+ context "when limiting fields" do
1821
+
1822
+ let(:criteria) do
1823
+ Band.only(:_id)
1824
+ end
1825
+
1826
+ let(:context) do
1827
+ described_class.new(criteria)
1828
+ end
1829
+
1830
+ let!(:result) do
1831
+ context.find_one_and_update("$inc" => { likes: 1 })
1832
+ end
1833
+
1834
+ it "returns the first matching document" do
1835
+ expect(result).to eq(depeche)
1836
+ end
1837
+
1838
+ it "limits the returned fields" do
1839
+ expect(result.name).to be_nil
1840
+ end
1841
+
1842
+ it "updates the document in the database" do
1843
+ expect(depeche.reload.likes).to eq(1)
1844
+ end
1845
+ end
1846
+
1847
+ context "when returning new" do
1848
+
1849
+ let(:criteria) do
1850
+ Band.where(name: "Depeche Mode")
1851
+ end
1852
+
1853
+ let(:context) do
1854
+ described_class.new(criteria)
1855
+ end
1856
+
1857
+ let!(:result) do
1858
+ context.find_one_and_update({ "$inc" => { likes: 1 }}, return_document: :after)
1859
+ end
1860
+
1861
+ it "returns the first matching document" do
1862
+ expect(result).to eq(depeche)
1863
+ end
1864
+
1865
+ it "returns the updated document" do
1866
+ expect(result.likes).to eq(1)
1867
+ end
1868
+ end
1869
+
1870
+ context 'when a collation is specified on the criteria' do
1871
+ min_server_version '3.4'
1872
+
1873
+ let(:criteria) do
1874
+ Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2)
1875
+ end
1876
+
1877
+ let(:context) do
1878
+ described_class.new(criteria)
1879
+ end
1880
+
1881
+ let!(:result) do
1882
+ context.find_one_and_update({ "$inc" => { likes: 1 }}, return_document: :after)
1883
+ end
1884
+
1885
+ it "returns the first matching document" do
1886
+ expect(result).to eq(depeche)
1887
+ end
1888
+
1889
+ it "returns the updated document" do
1890
+ expect(result.likes).to eq(1)
1891
+ end
1892
+ end
1893
+ end
1894
+
1895
+ context "when the selector does not match" do
1896
+
1897
+ let(:criteria) do
1898
+ Band.where(name: "Placebo")
1899
+ end
1900
+
1901
+ let(:context) do
1902
+ described_class.new(criteria)
1903
+ end
1904
+
1905
+ let(:result) do
1906
+ context.find_one_and_update("$inc" => { likes: 1 })
1907
+ end
1908
+
1909
+ it "returns nil" do
1910
+ expect(result).to be_nil
1911
+ end
1912
+ end
1913
+ end
1914
+
1915
+ describe "#find_one_and_delete" do
1916
+
1917
+ let!(:depeche) do
1918
+ Band.create!(name: "Depeche Mode")
1919
+ end
1920
+
1921
+ let(:criteria) do
1922
+ Band.where(name: "Depeche Mode")
1923
+ end
1924
+
1925
+ let(:context) do
1926
+ described_class.new(criteria)
1927
+ end
1928
+
1929
+ let!(:result) do
1930
+ context.find_one_and_delete
1931
+ end
1932
+
1933
+ context 'when the selector matches a document' do
1934
+
1935
+ it "returns the first matching document" do
1936
+ expect(result).to eq(depeche)
1937
+ end
1938
+
1939
+ it "deletes the document from the database" do
1940
+ expect {
1941
+ depeche.reload
1942
+ }.to raise_error(Mongoid::Errors::DocumentNotFound, /Document\(s\) not found for class Band with id\(s\)/)
1943
+ end
1944
+
1945
+ context 'when a collation is specified on the criteria' do
1946
+ min_server_version '3.4'
1947
+
1948
+ let(:criteria) do
1949
+ Band.where(name: "DEPECHE MODE").collation(locale: 'en_US', strength: 2)
1950
+ end
1951
+
1952
+ let(:context) do
1953
+ described_class.new(criteria)
1954
+ end
1955
+
1956
+ let!(:result) do
1957
+ context.find_one_and_delete
1958
+ end
1959
+
1960
+ it "returns the first matching document" do
1961
+ expect(result).to eq(depeche)
1962
+ end
1963
+
1964
+ it "deletes the document from the database" do
1965
+ expect {
1966
+ depeche.reload
1967
+ }.to raise_error(Mongoid::Errors::DocumentNotFound, /Document\(s\) not found for class Band with id\(s\)/)
1968
+ end
1969
+ end
1970
+ end
1971
+
1972
+ context 'when the selector does not match a document' do
1973
+
1974
+ let(:criteria) do
1975
+ Band.where(name: "Placebo")
1976
+ end
1977
+
1978
+ let(:context) do
1979
+ described_class.new(criteria)
1980
+ end
1981
+
1982
+ let(:result) do
1983
+ context.find_one_and_delete
1984
+ end
1985
+
1986
+ it "returns nil" do
1987
+ expect(result).to be_nil
1988
+ end
1989
+ end
1990
+ end
1991
+
1992
+ [ :first, :one ].each do |method|
1993
+
1994
+ describe "##{method}" do
1995
+
1996
+ let!(:depeche_mode) do
1997
+ Band.create!(name: "Depeche Mode")
1998
+ end
1999
+
2000
+ let!(:new_order) do
2001
+ Band.create!(name: "New Order")
2002
+ end
2003
+
2004
+ let!(:rolling_stones) do
2005
+ Band.create!(name: "The Rolling Stones")
2006
+ end
2007
+
2008
+ context "when the context is not cached" do
2009
+
2010
+ let(:criteria) do
2011
+ Band.where(name: "Depeche Mode")
2012
+ end
2013
+
2014
+ let(:context) do
2015
+ described_class.new(criteria)
2016
+ end
2017
+
2018
+ it "returns the first matching document" do
2019
+ expect(context.send(method)).to eq(depeche_mode)
2020
+ end
2021
+
2022
+ context 'when the criteria has a collation' do
1504
2023
  min_server_version '3.4'
1505
2024
 
1506
2025
  let(:criteria) do
@@ -1554,31 +2073,13 @@ describe Mongoid::Contextual::Mongo do
1554
2073
  expect(context.send(method)).to eq(depeche_mode)
1555
2074
  end
1556
2075
 
1557
- context "when calling ##{method}" do
2076
+ context 'when calling #last' do
1558
2077
 
1559
- it 'returns the requested document, sorted by _id' do
2078
+ it 'returns the last document, sorted by _id' do
1560
2079
  expect(context.send(method)).to eq(depeche_mode)
1561
2080
  expect(context.last).to eq(rolling_stones)
1562
2081
  end
1563
2082
  end
1564
-
1565
- context 'with option { id_sort: :none }' do
1566
- let(:opts) do
1567
- { id_sort: :none }
1568
- end
1569
-
1570
- it 'applies the sort on _id' do
1571
- expect(context.send(method, opts)).to eq(depeche_mode)
1572
- end
1573
-
1574
- context "when calling ##{method}" do
1575
-
1576
- it 'doesn\'t apply a sort on _id' do
1577
- expect(context.send(method, opts)).to eq(depeche_mode)
1578
- expect(context.last(opts)).to eq(depeche_mode)
1579
- end
1580
- end
1581
- end
1582
2083
  end
1583
2084
 
1584
2085
  context 'when the criteria has a sort' do
@@ -1602,25 +2103,6 @@ describe Mongoid::Contextual::Mongo do
1602
2103
  expect(context.last).to eq(depeche_mode)
1603
2104
  end
1604
2105
  end
1605
-
1606
- context 'with option { id_sort: :none }' do
1607
-
1608
- let(:opts) do
1609
- { id_sort: :none }
1610
- end
1611
-
1612
- it 'uses the preexisting sort' do
1613
- expect(context.send(method, opts)).to eq(rolling_stones)
1614
- end
1615
-
1616
- context 'when calling #last' do
1617
-
1618
- it 'uses the preexisting sort' do
1619
- expect(context.send(method, opts)).to eq(rolling_stones)
1620
- expect(context.last(opts)).to eq(depeche_mode)
1621
- end
1622
- end
1623
- end
1624
2106
  end
1625
2107
 
1626
2108
  context "when using .sort" do
@@ -1649,28 +2131,17 @@ describe Mongoid::Contextual::Mongo do
1649
2131
  end
1650
2132
  end
1651
2133
 
1652
- context "when the context is cached" do
2134
+ context "when the query cache is enabled" do
2135
+ query_cache_enabled
1653
2136
 
1654
2137
  let(:criteria) do
1655
- Band.where(name: "Depeche Mode").cache
2138
+ Band.where(name: "Depeche Mode")
1656
2139
  end
1657
2140
 
1658
2141
  let(:context) do
1659
2142
  described_class.new(criteria)
1660
2143
  end
1661
2144
 
1662
- context "when the cache is loaded" do
1663
-
1664
- before do
1665
- context.to_a
1666
- end
1667
-
1668
- it "returns the first document without touching the database" do
1669
- expect(context).to receive(:view).never
1670
- expect(context.send(method)).to eq(depeche_mode)
1671
- end
1672
- end
1673
-
1674
2145
  context "when first method was called before" do
1675
2146
 
1676
2147
  before do
@@ -1678,8 +2149,9 @@ describe Mongoid::Contextual::Mongo do
1678
2149
  end
1679
2150
 
1680
2151
  it "returns the first document without touching the database" do
1681
- expect(context).to receive(:view).never
1682
- expect(context.send(method)).to eq(depeche_mode)
2152
+ expect_no_queries do
2153
+ expect(context.send(method)).to eq(depeche_mode)
2154
+ end
1683
2155
  end
1684
2156
  end
1685
2157
  end
@@ -1733,86 +2205,30 @@ describe Mongoid::Contextual::Mongo do
1733
2205
  end
1734
2206
  end
1735
2207
 
1736
- context "when the context is cached" do
2208
+ context "when the query cache is enabled" do
1737
2209
 
1738
2210
  let(:context) do
1739
2211
  described_class.new(criteria)
1740
2212
  end
1741
2213
 
1742
- context "when the whole context is loaded" do
2214
+ context "when calling first beforehand" do
2215
+ query_cache_enabled
1743
2216
 
1744
- before do
1745
- context.to_a
2217
+ let(:context) do
2218
+ described_class.new(criteria)
1746
2219
  end
1747
2220
 
1748
- context "when all of the documents are cached" do
2221
+ let(:criteria) do
2222
+ Band.all
2223
+ end
1749
2224
 
1750
- let(:criteria) do
1751
- Band.all.cache
1752
- end
2225
+ before do
2226
+ context.first(before_limit)
2227
+ end
1753
2228
 
1754
- context "when requesting all of the documents" do
1755
-
1756
- let(:docs) do
1757
- context.send(method, 3)
1758
- end
1759
-
1760
- it "returns all of the documents without touching the database" do
1761
- expect(context).to receive(:view).never
1762
- expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
1763
- end
1764
- end
1765
-
1766
- context "when requesting fewer than all of the documents" do
1767
-
1768
- let(:docs) do
1769
- context.send(method, 2)
1770
- end
1771
-
1772
- it "returns all of the documents without touching the database" do
1773
- expect(context).to receive(:view).never
1774
- expect(docs).to eq([ depeche_mode, new_order ])
1775
- end
1776
- end
1777
- end
1778
-
1779
- context "when only one document is cached" do
1780
-
1781
- let(:criteria) do
1782
- Band.where(name: "Depeche Mode").cache
1783
- end
1784
-
1785
- context "when requesting one document" do
1786
-
1787
- let(:docs) do
1788
- context.send(method, 1)
1789
- end
1790
-
1791
- it "returns one document without touching the database" do
1792
- expect(context).to receive(:view).never
1793
- expect(docs).to eq([ depeche_mode ])
1794
- end
1795
- end
1796
- end
1797
- end
1798
-
1799
- context "when the first method was called before" do
1800
-
1801
- let(:context) do
1802
- described_class.new(criteria)
1803
- end
1804
-
1805
- let(:criteria) do
1806
- Band.all.cache
1807
- end
1808
-
1809
- before do
1810
- context.first(before_limit)
1811
- end
1812
-
1813
- let(:docs) do
1814
- context.send(method, limit)
1815
- end
2229
+ let(:docs) do
2230
+ context.send(method, limit)
2231
+ end
1816
2232
 
1817
2233
  context "when getting all of the documents before" do
1818
2234
  let(:before_limit) { 3 }
@@ -1821,8 +2237,9 @@ describe Mongoid::Contextual::Mongo do
1821
2237
  let(:limit) { 3 }
1822
2238
 
1823
2239
  it "returns all documents without touching the database" do
1824
- expect(context).to receive(:view).never
1825
- expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2240
+ expect_no_queries do
2241
+ expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2242
+ end
1826
2243
  end
1827
2244
  end
1828
2245
 
@@ -1830,8 +2247,9 @@ describe Mongoid::Contextual::Mongo do
1830
2247
  let(:limit) { 2 }
1831
2248
 
1832
2249
  it "returns the correct documents without touching the database" do
1833
- expect(context).to receive(:view).never
1834
- expect(docs).to eq([ depeche_mode, new_order ])
2250
+ expect_no_queries do
2251
+ expect(docs).to eq([ depeche_mode, new_order ])
2252
+ end
1835
2253
  end
1836
2254
  end
1837
2255
  end
@@ -1843,8 +2261,9 @@ describe Mongoid::Contextual::Mongo do
1843
2261
  let(:limit) { 2 }
1844
2262
 
1845
2263
  it "returns the correct documents without touching the database" do
1846
- expect(context).to receive(:view).never
1847
- expect(docs).to eq([ depeche_mode, new_order ])
2264
+ expect_no_queries do
2265
+ expect(docs).to eq([ depeche_mode, new_order ])
2266
+ end
1848
2267
  end
1849
2268
  end
1850
2269
 
@@ -1852,8 +2271,9 @@ describe Mongoid::Contextual::Mongo do
1852
2271
  let(:limit) { 3 }
1853
2272
 
1854
2273
  it "returns the correct documents and touches the database" do
1855
- expect(context).to receive(:view).exactly(3).times.and_call_original
1856
- expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2274
+ expect_query(1) do
2275
+ expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2276
+ end
1857
2277
  end
1858
2278
  end
1859
2279
  end
@@ -1865,8 +2285,9 @@ describe Mongoid::Contextual::Mongo do
1865
2285
  let(:limit) { 1 }
1866
2286
 
1867
2287
  it "returns the correct documents without touching the database" do
1868
- expect(context).to receive(:view).never
1869
- expect(docs).to eq([ depeche_mode ])
2288
+ expect_no_queries do
2289
+ expect(docs).to eq([ depeche_mode ])
2290
+ end
1870
2291
  end
1871
2292
  end
1872
2293
 
@@ -1874,8 +2295,9 @@ describe Mongoid::Contextual::Mongo do
1874
2295
  let(:limit) { 3 }
1875
2296
 
1876
2297
  it "returns the correct documents and touches the database" do
1877
- expect(context).to receive(:view).exactly(3).times.and_call_original
1878
- expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2298
+ expect_query(1) do
2299
+ expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2300
+ end
1879
2301
  end
1880
2302
  end
1881
2303
  end
@@ -1883,24 +2305,15 @@ describe Mongoid::Contextual::Mongo do
1883
2305
  end
1884
2306
  end
1885
2307
 
1886
- context "when given an empty hash" do
1887
- let(:context) { described_class.new(criteria) }
1888
- let(:criteria) { Band.criteria }
1889
- let(:docs) { context.send(method, {}) }
1890
-
1891
- it "behaves as if limit is nil" do
1892
- expect(docs).to eq(depeche_mode)
1893
- end
1894
- end
1895
-
1896
- context "when calling #first then #last" do
2308
+ context "when calling #first then #last and the query cache is enabled" do
2309
+ query_cache_enabled
1897
2310
 
1898
2311
  let(:context) do
1899
2312
  described_class.new(criteria)
1900
2313
  end
1901
2314
 
1902
2315
  let(:criteria) do
1903
- Band.all.cache
2316
+ Band.all
1904
2317
  end
1905
2318
 
1906
2319
  before do
@@ -1915,8 +2328,10 @@ describe Mongoid::Contextual::Mongo do
1915
2328
  let(:before_limit) { 2 }
1916
2329
  let(:limit) { 1 }
1917
2330
 
1918
- it "gets the correct document" do
1919
- expect(docs).to eq([rolling_stones])
2331
+ it "gets the correct document and hits the database" do
2332
+ expect_query(1) do
2333
+ expect(docs).to eq([rolling_stones])
2334
+ end
1920
2335
  end
1921
2336
  end
1922
2337
  end
@@ -2006,30 +2421,10 @@ describe Mongoid::Contextual::Mongo do
2006
2421
  context 'when calling #first' do
2007
2422
 
2008
2423
  it 'returns the first document, sorted by _id' do
2009
- pending "MONGOID-5416"
2010
2424
  expect(context.last).to eq(rolling_stones)
2011
2425
  expect(context.first).to eq(depeche_mode)
2012
2426
  end
2013
2427
  end
2014
-
2015
- context 'with option { id_sort: :none }' do
2016
- let(:opts) do
2017
- { id_sort: :none }
2018
- end
2019
-
2020
- it 'doesn\'t apply the sort on _id' do
2021
- expect(context.last(opts)).to eq(depeche_mode)
2022
- end
2023
-
2024
- context 'when calling #first' do
2025
-
2026
- it 'doesn\'t apply the sort on _id' do
2027
- pending "MONGOID-5416"
2028
- expect(context.last(opts)).to eq(rolling_stones)
2029
- expect(context.first(opts)).to eq(depeche_mode)
2030
- end
2031
- end
2032
- end
2033
2428
  end
2034
2429
 
2035
2430
  context 'when the criteria has a sort' do
@@ -2054,25 +2449,6 @@ describe Mongoid::Contextual::Mongo do
2054
2449
  expect(context.first).to eq(rolling_stones)
2055
2450
  end
2056
2451
  end
2057
-
2058
- context 'with option { id_sort: :none }' do
2059
-
2060
- let(:opts) do
2061
- { id_sort: :none }
2062
- end
2063
-
2064
- it 'uses the preexisting sort' do
2065
- expect(context.last(opts)).to eq(depeche_mode)
2066
- end
2067
-
2068
- context 'when calling #first' do
2069
-
2070
- it 'uses the preexisting sort' do
2071
- expect(context.last(opts)).to eq(depeche_mode)
2072
- expect(context.first(opts)).to eq(rolling_stones)
2073
- end
2074
- end
2075
- end
2076
2452
  end
2077
2453
 
2078
2454
  context "when using .sort" do
@@ -2101,28 +2477,17 @@ describe Mongoid::Contextual::Mongo do
2101
2477
  end
2102
2478
  end
2103
2479
 
2104
- context "when the context is cached" do
2480
+ context "when the query cache is enabled" do
2481
+ query_cache_enabled
2105
2482
 
2106
2483
  let(:criteria) do
2107
- Band.where(name: "Depeche Mode").cache
2484
+ Band.where(name: "Depeche Mode")
2108
2485
  end
2109
2486
 
2110
2487
  let(:context) do
2111
2488
  described_class.new(criteria)
2112
2489
  end
2113
2490
 
2114
- context "when the cache is loaded" do
2115
-
2116
- before do
2117
- context.to_a
2118
- end
2119
-
2120
- it "returns the last document without touching the database" do
2121
- expect(context).to receive(:view).never
2122
- expect(context.last).to eq(depeche_mode)
2123
- end
2124
- end
2125
-
2126
2491
  context "when last method was called before" do
2127
2492
 
2128
2493
  before do
@@ -2130,8 +2495,9 @@ describe Mongoid::Contextual::Mongo do
2130
2495
  end
2131
2496
 
2132
2497
  it "returns the last document without touching the database" do
2133
- expect(context).to receive(:view).never
2134
- expect(context.last).to eq(depeche_mode)
2498
+ expect_no_queries do
2499
+ expect(context.last).to eq(depeche_mode)
2500
+ end
2135
2501
  end
2136
2502
  end
2137
2503
  end
@@ -2191,71 +2557,15 @@ describe Mongoid::Contextual::Mongo do
2191
2557
  described_class.new(criteria)
2192
2558
  end
2193
2559
 
2194
- context "when the whole context is loaded" do
2195
-
2196
- before do
2197
- context.to_a
2198
- end
2199
-
2200
- context "when all of the documents are cached" do
2201
-
2202
- let(:criteria) do
2203
- Band.all.cache
2204
- end
2205
-
2206
- context "when requesting all of the documents" do
2207
-
2208
- let(:docs) do
2209
- context.last(3)
2210
- end
2211
-
2212
- it "returns all of the documents without touching the database" do
2213
- expect(context).to receive(:view).never
2214
- expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2215
- end
2216
- end
2217
-
2218
- context "when requesting fewer than all of the documents" do
2219
-
2220
- let(:docs) do
2221
- context.last(2)
2222
- end
2223
-
2224
- it "returns all of the documents without touching the database" do
2225
- expect(context).to receive(:view).never
2226
- expect(docs).to eq([ new_order, rolling_stones ])
2227
- end
2228
- end
2229
- end
2230
-
2231
- context "when only one document is cached" do
2232
-
2233
- let(:criteria) do
2234
- Band.where(name: "Depeche Mode").cache
2235
- end
2236
-
2237
- context "when requesting one document" do
2238
-
2239
- let(:docs) do
2240
- context.last(1)
2241
- end
2242
-
2243
- it "returns one document without touching the database" do
2244
- expect(context).to receive(:view).never
2245
- expect(docs).to eq([ depeche_mode ])
2246
- end
2247
- end
2248
- end
2249
- end
2250
-
2251
- context "when the last method was called before" do
2560
+ context "when query cache is enabled" do
2561
+ query_cache_enabled
2252
2562
 
2253
2563
  let(:context) do
2254
2564
  described_class.new(criteria)
2255
2565
  end
2256
2566
 
2257
2567
  let(:criteria) do
2258
- Band.all.cache
2568
+ Band.all
2259
2569
  end
2260
2570
 
2261
2571
  before do
@@ -2272,18 +2582,20 @@ describe Mongoid::Contextual::Mongo do
2272
2582
  context "when getting all of the documents" do
2273
2583
  let(:limit) { 3 }
2274
2584
 
2275
- it "returns all documents without touching the database" do
2276
- expect(context).to receive(:view).never
2277
- expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2585
+ it "returns all documents without touching the db" do
2586
+ expect_no_queries do
2587
+ expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2588
+ end
2278
2589
  end
2279
2590
  end
2280
2591
 
2281
2592
  context "when getting fewer documents" do
2282
2593
  let(:limit) { 2 }
2283
2594
 
2284
- it "returns the correct documents without touching the database" do
2285
- expect(context).to receive(:view).never
2286
- expect(docs).to eq([ new_order, rolling_stones ])
2595
+ it "returns the correct documents without touching the db" do
2596
+ expect_no_queries do
2597
+ expect(docs).to eq([ new_order, rolling_stones ])
2598
+ end
2287
2599
  end
2288
2600
  end
2289
2601
  end
@@ -2294,9 +2606,10 @@ describe Mongoid::Contextual::Mongo do
2294
2606
  context "when getting the same number of documents" do
2295
2607
  let(:limit) { 2 }
2296
2608
 
2297
- it "returns the correct documents without touching the database" do
2298
- expect(context).to receive(:view).never
2299
- expect(docs).to eq([ new_order, rolling_stones ])
2609
+ it "returns the correct documents without touching the db" do
2610
+ expect_no_queries do
2611
+ expect(docs).to eq([ new_order, rolling_stones ])
2612
+ end
2300
2613
  end
2301
2614
  end
2302
2615
 
@@ -2304,8 +2617,9 @@ describe Mongoid::Contextual::Mongo do
2304
2617
  let(:limit) { 3 }
2305
2618
 
2306
2619
  it "returns the correct documents and touches the database" do
2307
- expect(context).to receive(:view).twice.and_call_original
2308
- expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2620
+ expect_query(1) do
2621
+ expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2622
+ end
2309
2623
  end
2310
2624
  end
2311
2625
  end
@@ -2317,8 +2631,9 @@ describe Mongoid::Contextual::Mongo do
2317
2631
  let(:limit) { 1 }
2318
2632
 
2319
2633
  it "returns the correct documents without touching the database" do
2320
- expect(context).to receive(:view).never
2321
- expect(docs).to eq([ rolling_stones ])
2634
+ expect_no_queries do
2635
+ expect(docs).to eq([ rolling_stones ])
2636
+ end
2322
2637
  end
2323
2638
  end
2324
2639
 
@@ -2326,8 +2641,9 @@ describe Mongoid::Contextual::Mongo do
2326
2641
  let(:limit) { 3 }
2327
2642
 
2328
2643
  it "returns the correct documents and touches the database" do
2329
- expect(context).to receive(:view).twice.and_call_original
2330
- expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2644
+ expect_query(1) do
2645
+ expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2646
+ end
2331
2647
  end
2332
2648
  end
2333
2649
  end
@@ -2335,14 +2651,15 @@ describe Mongoid::Contextual::Mongo do
2335
2651
  end
2336
2652
  end
2337
2653
 
2338
- context "when calling #last then #first" do
2654
+ context "when calling #last then #first and the query cache is enabled" do
2655
+ query_cache_enabled
2339
2656
 
2340
2657
  let(:context) do
2341
2658
  described_class.new(criteria)
2342
2659
  end
2343
2660
 
2344
2661
  let(:criteria) do
2345
- Band.all.cache
2662
+ Band.all
2346
2663
  end
2347
2664
 
2348
2665
  before do
@@ -2358,26 +2675,16 @@ describe Mongoid::Contextual::Mongo do
2358
2675
  let(:limit) { 1 }
2359
2676
 
2360
2677
  it "hits the database" do
2361
- expect(context).to receive(:view).exactly(3).times.and_call_original
2362
- docs
2678
+ expect_query(1) do
2679
+ docs
2680
+ end
2363
2681
  end
2364
2682
 
2365
2683
  it "gets the correct document" do
2366
- pending "MONGOID-5416"
2367
2684
  expect(docs).to eq([ depeche_mode ])
2368
2685
  end
2369
2686
  end
2370
2687
  end
2371
-
2372
- context "when given an empty hash" do
2373
- let(:context) { described_class.new(criteria) }
2374
- let(:criteria) { Band.criteria }
2375
- let(:docs) { context.last({}) }
2376
-
2377
- it "behaves as if limit is nil" do
2378
- expect(docs).to eq(rolling_stones)
2379
- end
2380
- end
2381
2688
  end
2382
2689
 
2383
2690
  describe "#initialize" do
@@ -2426,37 +2733,28 @@ describe Mongoid::Contextual::Mongo do
2426
2733
  described_class.new(criteria)
2427
2734
  end
2428
2735
 
2429
- it "returns the number of documents that match" do
2430
- expect(context.send(method)).to eq(2)
2431
- end
2736
+ context "when broken_view_options is false" do
2737
+ driver_config_override :broken_view_options, false
2432
2738
 
2433
- context "when calling more than once" do
2434
- it "returns the cached value for subsequent calls" do
2435
- expect(context.view).to receive(:count_documents).once.and_return(2)
2436
- 2.times { expect(context.send(method)).to eq(2) }
2739
+ it "returns the number of documents that match" do
2740
+ expect(context.send(method)).to eq(1)
2437
2741
  end
2438
2742
  end
2439
2743
 
2440
- context "when the results have been iterated over" do
2441
-
2442
- before do
2443
- context.entries
2444
- end
2744
+ context "when broken_view_options is true" do
2745
+ driver_config_override :broken_view_options, true
2445
2746
 
2446
- it "returns the cached value for all calls" do
2447
- expect(context.view).to receive(:count_documents).once.and_return(2)
2747
+ it "returns the number of documents that match" do
2448
2748
  expect(context.send(method)).to eq(2)
2449
2749
  end
2750
+ end
2450
2751
 
2451
- context "when the results have been iterated over multiple times" do
2452
-
2453
- before do
2454
- context.entries
2455
- end
2752
+ context "when calling more than once with different limits" do
2753
+ driver_config_override :broken_view_options, false
2456
2754
 
2457
- it "resets the length on each full iteration" do
2458
- expect(context.size).to eq(2)
2459
- end
2755
+ it "does not cache the value" do
2756
+ expect(context.limit(1).send(method)).to eq(1)
2757
+ expect(context.limit(2).send(method)).to eq(2)
2460
2758
  end
2461
2759
  end
2462
2760
  end
@@ -2475,10 +2773,12 @@ describe Mongoid::Contextual::Mongo do
2475
2773
  expect(context.send(method)).to eq(1)
2476
2774
  end
2477
2775
 
2478
- context "when calling more than once" do
2479
- it "returns the cached value for subsequent calls" do
2480
- expect(context.view).to receive(:count_documents).once.and_return(1)
2481
- 2.times { expect(context.send(method)).to eq(1) }
2776
+ context "when calling more than once with different skips" do
2777
+ driver_config_override :broken_view_options, false
2778
+
2779
+ it "does not cache the value" do
2780
+ expect(context.skip(0).send(method)).to eq(1)
2781
+ expect(context.skip(1).send(method)).to eq(0)
2482
2782
  end
2483
2783
  end
2484
2784
 
@@ -2600,7 +2900,7 @@ describe Mongoid::Contextual::Mongo do
2600
2900
  it "raises an error" do
2601
2901
  expect do
2602
2902
  Person.take!
2603
- end.to raise_error(Mongoid::Errors::DocumentNotFound)
2903
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Person./)
2604
2904
  end
2605
2905
  end
2606
2906
  end
@@ -2622,9 +2922,10 @@ describe Mongoid::Contextual::Mongo do
2622
2922
 
2623
2923
  context "when passed the symbol field name" do
2624
2924
 
2625
- it "performs mapping and warns" do
2626
- expect(Mongoid::Warnings).to receive(:warn_map_field_deprecated)
2627
- expect(context.map(:name)).to eq ["Depeche Mode", "New Order"]
2925
+ it "raises an error" do
2926
+ expect do
2927
+ context.map(:name)
2928
+ end.to raise_error(ArgumentError)
2628
2929
  end
2629
2930
  end
2630
2931
 
@@ -3065,7 +3366,805 @@ describe Mongoid::Contextual::Mongo do
3065
3366
  end
3066
3367
  end
3067
3368
 
3068
- describe "#skip" do
3369
+ describe "#skip" do
3370
+
3371
+ let!(:depeche_mode) do
3372
+ Band.create!(name: "Depeche Mode")
3373
+ end
3374
+
3375
+ let!(:new_order) do
3376
+ Band.create!(name: "New Order")
3377
+ end
3378
+
3379
+ let(:criteria) do
3380
+ Band.all
3381
+ end
3382
+
3383
+ let(:context) do
3384
+ described_class.new(criteria)
3385
+ end
3386
+
3387
+ it "limits the results" do
3388
+ expect(context.skip(1).entries).to eq([ new_order ])
3389
+ end
3390
+ end
3391
+
3392
+ describe "#sort" do
3393
+
3394
+ let!(:depeche_mode) do
3395
+ Band.create!(name: "Depeche Mode")
3396
+ end
3397
+
3398
+ let!(:new_order) do
3399
+ Band.create!(name: "New Order")
3400
+ end
3401
+
3402
+ let(:criteria) do
3403
+ Band.all
3404
+ end
3405
+
3406
+ let(:context) do
3407
+ described_class.new(criteria)
3408
+ end
3409
+
3410
+ context "when providing a spec" do
3411
+
3412
+ it "sorts the results" do
3413
+ expect(context.sort(name: -1).entries).to eq([ new_order, depeche_mode ])
3414
+ end
3415
+
3416
+ it "returns the context" do
3417
+ expect(context.sort(name: 1)).to eq(context)
3418
+ end
3419
+ end
3420
+
3421
+ context "when providing a block" do
3422
+
3423
+ let(:sorted) do
3424
+ context.sort do |a, b|
3425
+ b.name <=> a.name
3426
+ end
3427
+ end
3428
+
3429
+ it "sorts the results in memory" do
3430
+ expect(sorted).to eq([ new_order, depeche_mode ])
3431
+ end
3432
+ end
3433
+ end
3434
+
3435
+ describe "#update" do
3436
+
3437
+ let!(:depeche_mode) do
3438
+ Band.create!(name: "Depeche Mode")
3439
+ end
3440
+
3441
+ let!(:new_order) do
3442
+ Band.create!(name: "New Order")
3443
+ end
3444
+
3445
+ let(:criteria) do
3446
+ Band.all
3447
+ end
3448
+
3449
+ let(:context) do
3450
+ described_class.new(criteria)
3451
+ end
3452
+
3453
+ context "when adding an element to a HABTM set" do
3454
+
3455
+ let(:person) do
3456
+ Person.create!
3457
+ end
3458
+
3459
+ let(:preference) do
3460
+ Preference.create!
3461
+ end
3462
+
3463
+ before do
3464
+ Person.where(id: person.id).
3465
+ update("$addToSet" => { preference_ids: preference.id })
3466
+ end
3467
+
3468
+ it "adds a single element to the array" do
3469
+ expect(person.reload.preference_ids).to eq([ preference.id ])
3470
+ end
3471
+ end
3472
+
3473
+ context "when providing attributes" do
3474
+
3475
+ context "when the attributes are of the correct type" do
3476
+
3477
+ before do
3478
+ context.update(name: "Smiths")
3479
+ end
3480
+
3481
+ it "updates only the first matching document" do
3482
+ expect(depeche_mode.reload.name).to eq("Smiths")
3483
+ end
3484
+
3485
+ it "does not update the last matching document" do
3486
+ expect(new_order.reload.name).to eq("New Order")
3487
+ end
3488
+ end
3489
+
3490
+ context "when the attributes must be mongoized" do
3491
+
3492
+ context "when coercing a string to integer" do
3493
+
3494
+ before do
3495
+ context.update(member_count: "1")
3496
+ end
3497
+
3498
+ it "updates the first matching document" do
3499
+ expect(depeche_mode.reload.member_count).to eq(1)
3500
+ end
3501
+
3502
+ it "does not update the last matching document" do
3503
+ expect(new_order.reload.member_count).to be_nil
3504
+ end
3505
+ end
3506
+
3507
+ context "when coercing a string to date" do
3508
+
3509
+ before do
3510
+ context.update(founded: "1979/1/1")
3511
+ end
3512
+
3513
+ it "updates the first matching document" do
3514
+ expect(depeche_mode.reload.founded).to eq(Date.new(1979, 1, 1))
3515
+ end
3516
+
3517
+ it "does not update the last matching document" do
3518
+ expect(new_order.reload.founded).to be_nil
3519
+ end
3520
+ end
3521
+ end
3522
+ end
3523
+
3524
+ context "when providing atomic operations" do
3525
+
3526
+ context "when only atomic operations are provided" do
3527
+
3528
+ context "when the attributes are in the correct type" do
3529
+
3530
+ before do
3531
+ context.update("$set" => { name: "Smiths" })
3532
+ end
3533
+
3534
+ it "updates the first matching document" do
3535
+ expect(depeche_mode.reload.name).to eq("Smiths")
3536
+ end
3537
+
3538
+ it "does not update the last matching document" do
3539
+ expect(new_order.reload.name).to eq("New Order")
3540
+ end
3541
+ end
3542
+
3543
+ context "when the attributes must be mongoized" do
3544
+
3545
+ before do
3546
+ context.update("$set" => { member_count: "1" })
3547
+ end
3548
+
3549
+ it "updates the first matching document" do
3550
+ expect(depeche_mode.reload.member_count).to eq(1)
3551
+ end
3552
+
3553
+ it "does not update the last matching document" do
3554
+ expect(new_order.reload.member_count).to be_nil
3555
+ end
3556
+ end
3557
+ end
3558
+
3559
+ context "when a mix are provided" do
3560
+
3561
+ before do
3562
+ context.update("$set" => { name: "Smiths" }, likes: 100)
3563
+ end
3564
+
3565
+ it "updates the first matching document's set" do
3566
+ expect(depeche_mode.reload.name).to eq("Smiths")
3567
+ end
3568
+
3569
+ it "updates the first matching document's updates" do
3570
+ expect(depeche_mode.reload.likes).to eq(100)
3571
+ end
3572
+
3573
+ it "does not update the last matching document's set" do
3574
+ expect(new_order.reload.name).to eq("New Order")
3575
+ end
3576
+
3577
+ it "does not update the last matching document's updates" do
3578
+ expect(new_order.reload.likes).to be_nil
3579
+ end
3580
+ end
3581
+ end
3582
+
3583
+ context "when providing no attributes" do
3584
+
3585
+ it "returns false" do
3586
+ expect(context.update).to be false
3587
+ end
3588
+ end
3589
+
3590
+ context 'when provided array filters' do
3591
+ min_server_version '3.6'
3592
+
3593
+ before do
3594
+ Band.delete_all
3595
+ b = Band.new(name: 'Depeche Mode')
3596
+ b.labels << Label.new(name: 'Warner')
3597
+ b.labels << Label.new(name: 'Sony')
3598
+ b.labels << Label.new(name: 'Cbs')
3599
+ b.save!
3600
+
3601
+ b = Band.new(name: 'FKA Twigs')
3602
+ b.labels << Label.new(name: 'Warner')
3603
+ b.labels << Label.new(name: 'Cbs')
3604
+ b.save!
3605
+ end
3606
+
3607
+
3608
+ let(:criteria) do
3609
+ Band.where(name: 'Depeche Mode')
3610
+ end
3611
+
3612
+ let!(:update) do
3613
+ context.update({ '$set' => { 'labels.$[i].name' => 'Sony' } },
3614
+ array_filters: [{ 'i.name' => 'Cbs' }])
3615
+ end
3616
+
3617
+ it 'applies the array filters' do
3618
+ expect(Band.where(name: 'Depeche Mode').first.labels.collect(&:name)).to match_array(['Warner', 'Sony', 'Sony'])
3619
+ end
3620
+
3621
+ it 'does not affect other documents' do
3622
+ expect(Band.where(name: 'FKA Twigs').first.labels.collect(&:name)).to match_array(['Warner', 'Cbs'])
3623
+ end
3624
+ end
3625
+ end
3626
+
3627
+ describe "#update_all" do
3628
+
3629
+ let!(:depeche_mode) do
3630
+ Band.create!(name: "Depeche Mode", origin: "Essex")
3631
+ end
3632
+
3633
+ let!(:new_order) do
3634
+ Band.create!(name: "New Order")
3635
+ end
3636
+
3637
+ let(:criteria) do
3638
+ Band.all
3639
+ end
3640
+
3641
+ let(:context) do
3642
+ described_class.new(criteria)
3643
+ end
3644
+
3645
+ context "when providing attributes" do
3646
+
3647
+ context "when the attributes are of the correct type" do
3648
+
3649
+ before do
3650
+ context.update_all(name: "Smiths")
3651
+ end
3652
+
3653
+ it "updates the first matching document" do
3654
+ expect(depeche_mode.reload.name).to eq("Smiths")
3655
+ end
3656
+
3657
+ it "does not clear out other attributes" do
3658
+ expect(depeche_mode.reload.origin).to eq("Essex")
3659
+ end
3660
+
3661
+ it "updates the last matching document" do
3662
+ expect(new_order.reload.name).to eq("Smiths")
3663
+ end
3664
+ end
3665
+
3666
+ context "when the attributes must be mongoized" do
3667
+
3668
+ before do
3669
+ context.update_all(member_count: "1")
3670
+ end
3671
+
3672
+ it "updates the first matching document" do
3673
+ expect(depeche_mode.reload.member_count).to eq(1)
3674
+ end
3675
+
3676
+ it "updates the last matching document" do
3677
+ expect(new_order.reload.member_count).to eq(1)
3678
+ end
3679
+ end
3680
+
3681
+ context "when using aliased field names" do
3682
+
3683
+ before do
3684
+ context.update_all(years: 100)
3685
+ end
3686
+
3687
+ it "updates the first matching document" do
3688
+ expect(depeche_mode.reload.years).to eq(100)
3689
+ end
3690
+
3691
+ it "updates the last matching document" do
3692
+ expect(new_order.reload.years).to eq(100)
3693
+ end
3694
+ end
3695
+ end
3696
+
3697
+ context "when providing atomic operations" do
3698
+
3699
+ context "when only atomic operations are provided" do
3700
+
3701
+ context "when the attributes are in the correct type" do
3702
+
3703
+ context "when operation is $set" do
3704
+
3705
+ before do
3706
+ context.update_all("$set" => { name: "Smiths" })
3707
+ end
3708
+
3709
+ it "updates the first matching document" do
3710
+ expect(depeche_mode.reload.name).to eq("Smiths")
3711
+ end
3712
+
3713
+ it "updates the last matching document" do
3714
+ expect(new_order.reload.name).to eq("Smiths")
3715
+ end
3716
+ end
3717
+
3718
+ context "when operation is $push" do
3719
+
3720
+ before do
3721
+ depeche_mode.update_attribute(:genres, ["electronic"])
3722
+ new_order.update_attribute(:genres, ["electronic"])
3723
+ context.update_all("$push" => { genres: "pop" })
3724
+ end
3725
+
3726
+ it "updates the first matching document" do
3727
+ expect(depeche_mode.reload.genres).to eq(["electronic", "pop"])
3728
+ end
3729
+
3730
+ it "updates the last matching document" do
3731
+ expect(new_order.reload.genres).to eq(["electronic", "pop"])
3732
+ end
3733
+ end
3734
+
3735
+ context "when operation is $addToSet" do
3736
+
3737
+ before do
3738
+ context.update_all("$addToSet" => { genres: "electronic" })
3739
+ end
3740
+
3741
+ it "updates the first matching document" do
3742
+ expect(depeche_mode.reload.genres).to eq(["electronic"])
3743
+ end
3744
+
3745
+ it "updates the last matching document" do
3746
+ expect(new_order.reload.genres).to eq(["electronic"])
3747
+ end
3748
+ end
3749
+
3750
+ context "when operation is $pull" do
3751
+ context "when pulling single element" do
3752
+
3753
+ before do
3754
+ depeche_mode.update_attribute(:genres, ["electronic", "pop"])
3755
+ new_order.update_attribute(:genres, ["electronic", "pop"])
3756
+ context.update_all("$pull" => { genres: "electronic" })
3757
+ end
3758
+
3759
+ it "updates the first matching document" do
3760
+ expect(depeche_mode.reload.genres).to eq(["pop"])
3761
+ end
3762
+
3763
+ it "updates the last matching document" do
3764
+ expect(new_order.reload.genres).to eq(["pop"])
3765
+ end
3766
+ end
3767
+
3768
+ context "when pulling based on condition" do
3769
+ before do
3770
+ depeche_mode.update_attribute(:genres, ["electronic", "pop", "dance"])
3771
+ new_order.update_attribute(:genres, ["electronic", "pop", "dance"])
3772
+ context.update_all("$pull" => { genres: { '$in' => ["electronic", "pop"] } })
3773
+ end
3774
+
3775
+ it "updates the first matching document" do
3776
+ expect(depeche_mode.reload.genres).to eq(["dance"])
3777
+ end
3778
+
3779
+ it "updates the last matching document" do
3780
+ expect(new_order.reload.genres).to eq(["dance"])
3781
+ end
3782
+ end
3783
+ end
3784
+
3785
+ context "when operation is $pop" do
3786
+
3787
+ before do
3788
+ depeche_mode.update_attribute(:genres, ["pop", "electronic"])
3789
+ end
3790
+
3791
+ it "removes first element in array" do
3792
+ context.update_all("$pop" => { genres: -1 })
3793
+ expect(depeche_mode.reload.genres).to eq(["electronic"])
3794
+ end
3795
+
3796
+ it "removes last element in array" do
3797
+ context.update_all("$pop" => { genres: 1 })
3798
+ expect(depeche_mode.reload.genres).to eq(["pop"])
3799
+ end
3800
+ end
3801
+
3802
+ context "when operation is $pullAll" do
3803
+
3804
+ before do
3805
+ depeche_mode.update_attribute(:genres, ["pop", "electronic", "dance", "pop" ])
3806
+ new_order.update_attribute(:genres, ["electronic", "pop", "electronic", "dance"])
3807
+ context.update_all("$pullAll" => { genres: ["pop", "electronic"] })
3808
+ end
3809
+
3810
+ it "updates the first matching document" do
3811
+ expect(depeche_mode.reload.genres).to eq(["dance"])
3812
+ end
3813
+
3814
+ it "updates the last matching document" do
3815
+ expect(new_order.reload.genres).to eq(["dance"])
3816
+ end
3817
+ end
3818
+ end
3819
+
3820
+ context 'when using aliased field names' do
3821
+ before do
3822
+ context.update_all('$set' => { years: 100 })
3823
+ end
3824
+
3825
+ it "updates the first matching document" do
3826
+ expect(depeche_mode.reload.years).to eq(100)
3827
+ end
3828
+
3829
+ it "updates the last matching document" do
3830
+ expect(new_order.reload.years).to eq(100)
3831
+ end
3832
+ end
3833
+
3834
+ context "when the attributes must be mongoized" do
3835
+
3836
+ before do
3837
+ context.update_all("$set" => { location: LatLng.new(52.30, 13.25) })
3838
+ end
3839
+
3840
+ it "updates the first matching document" do
3841
+ expect(depeche_mode.reload.location).to eq(LatLng.new(52.30, 13.25))
3842
+ end
3843
+
3844
+ it "updates the last matching document" do
3845
+ expect(new_order.reload.location).to eq(LatLng.new(52.30, 13.25))
3846
+ end
3847
+ end
3848
+ end
3849
+
3850
+ context "when a mix are provided" do
3851
+
3852
+ before do
3853
+ context.update_all("$set" => { name: "Smiths" }, likes: 100)
3854
+ end
3855
+
3856
+ it "updates the first matching document's set" do
3857
+ expect(depeche_mode.reload.name).to eq("Smiths")
3858
+ end
3859
+
3860
+ it "updates the first matching document's updates" do
3861
+ expect(depeche_mode.reload.likes).to eq(100)
3862
+ end
3863
+
3864
+ it "updates the last matching document's set" do
3865
+ expect(new_order.reload.name).to eq("Smiths")
3866
+ end
3867
+
3868
+ it "updates the last matching document's updates" do
3869
+ expect(new_order.reload.likes).to eq(100)
3870
+ end
3871
+ end
3872
+ end
3873
+
3874
+ context "when providing no attributes" do
3875
+
3876
+ it "returns false" do
3877
+ expect(context.update_all).to be false
3878
+ end
3879
+ end
3880
+
3881
+ context 'when provided array filters' do
3882
+ min_server_version '3.6'
3883
+
3884
+ before do
3885
+ Band.delete_all
3886
+ b = Band.new(name: 'Depeche Mode')
3887
+ b.labels << Label.new(name: 'Warner')
3888
+ b.labels << Label.new(name: 'Sony')
3889
+ b.labels << Label.new(name: 'Cbs')
3890
+ b.save!
3891
+
3892
+ b = Band.new(name: 'FKA Twigs')
3893
+ b.labels << Label.new(name: 'Warner')
3894
+ b.labels << Label.new(name: 'Cbs')
3895
+ b.save!
3896
+ end
3897
+
3898
+
3899
+ let(:criteria) do
3900
+ Band.all
3901
+ end
3902
+
3903
+ let!(:update) do
3904
+ context.update_all({ '$set' => { 'labels.$[i].name' => 'Sony' } },
3905
+ array_filters: [{ 'i.name' => 'Cbs' }])
3906
+ end
3907
+
3908
+ it 'applies the array filters' do
3909
+ expect(Band.where(name: 'Depeche Mode').first.labels.collect(&:name)).to match_array(['Warner', 'Sony', 'Sony'])
3910
+ end
3911
+
3912
+ it 'updates all documents' do
3913
+ expect(Band.where(name: 'FKA Twigs').first.labels.collect(&:name)).to match_array(['Warner', 'Sony'])
3914
+ end
3915
+ end
3916
+ end
3917
+
3918
+ describe '#pipeline' do
3919
+
3920
+ context 'when the criteria has a selector' do
3921
+
3922
+ before do
3923
+ Artist.index(name: "text")
3924
+ Artist.create_indexes
3925
+ end
3926
+
3927
+ let(:criteria) do
3928
+ Artist.text_search("New Order")
3929
+ end
3930
+
3931
+ let(:context) do
3932
+ described_class.new(criteria)
3933
+ end
3934
+
3935
+ let(:pipeline_match) do
3936
+ context.send(:pipeline, :some_field).first['$match']
3937
+ end
3938
+
3939
+ it 'creates a pipeline with the selector as one of the $match criteria' do
3940
+ expect(pipeline_match).to include({ '$text' => { '$search' => "New Order" } })
3941
+ end
3942
+
3943
+ it 'creates a pipeline with the $exists operator as one of the $match criteria' do
3944
+ expect(pipeline_match).to include({ 'some_field' => { '$exists' => true } })
3945
+ end
3946
+ end
3947
+ end
3948
+
3949
+ describe "#first!" do
3950
+
3951
+ let!(:depeche_mode) do
3952
+ Band.create!(name: "Depeche Mode")
3953
+ end
3954
+
3955
+ let!(:new_order) do
3956
+ Band.create!(name: "New Order")
3957
+ end
3958
+
3959
+ let!(:rolling_stones) do
3960
+ Band.create!(name: "The Rolling Stones")
3961
+ end
3962
+
3963
+ let!(:death_cab) do
3964
+ Band.create!(name: "Death Cab For Cutie")
3965
+ end
3966
+
3967
+ let(:context) do
3968
+ described_class.new(criteria)
3969
+ end
3970
+
3971
+ context "when there's no sort" do
3972
+ let(:criteria) do
3973
+ Band.all
3974
+ end
3975
+
3976
+ it "gets the first document" do
3977
+ expect(context.first!).to eq(depeche_mode)
3978
+ end
3979
+ end
3980
+
3981
+ context "when there's a custom sort" do
3982
+ let(:criteria) do
3983
+ Band.all
3984
+ end
3985
+
3986
+ it "gets the first document" do
3987
+ expect(context.sort(name: 1).first!).to eq(death_cab)
3988
+ end
3989
+ end
3990
+
3991
+ context "when there are no documents" do
3992
+ let(:criteria) do
3993
+ Band.where(name: "bogus")
3994
+ end
3995
+
3996
+ it "raises an error" do
3997
+ expect do
3998
+ context.first!
3999
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
4000
+ end
4001
+ end
4002
+ end
4003
+
4004
+ describe "#last!" do
4005
+
4006
+ let!(:depeche_mode) do
4007
+ Band.create!(name: "Depeche Mode")
4008
+ end
4009
+
4010
+ let!(:new_order) do
4011
+ Band.create!(name: "New Order")
4012
+ end
4013
+
4014
+ let!(:rolling_stones) do
4015
+ Band.create!(name: "The Rolling Stones")
4016
+ end
4017
+
4018
+ let!(:death_cab) do
4019
+ Band.create!(name: "Death Cab For Cutie")
4020
+ end
4021
+
4022
+ let(:context) do
4023
+ described_class.new(criteria)
4024
+ end
4025
+
4026
+ context "when there's no sort" do
4027
+ let(:criteria) do
4028
+ Band.all
4029
+ end
4030
+
4031
+ it "gets the last document" do
4032
+ expect(context.last!).to eq(death_cab)
4033
+ end
4034
+ end
4035
+
4036
+ context "when there's a custom sort" do
4037
+ let(:criteria) do
4038
+ Band.all
4039
+ end
4040
+
4041
+ it "gets the last document" do
4042
+ expect(context.sort(name: 1).last!).to eq(rolling_stones)
4043
+ end
4044
+ end
4045
+
4046
+ context "when there are no documents" do
4047
+ let(:criteria) do
4048
+ Band.where(name: "bogus")
4049
+ end
4050
+
4051
+ it "raises an error" do
4052
+ expect do
4053
+ context.last!
4054
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
4055
+ end
4056
+ end
4057
+ end
4058
+
4059
+ describe "#second" do
4060
+
4061
+ let!(:depeche_mode) do
4062
+ Band.create!(name: "Depeche Mode")
4063
+ end
4064
+
4065
+ let!(:new_order) do
4066
+ Band.create!(name: "New Order")
4067
+ end
4068
+
4069
+ let!(:rolling_stones) do
4070
+ Band.create!(name: "The Rolling Stones")
4071
+ end
4072
+
4073
+ let!(:death_cab) do
4074
+ Band.create!(name: "Death Cab For Cutie")
4075
+ end
4076
+
4077
+ let(:context) do
4078
+ described_class.new(criteria)
4079
+ end
4080
+
4081
+ context "when there's no sort" do
4082
+ let(:criteria) do
4083
+ Band.all
4084
+ end
4085
+
4086
+ it "gets the second document" do
4087
+ expect(context.second).to eq(new_order)
4088
+ end
4089
+ end
4090
+
4091
+ context "when there's a custom sort" do
4092
+ let(:criteria) do
4093
+ Band.all
4094
+ end
4095
+
4096
+ it "gets the second document" do
4097
+ expect(context.sort(name: 1).second).to eq(depeche_mode)
4098
+ end
4099
+ end
4100
+
4101
+ context "when there are no documents" do
4102
+ let(:criteria) do
4103
+ Band.where(name: "bogus")
4104
+ end
4105
+
4106
+ it "returns nil" do
4107
+ expect(context.second).to be_nil
4108
+ end
4109
+ end
4110
+ end
4111
+
4112
+ describe "#second!" do
4113
+
4114
+ let!(:depeche_mode) do
4115
+ Band.create!(name: "Depeche Mode")
4116
+ end
4117
+
4118
+ let!(:new_order) do
4119
+ Band.create!(name: "New Order")
4120
+ end
4121
+
4122
+ let!(:rolling_stones) do
4123
+ Band.create!(name: "The Rolling Stones")
4124
+ end
4125
+
4126
+ let!(:death_cab) do
4127
+ Band.create!(name: "Death Cab For Cutie")
4128
+ end
4129
+
4130
+ let(:context) do
4131
+ described_class.new(criteria)
4132
+ end
4133
+
4134
+ context "when there's no sort" do
4135
+ let(:criteria) do
4136
+ Band.all
4137
+ end
4138
+
4139
+ it "gets the second document" do
4140
+ expect(context.second!).to eq(new_order)
4141
+ end
4142
+ end
4143
+
4144
+ context "when there's a custom sort" do
4145
+ let(:criteria) do
4146
+ Band.all
4147
+ end
4148
+
4149
+ it "gets the second document" do
4150
+ expect(context.sort(name: 1).second!).to eq(depeche_mode)
4151
+ end
4152
+ end
4153
+
4154
+ context "when there are no documents" do
4155
+ let(:criteria) do
4156
+ Band.where(name: "bogus")
4157
+ end
4158
+
4159
+ it "raises an error" do
4160
+ expect do
4161
+ context.second!
4162
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
4163
+ end
4164
+ end
4165
+ end
4166
+
4167
+ describe "#third" do
3069
4168
 
3070
4169
  let!(:depeche_mode) do
3071
4170
  Band.create!(name: "Depeche Mode")
@@ -3075,20 +4174,50 @@ describe Mongoid::Contextual::Mongo do
3075
4174
  Band.create!(name: "New Order")
3076
4175
  end
3077
4176
 
3078
- let(:criteria) do
3079
- Band.all
4177
+ let!(:rolling_stones) do
4178
+ Band.create!(name: "The Rolling Stones")
4179
+ end
4180
+
4181
+ let!(:death_cab) do
4182
+ Band.create!(name: "Death Cab For Cutie")
3080
4183
  end
3081
4184
 
3082
4185
  let(:context) do
3083
4186
  described_class.new(criteria)
3084
4187
  end
3085
4188
 
3086
- it "limits the results" do
3087
- expect(context.skip(1).entries).to eq([ new_order ])
4189
+ context "when there's no sort" do
4190
+ let(:criteria) do
4191
+ Band.all
4192
+ end
4193
+
4194
+ it "gets the third document" do
4195
+ expect(context.third).to eq(rolling_stones)
4196
+ end
4197
+ end
4198
+
4199
+ context "when there's a custom sort" do
4200
+ let(:criteria) do
4201
+ Band.all
4202
+ end
4203
+
4204
+ it "gets the third document" do
4205
+ expect(context.sort(name: 1).third).to eq(new_order)
4206
+ end
4207
+ end
4208
+
4209
+ context "when there are no documents" do
4210
+ let(:criteria) do
4211
+ Band.where(name: "bogus")
4212
+ end
4213
+
4214
+ it "returns nil" do
4215
+ expect(context.third).to be_nil
4216
+ end
3088
4217
  end
3089
4218
  end
3090
4219
 
3091
- describe "#sort" do
4220
+ describe "#third!" do
3092
4221
 
3093
4222
  let!(:depeche_mode) do
3094
4223
  Band.create!(name: "Depeche Mode")
@@ -3098,40 +4227,52 @@ describe Mongoid::Contextual::Mongo do
3098
4227
  Band.create!(name: "New Order")
3099
4228
  end
3100
4229
 
3101
- let(:criteria) do
3102
- Band.all
4230
+ let!(:rolling_stones) do
4231
+ Band.create!(name: "The Rolling Stones")
4232
+ end
4233
+
4234
+ let!(:death_cab) do
4235
+ Band.create!(name: "Death Cab For Cutie")
3103
4236
  end
3104
4237
 
3105
4238
  let(:context) do
3106
4239
  described_class.new(criteria)
3107
4240
  end
3108
4241
 
3109
- context "when providing a spec" do
3110
-
3111
- it "sorts the results" do
3112
- expect(context.sort(name: -1).entries).to eq([ new_order, depeche_mode ])
4242
+ context "when there's no sort" do
4243
+ let(:criteria) do
4244
+ Band.all
3113
4245
  end
3114
4246
 
3115
- it "returns the context" do
3116
- expect(context.sort(name: 1)).to eq(context)
4247
+ it "gets the third document" do
4248
+ expect(context.third!).to eq(rolling_stones)
3117
4249
  end
3118
4250
  end
3119
4251
 
3120
- context "when providing a block" do
4252
+ context "when there's a custom sort" do
4253
+ let(:criteria) do
4254
+ Band.all
4255
+ end
3121
4256
 
3122
- let(:sorted) do
3123
- context.sort do |a, b|
3124
- b.name <=> a.name
3125
- end
4257
+ it "gets the third document" do
4258
+ expect(context.sort(name: 1).third!).to eq(new_order)
3126
4259
  end
4260
+ end
3127
4261
 
3128
- it "sorts the results in memory" do
3129
- expect(sorted).to eq([ new_order, depeche_mode ])
4262
+ context "when there are no documents" do
4263
+ let(:criteria) do
4264
+ Band.where(name: "bogus")
4265
+ end
4266
+
4267
+ it "raises an error" do
4268
+ expect do
4269
+ context.third!
4270
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
3130
4271
  end
3131
4272
  end
3132
4273
  end
3133
4274
 
3134
- describe "#update" do
4275
+ describe "#fourth" do
3135
4276
 
3136
4277
  let!(:depeche_mode) do
3137
4278
  Band.create!(name: "Depeche Mode")
@@ -3141,388 +4282,498 @@ describe Mongoid::Contextual::Mongo do
3141
4282
  Band.create!(name: "New Order")
3142
4283
  end
3143
4284
 
3144
- let(:criteria) do
3145
- Band.all
4285
+ let!(:rolling_stones) do
4286
+ Band.create!(name: "The Rolling Stones")
4287
+ end
4288
+
4289
+ let!(:death_cab) do
4290
+ Band.create!(name: "Death Cab For Cutie")
3146
4291
  end
3147
4292
 
3148
4293
  let(:context) do
3149
4294
  described_class.new(criteria)
3150
4295
  end
3151
4296
 
3152
- context "when adding an element to a HABTM set" do
3153
-
3154
- let(:person) do
3155
- Person.create!
4297
+ context "when there's no sort" do
4298
+ let(:criteria) do
4299
+ Band.all
3156
4300
  end
3157
4301
 
3158
- let(:preference) do
3159
- Preference.create!
4302
+ it "gets the fourth document" do
4303
+ expect(context.fourth).to eq(death_cab)
3160
4304
  end
4305
+ end
3161
4306
 
3162
- before do
3163
- Person.where(id: person.id).
3164
- update("$addToSet" => { preference_ids: preference.id })
4307
+ context "when there's a custom sort" do
4308
+ let(:criteria) do
4309
+ Band.all
3165
4310
  end
3166
4311
 
3167
- it "adds a single element to the array" do
3168
- expect(person.reload.preference_ids).to eq([ preference.id ])
4312
+ it "gets the fourth document" do
4313
+ expect(context.sort(name: 1).fourth).to eq(rolling_stones)
3169
4314
  end
3170
4315
  end
3171
4316
 
3172
- context "when providing attributes" do
4317
+ context "when there are no documents" do
4318
+ let(:criteria) do
4319
+ Band.where(name: "bogus")
4320
+ end
3173
4321
 
3174
- context "when the attributes are of the correct type" do
4322
+ it "returns nil" do
4323
+ expect(context.fourth).to be_nil
4324
+ end
4325
+ end
4326
+ end
3175
4327
 
3176
- before do
3177
- context.update(name: "Smiths")
3178
- end
4328
+ describe "#fourth!" do
3179
4329
 
3180
- it "updates only the first matching document" do
3181
- expect(depeche_mode.reload.name).to eq("Smiths")
3182
- end
4330
+ let!(:depeche_mode) do
4331
+ Band.create!(name: "Depeche Mode")
4332
+ end
3183
4333
 
3184
- it "does not update the last matching document" do
3185
- expect(new_order.reload.name).to eq("New Order")
3186
- end
3187
- end
4334
+ let!(:new_order) do
4335
+ Band.create!(name: "New Order")
4336
+ end
3188
4337
 
3189
- context "when the attributes must be mongoized" do
4338
+ let!(:rolling_stones) do
4339
+ Band.create!(name: "The Rolling Stones")
4340
+ end
3190
4341
 
3191
- context "when coercing a string to integer" do
4342
+ let!(:death_cab) do
4343
+ Band.create!(name: "Death Cab For Cutie")
4344
+ end
3192
4345
 
3193
- before do
3194
- context.update(member_count: "1")
3195
- end
4346
+ let(:context) do
4347
+ described_class.new(criteria)
4348
+ end
3196
4349
 
3197
- it "updates the first matching document" do
3198
- expect(depeche_mode.reload.member_count).to eq(1)
3199
- end
4350
+ context "when there's no sort" do
4351
+ let(:criteria) do
4352
+ Band.all
4353
+ end
3200
4354
 
3201
- it "does not update the last matching document" do
3202
- expect(new_order.reload.member_count).to be_nil
3203
- end
3204
- end
4355
+ it "gets the fourth document" do
4356
+ expect(context.fourth!).to eq(death_cab)
4357
+ end
4358
+ end
3205
4359
 
3206
- context "when coercing a string to date" do
4360
+ context "when there's a custom sort" do
4361
+ let(:criteria) do
4362
+ Band.all
4363
+ end
3207
4364
 
3208
- before do
3209
- context.update(founded: "1979/1/1")
3210
- end
4365
+ it "gets the fourth document" do
4366
+ expect(context.sort(name: 1).fourth!).to eq(rolling_stones)
4367
+ end
4368
+ end
3211
4369
 
3212
- it "updates the first matching document" do
3213
- expect(depeche_mode.reload.founded).to eq(Date.new(1979, 1, 1))
3214
- end
4370
+ context "when there are no documents" do
4371
+ let(:criteria) do
4372
+ Band.where(name: "bogus")
4373
+ end
3215
4374
 
3216
- it "does not update the last matching document" do
3217
- expect(new_order.reload.founded).to be_nil
3218
- end
3219
- end
4375
+ it "raises an error" do
4376
+ expect do
4377
+ context.fourth!
4378
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
3220
4379
  end
3221
4380
  end
4381
+ end
3222
4382
 
3223
- context "when providing atomic operations" do
4383
+ describe "#fifth" do
3224
4384
 
3225
- context "when only atomic operations are provided" do
4385
+ let!(:depeche_mode) do
4386
+ Band.create!(name: "Depeche Mode")
4387
+ end
3226
4388
 
3227
- context "when the attributes are in the correct type" do
4389
+ let!(:new_order) do
4390
+ Band.create!(name: "New Order")
4391
+ end
3228
4392
 
3229
- before do
3230
- context.update("$set" => { name: "Smiths" })
3231
- end
4393
+ let!(:rolling_stones) do
4394
+ Band.create!(name: "The Rolling Stones")
4395
+ end
3232
4396
 
3233
- it "updates the first matching document" do
3234
- expect(depeche_mode.reload.name).to eq("Smiths")
3235
- end
4397
+ let!(:death_cab) do
4398
+ Band.create!(name: "Death Cab For Cutie")
4399
+ end
3236
4400
 
3237
- it "does not update the last matching document" do
3238
- expect(new_order.reload.name).to eq("New Order")
3239
- end
3240
- end
4401
+ let!(:guns_and_roses) do
4402
+ Band.create!(name: "Guns and Roses")
4403
+ end
3241
4404
 
3242
- context "when the attributes must be mongoized" do
4405
+ let(:context) do
4406
+ described_class.new(criteria)
4407
+ end
3243
4408
 
3244
- before do
3245
- context.update("$set" => { member_count: "1" })
3246
- end
4409
+ context "when there's no sort" do
4410
+ let(:criteria) do
4411
+ Band.all
4412
+ end
3247
4413
 
3248
- it "updates the first matching document" do
3249
- expect(depeche_mode.reload.member_count).to eq(1)
3250
- end
4414
+ it "gets the fifth document" do
4415
+ expect(context.fifth).to eq(guns_and_roses)
4416
+ end
4417
+ end
3251
4418
 
3252
- it "does not update the last matching document" do
3253
- expect(new_order.reload.member_count).to be_nil
3254
- end
3255
- end
4419
+ context "when there's a custom sort" do
4420
+ let(:criteria) do
4421
+ Band.all
3256
4422
  end
3257
4423
 
3258
- context "when a mix are provided" do
4424
+ it "gets the fifth document" do
4425
+ expect(context.sort(name: 1).fifth).to eq(rolling_stones)
4426
+ end
4427
+ end
3259
4428
 
3260
- before do
3261
- context.update("$set" => { name: "Smiths" }, likes: 100)
3262
- end
4429
+ context "when there are no documents" do
4430
+ let(:criteria) do
4431
+ Band.where(name: "bogus")
4432
+ end
3263
4433
 
3264
- it "updates the first matching document's set" do
3265
- expect(depeche_mode.reload.name).to eq("Smiths")
3266
- end
4434
+ it "returns nil" do
4435
+ expect(context.fifth).to be_nil
4436
+ end
4437
+ end
4438
+ end
3267
4439
 
3268
- it "updates the first matching document's updates" do
3269
- expect(depeche_mode.reload.likes).to eq(100)
3270
- end
4440
+ describe "#fifth!" do
3271
4441
 
3272
- it "does not update the last matching document's set" do
3273
- expect(new_order.reload.name).to eq("New Order")
3274
- end
4442
+ let!(:depeche_mode) do
4443
+ Band.create!(name: "Depeche Mode")
4444
+ end
3275
4445
 
3276
- it "does not update the last matching document's updates" do
3277
- expect(new_order.reload.likes).to be_nil
3278
- end
3279
- end
4446
+ let!(:new_order) do
4447
+ Band.create!(name: "New Order")
3280
4448
  end
3281
4449
 
3282
- context "when providing no attributes" do
4450
+ let!(:rolling_stones) do
4451
+ Band.create!(name: "The Rolling Stones")
4452
+ end
3283
4453
 
3284
- it "returns false" do
3285
- expect(context.update).to be false
3286
- end
4454
+ let!(:death_cab) do
4455
+ Band.create!(name: "Death Cab For Cutie")
3287
4456
  end
3288
4457
 
3289
- context 'when provided array filters' do
3290
- min_server_version '3.6'
4458
+ let!(:guns_and_roses) do
4459
+ Band.create!(name: "Guns and Roses")
4460
+ end
3291
4461
 
3292
- before do
3293
- Band.delete_all
3294
- b = Band.new(name: 'Depeche Mode')
3295
- b.labels << Label.new(name: 'Warner')
3296
- b.labels << Label.new(name: 'Sony')
3297
- b.labels << Label.new(name: 'Cbs')
3298
- b.save!
4462
+ let(:context) do
4463
+ described_class.new(criteria)
4464
+ end
3299
4465
 
3300
- b = Band.new(name: 'FKA Twigs')
3301
- b.labels << Label.new(name: 'Warner')
3302
- b.labels << Label.new(name: 'Cbs')
3303
- b.save!
4466
+ context "when there's no sort" do
4467
+ let(:criteria) do
4468
+ Band.all
3304
4469
  end
3305
4470
 
4471
+ it "gets the fifth document" do
4472
+ expect(context.fifth!).to eq(guns_and_roses)
4473
+ end
4474
+ end
3306
4475
 
4476
+ context "when there's a custom sort" do
3307
4477
  let(:criteria) do
3308
- Band.where(name: 'Depeche Mode')
4478
+ Band.all
3309
4479
  end
3310
4480
 
3311
- let!(:update) do
3312
- context.update({ '$set' => { 'labels.$[i].name' => 'Sony' } },
3313
- array_filters: [{ 'i.name' => 'Cbs' }])
4481
+ it "gets the fifth document" do
4482
+ expect(context.sort(name: 1).fifth!).to eq(rolling_stones)
3314
4483
  end
4484
+ end
3315
4485
 
3316
- it 'applies the array filters' do
3317
- expect(Band.where(name: 'Depeche Mode').first.labels.collect(&:name)).to match_array(['Warner', 'Sony', 'Sony'])
4486
+ context "when there are no documents" do
4487
+ let(:criteria) do
4488
+ Band.where(name: "bogus")
3318
4489
  end
3319
4490
 
3320
- it 'does not affect other documents' do
3321
- expect(Band.where(name: 'FKA Twigs').first.labels.collect(&:name)).to match_array(['Warner', 'Cbs'])
4491
+ it "raises an error" do
4492
+ expect do
4493
+ context.fifth!
4494
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
3322
4495
  end
3323
4496
  end
3324
4497
  end
3325
4498
 
3326
- describe "#update_all" do
4499
+ describe "#second_to_last" do
3327
4500
 
3328
4501
  let!(:depeche_mode) do
3329
- Band.create!(name: "Depeche Mode", origin: "Essex")
4502
+ Band.create!(name: "Depeche Mode")
3330
4503
  end
3331
4504
 
3332
4505
  let!(:new_order) do
3333
4506
  Band.create!(name: "New Order")
3334
4507
  end
3335
4508
 
3336
- let(:criteria) do
3337
- Band.all
4509
+ let!(:rolling_stones) do
4510
+ Band.create!(name: "The Rolling Stones")
4511
+ end
4512
+
4513
+ let!(:death_cab) do
4514
+ Band.create!(name: "Death Cab For Cutie")
3338
4515
  end
3339
4516
 
3340
4517
  let(:context) do
3341
4518
  described_class.new(criteria)
3342
4519
  end
3343
4520
 
3344
- context "when providing attributes" do
4521
+ context "when there's no sort" do
4522
+ let(:criteria) do
4523
+ Band.all
4524
+ end
3345
4525
 
3346
- context "when the attributes are of the correct type" do
4526
+ it "gets the second_to_last document" do
4527
+ expect(context.second_to_last).to eq(rolling_stones)
4528
+ end
4529
+ end
3347
4530
 
3348
- before do
3349
- context.update_all(name: "Smiths")
3350
- end
4531
+ context "when there's a custom sort" do
4532
+ let(:criteria) do
4533
+ Band.all
4534
+ end
3351
4535
 
3352
- it "updates the first matching document" do
3353
- expect(depeche_mode.reload.name).to eq("Smiths")
3354
- end
4536
+ it "gets the second_to_last document" do
4537
+ expect(context.sort(name: 1).second_to_last).to eq(new_order)
4538
+ end
4539
+ end
3355
4540
 
3356
- it "does not clear out other attributes" do
3357
- expect(depeche_mode.reload.origin).to eq("Essex")
3358
- end
4541
+ context "when there are no documents" do
4542
+ let(:criteria) do
4543
+ Band.where(name: "bogus")
4544
+ end
3359
4545
 
3360
- it "updates the last matching document" do
3361
- expect(new_order.reload.name).to eq("Smiths")
3362
- end
4546
+ it "returns nil" do
4547
+ expect(context.second_to_last).to be_nil
3363
4548
  end
4549
+ end
4550
+ end
3364
4551
 
3365
- context "when the attributes must be mongoized" do
4552
+ describe "#second_to_last!" do
3366
4553
 
3367
- before do
3368
- context.update_all(member_count: "1")
3369
- end
4554
+ let!(:depeche_mode) do
4555
+ Band.create!(name: "Depeche Mode")
4556
+ end
3370
4557
 
3371
- it "updates the first matching document" do
3372
- expect(depeche_mode.reload.member_count).to eq(1)
3373
- end
4558
+ let!(:new_order) do
4559
+ Band.create!(name: "New Order")
4560
+ end
3374
4561
 
3375
- it "updates the last matching document" do
3376
- expect(new_order.reload.member_count).to eq(1)
3377
- end
3378
- end
4562
+ let!(:rolling_stones) do
4563
+ Band.create!(name: "The Rolling Stones")
4564
+ end
3379
4565
 
3380
- context "when using aliased field names" do
4566
+ let!(:death_cab) do
4567
+ Band.create!(name: "Death Cab For Cutie")
4568
+ end
3381
4569
 
3382
- before do
3383
- context.update_all(years: 100)
3384
- end
4570
+ let(:context) do
4571
+ described_class.new(criteria)
4572
+ end
3385
4573
 
3386
- it "updates the first matching document" do
3387
- expect(depeche_mode.reload.years).to eq(100)
3388
- end
4574
+ context "when there's no sort" do
4575
+ let(:criteria) do
4576
+ Band.all
4577
+ end
3389
4578
 
3390
- it "updates the last matching document" do
3391
- expect(new_order.reload.years).to eq(100)
3392
- end
4579
+ it "gets the second_to_last document" do
4580
+ expect(context.second_to_last!).to eq(rolling_stones)
3393
4581
  end
3394
4582
  end
3395
4583
 
3396
- context "when providing atomic operations" do
4584
+ context "when there's a custom sort" do
4585
+ let(:criteria) do
4586
+ Band.all
4587
+ end
3397
4588
 
3398
- context "when only atomic operations are provided" do
4589
+ it "gets the second_to_last document" do
4590
+ expect(context.sort(name: 1).second_to_last!).to eq(new_order)
4591
+ end
4592
+ end
3399
4593
 
3400
- context "when the attributes are in the correct type" do
4594
+ context "when there are no documents" do
4595
+ let(:criteria) do
4596
+ Band.where(name: "bogus")
4597
+ end
3401
4598
 
3402
- before do
3403
- context.update_all("$set" => { name: "Smiths" })
3404
- end
4599
+ it "raises an error" do
4600
+ expect do
4601
+ context.second_to_last!
4602
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
4603
+ end
4604
+ end
4605
+ end
3405
4606
 
3406
- it "updates the first matching document" do
3407
- expect(depeche_mode.reload.name).to eq("Smiths")
3408
- end
4607
+ describe "#third_to_last" do
3409
4608
 
3410
- it "updates the last matching document" do
3411
- expect(new_order.reload.name).to eq("Smiths")
3412
- end
3413
- end
4609
+ let!(:depeche_mode) do
4610
+ Band.create!(name: "Depeche Mode")
4611
+ end
3414
4612
 
3415
- context "when the attributes must be mongoized" do
4613
+ let!(:new_order) do
4614
+ Band.create!(name: "New Order")
4615
+ end
3416
4616
 
3417
- before do
3418
- context.update_all("$set" => { member_count: "1" })
3419
- end
4617
+ let!(:rolling_stones) do
4618
+ Band.create!(name: "The Rolling Stones")
4619
+ end
3420
4620
 
3421
- it "updates the first matching document" do
3422
- expect(depeche_mode.reload.member_count).to eq(1)
3423
- end
4621
+ let!(:death_cab) do
4622
+ Band.create!(name: "Death Cab For Cutie")
4623
+ end
3424
4624
 
3425
- it "updates the last matching document" do
3426
- expect(new_order.reload.member_count).to eq(1)
3427
- end
3428
- end
3429
- end
4625
+ let(:context) do
4626
+ described_class.new(criteria)
4627
+ end
3430
4628
 
3431
- context "when a mix are provided" do
4629
+ context "when there's no sort" do
4630
+ let(:criteria) do
4631
+ Band.all
4632
+ end
3432
4633
 
3433
- before do
3434
- context.update_all("$set" => { name: "Smiths" }, likes: 100)
3435
- end
4634
+ it "gets the third_to_last document" do
4635
+ expect(context.third_to_last).to eq(new_order)
4636
+ end
4637
+ end
3436
4638
 
3437
- it "updates the first matching document's set" do
3438
- expect(depeche_mode.reload.name).to eq("Smiths")
3439
- end
4639
+ context "when there's a custom sort" do
4640
+ let(:criteria) do
4641
+ Band.all
4642
+ end
3440
4643
 
3441
- it "updates the first matching document's updates" do
3442
- expect(depeche_mode.reload.likes).to eq(100)
3443
- end
4644
+ it "gets the third_to_last document" do
4645
+ expect(context.sort(name: 1).third_to_last).to eq(depeche_mode)
4646
+ end
4647
+ end
3444
4648
 
3445
- it "updates the last matching document's set" do
3446
- expect(new_order.reload.name).to eq("Smiths")
3447
- end
4649
+ context "when there are no documents" do
4650
+ let(:criteria) do
4651
+ Band.where(name: "bogus")
4652
+ end
3448
4653
 
3449
- it "updates the last matching document's updates" do
3450
- expect(new_order.reload.likes).to eq(100)
3451
- end
4654
+ it "returns nil" do
4655
+ expect(context.third_to_last).to be_nil
3452
4656
  end
3453
4657
  end
4658
+ end
3454
4659
 
3455
- context "when providing no attributes" do
4660
+ describe "#third_to_last!" do
3456
4661
 
3457
- it "returns false" do
3458
- expect(context.update_all).to be false
3459
- end
4662
+ let!(:depeche_mode) do
4663
+ Band.create!(name: "Depeche Mode")
3460
4664
  end
3461
4665
 
3462
- context 'when provided array filters' do
3463
- min_server_version '3.6'
4666
+ let!(:new_order) do
4667
+ Band.create!(name: "New Order")
4668
+ end
3464
4669
 
3465
- before do
3466
- Band.delete_all
3467
- b = Band.new(name: 'Depeche Mode')
3468
- b.labels << Label.new(name: 'Warner')
3469
- b.labels << Label.new(name: 'Sony')
3470
- b.labels << Label.new(name: 'Cbs')
3471
- b.save!
4670
+ let!(:rolling_stones) do
4671
+ Band.create!(name: "The Rolling Stones")
4672
+ end
3472
4673
 
3473
- b = Band.new(name: 'FKA Twigs')
3474
- b.labels << Label.new(name: 'Warner')
3475
- b.labels << Label.new(name: 'Cbs')
3476
- b.save!
4674
+ let!(:death_cab) do
4675
+ Band.create!(name: "Death Cab For Cutie")
4676
+ end
4677
+
4678
+ let(:context) do
4679
+ described_class.new(criteria)
4680
+ end
4681
+
4682
+ context "when there's no sort" do
4683
+ let(:criteria) do
4684
+ Band.all
3477
4685
  end
3478
4686
 
4687
+ it "gets the third_to_last document" do
4688
+ expect(context.third_to_last!).to eq(new_order)
4689
+ end
4690
+ end
3479
4691
 
4692
+ context "when there's a custom sort" do
3480
4693
  let(:criteria) do
3481
4694
  Band.all
3482
4695
  end
3483
4696
 
3484
- let!(:update) do
3485
- context.update_all({ '$set' => { 'labels.$[i].name' => 'Sony' } },
3486
- array_filters: [{ 'i.name' => 'Cbs' }])
4697
+ it "gets the third_to_last document" do
4698
+ expect(context.sort(name: 1).third_to_last!).to eq(depeche_mode)
3487
4699
  end
4700
+ end
3488
4701
 
3489
- it 'applies the array filters' do
3490
- expect(Band.where(name: 'Depeche Mode').first.labels.collect(&:name)).to match_array(['Warner', 'Sony', 'Sony'])
4702
+ context "when there are no documents" do
4703
+ let(:criteria) do
4704
+ Band.where(name: "bogus")
3491
4705
  end
3492
4706
 
3493
- it 'updates all documents' do
3494
- expect(Band.where(name: 'FKA Twigs').first.labels.collect(&:name)).to match_array(['Warner', 'Sony'])
4707
+ it "raises an error" do
4708
+ expect do
4709
+ context.third_to_last!
4710
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
3495
4711
  end
3496
4712
  end
3497
4713
  end
3498
4714
 
3499
- describe '#pipeline' do
4715
+ describe '#load_async' do
4716
+ let!(:band) do
4717
+ Band.create!(name: "Depeche Mode")
4718
+ end
3500
4719
 
3501
- context 'when the criteria has a selector' do
4720
+ let(:criteria) do
4721
+ Band.where(name: "Depeche Mode")
4722
+ end
3502
4723
 
3503
- before do
3504
- Artist.index(name: "text")
3505
- Artist.create_indexes
3506
- end
4724
+ let(:context) do
4725
+ described_class.new(criteria)
4726
+ end
3507
4727
 
3508
- let(:criteria) do
3509
- Artist.text_search("New Order")
3510
- end
4728
+ context 'with global thread pool async query executor' do
4729
+ config_override :async_query_executor, :global_thread_pool
3511
4730
 
3512
- let(:context) do
3513
- described_class.new(criteria)
4731
+ it 'preloads the documents' do
4732
+ context.load_async
4733
+ context.documents_loader.wait
4734
+
4735
+ expect(context.view).not_to receive(:map)
4736
+ expect(context.to_a).to eq([band])
3514
4737
  end
3515
4738
 
3516
- let(:pipeline_match) do
3517
- context.send(:pipeline, :some_field).first['$match']
4739
+ it 're-raises exception during preload' do
4740
+ expect_any_instance_of(Mongoid::Contextual::Mongo::DocumentsLoader)
4741
+ .to receive(:execute)
4742
+ .at_least(:once)
4743
+ .and_raise(Mongo::Error::OperationFailure)
4744
+
4745
+ context.load_async
4746
+ context.documents_loader.wait
4747
+
4748
+ expect do
4749
+ context.to_a
4750
+ end.to raise_error(Mongo::Error::OperationFailure)
3518
4751
  end
4752
+ end
3519
4753
 
3520
- it 'creates a pipeline with the selector as one of the $match criteria' do
3521
- expect(pipeline_match).to include({ '$text' => { '$search' => "New Order" } })
4754
+ context 'with immediate thread pool async query executor' do
4755
+ config_override :async_query_executor, :immediate
4756
+
4757
+ it 'preloads the documents' do
4758
+ context.load_async
4759
+ context.documents_loader.wait
4760
+
4761
+ expect(context.view).not_to receive(:map)
4762
+ expect(context.to_a).to eq([band])
3522
4763
  end
3523
4764
 
3524
- it 'creates a pipeline with the $exists operator as one of the $match criteria' do
3525
- expect(pipeline_match).to include({ 'some_field' => { '$exists' => true } })
4765
+ it 're-raises exception during preload' do
4766
+ expect_any_instance_of(Mongoid::Contextual::Mongo::DocumentsLoader)
4767
+ .to receive(:execute)
4768
+ .at_least(:once)
4769
+ .and_raise(Mongo::Error::OperationFailure)
4770
+
4771
+ context.load_async
4772
+ context.documents_loader.wait
4773
+
4774
+ expect do
4775
+ context.to_a
4776
+ end.to raise_error(Mongo::Error::OperationFailure)
3526
4777
  end
3527
4778
  end
3528
4779
  end