mongoid 7.5.1 → 8.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (429) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +3 -3
  4. data/README.md +6 -6
  5. data/Rakefile +25 -0
  6. data/lib/config/locales/en.yml +92 -43
  7. data/lib/mongoid/association/accessors.rb +40 -11
  8. data/lib/mongoid/association/bindable.rb +50 -2
  9. data/lib/mongoid/association/builders.rb +5 -3
  10. data/lib/mongoid/association/constrainable.rb +0 -1
  11. data/lib/mongoid/association/eager_loadable.rb +29 -7
  12. data/lib/mongoid/association/embedded/batchable.rb +54 -14
  13. data/lib/mongoid/association/embedded/cyclic.rb +1 -1
  14. data/lib/mongoid/association/embedded/embedded_in/binding.rb +24 -2
  15. data/lib/mongoid/association/embedded/embedded_in/buildable.rb +2 -2
  16. data/lib/mongoid/association/embedded/embedded_in/proxy.rb +4 -3
  17. data/lib/mongoid/association/embedded/embedded_in.rb +3 -2
  18. data/lib/mongoid/association/embedded/embeds_many/binding.rb +1 -0
  19. data/lib/mongoid/association/embedded/embeds_many/buildable.rb +4 -3
  20. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +85 -46
  21. data/lib/mongoid/association/embedded/embeds_many.rb +2 -2
  22. data/lib/mongoid/association/embedded/embeds_one/buildable.rb +19 -5
  23. data/lib/mongoid/association/embedded/embeds_one/proxy.rb +24 -5
  24. data/lib/mongoid/association/embedded/embeds_one.rb +3 -3
  25. data/lib/mongoid/association/macros.rb +8 -1
  26. data/lib/mongoid/association/many.rb +11 -7
  27. data/lib/mongoid/association/nested/many.rb +5 -4
  28. data/lib/mongoid/association/nested/nested_buildable.rb +4 -4
  29. data/lib/mongoid/association/nested/one.rb +45 -7
  30. data/lib/mongoid/association/one.rb +2 -2
  31. data/lib/mongoid/association/options.rb +9 -9
  32. data/lib/mongoid/association/proxy.rb +15 -4
  33. data/lib/mongoid/association/referenced/auto_save.rb +4 -3
  34. data/lib/mongoid/association/referenced/belongs_to/binding.rb +1 -0
  35. data/lib/mongoid/association/referenced/belongs_to/buildable.rb +1 -1
  36. data/lib/mongoid/association/referenced/belongs_to/proxy.rb +5 -6
  37. data/lib/mongoid/association/referenced/belongs_to.rb +2 -2
  38. data/lib/mongoid/association/referenced/counter_cache.rb +10 -10
  39. data/lib/mongoid/association/referenced/eager.rb +2 -2
  40. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +70 -13
  41. data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +6 -3
  42. data/lib/mongoid/association/referenced/has_many/enumerable.rb +22 -30
  43. data/lib/mongoid/association/referenced/has_many/proxy.rb +40 -21
  44. data/lib/mongoid/association/referenced/has_many.rb +3 -3
  45. data/lib/mongoid/association/referenced/has_one/buildable.rb +1 -1
  46. data/lib/mongoid/association/referenced/has_one/nested_builder.rb +5 -5
  47. data/lib/mongoid/association/referenced/has_one/proxy.rb +9 -12
  48. data/lib/mongoid/association/referenced/has_one.rb +3 -3
  49. data/lib/mongoid/association/referenced/syncable.rb +4 -4
  50. data/lib/mongoid/association/reflections.rb +4 -4
  51. data/lib/mongoid/association/relatable.rb +44 -10
  52. data/lib/mongoid/association.rb +5 -5
  53. data/lib/mongoid/atomic/modifiers.rb +2 -2
  54. data/lib/mongoid/atomic/paths/embedded/many.rb +19 -0
  55. data/lib/mongoid/atomic.rb +7 -0
  56. data/lib/mongoid/attributes/dynamic.rb +4 -4
  57. data/lib/mongoid/attributes/nested.rb +6 -6
  58. data/lib/mongoid/attributes/processing.rb +37 -6
  59. data/lib/mongoid/attributes/projector.rb +2 -2
  60. data/lib/mongoid/attributes/readonly.rb +3 -3
  61. data/lib/mongoid/attributes.rb +51 -42
  62. data/lib/mongoid/cacheable.rb +2 -2
  63. data/lib/mongoid/changeable.rb +147 -14
  64. data/lib/mongoid/clients/options.rb +5 -1
  65. data/lib/mongoid/clients/sessions.rb +2 -14
  66. data/lib/mongoid/clients/storage_options.rb +2 -5
  67. data/lib/mongoid/clients/validators/storage.rb +3 -15
  68. data/lib/mongoid/collection_configurable.rb +58 -0
  69. data/lib/mongoid/composable.rb +2 -0
  70. data/lib/mongoid/config/defaults.rb +60 -0
  71. data/lib/mongoid/config/options.rb +3 -0
  72. data/lib/mongoid/config/validators/async_query_executor.rb +24 -0
  73. data/lib/mongoid/config/validators/client.rb +6 -6
  74. data/lib/mongoid/config/validators.rb +1 -0
  75. data/lib/mongoid/config.rb +158 -17
  76. data/lib/mongoid/contextual/aggregable/memory.rb +24 -16
  77. data/lib/mongoid/contextual/aggregable/mongo.rb +5 -5
  78. data/lib/mongoid/contextual/aggregable/none.rb +1 -1
  79. data/lib/mongoid/contextual/atomic.rb +1 -1
  80. data/lib/mongoid/contextual/geo_near.rb +7 -7
  81. data/lib/mongoid/contextual/map_reduce.rb +2 -2
  82. data/lib/mongoid/contextual/memory.rb +285 -58
  83. data/lib/mongoid/contextual/mongo/documents_loader.rb +177 -0
  84. data/lib/mongoid/contextual/mongo.rb +544 -333
  85. data/lib/mongoid/contextual/none.rb +193 -20
  86. data/lib/mongoid/contextual/queryable.rb +1 -1
  87. data/lib/mongoid/contextual.rb +14 -2
  88. data/lib/mongoid/copyable.rb +32 -8
  89. data/lib/mongoid/criteria/findable.rb +8 -5
  90. data/lib/mongoid/criteria/includable.rb +27 -22
  91. data/lib/mongoid/criteria/marshalable.rb +10 -2
  92. data/lib/mongoid/criteria/permission.rb +1 -1
  93. data/lib/mongoid/criteria/queryable/aggregable.rb +2 -2
  94. data/lib/mongoid/criteria/queryable/extensions/array.rb +3 -16
  95. data/lib/mongoid/criteria/queryable/extensions/big_decimal.rb +25 -4
  96. data/lib/mongoid/criteria/queryable/extensions/boolean.rb +2 -2
  97. data/lib/mongoid/criteria/queryable/extensions/date.rb +6 -1
  98. data/lib/mongoid/criteria/queryable/extensions/date_time.rb +6 -1
  99. data/lib/mongoid/criteria/queryable/extensions/hash.rb +1 -17
  100. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -9
  101. data/lib/mongoid/criteria/queryable/extensions/object.rb +2 -1
  102. data/lib/mongoid/criteria/queryable/extensions/range.rb +13 -5
  103. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +3 -3
  104. data/lib/mongoid/criteria/queryable/extensions/set.rb +1 -1
  105. data/lib/mongoid/criteria/queryable/extensions/string.rb +4 -14
  106. data/lib/mongoid/criteria/queryable/extensions/symbol.rb +4 -12
  107. data/lib/mongoid/criteria/queryable/extensions/time.rb +6 -1
  108. data/lib/mongoid/criteria/queryable/extensions/time_with_zone.rb +6 -1
  109. data/lib/mongoid/criteria/queryable/key.rb +4 -4
  110. data/lib/mongoid/criteria/queryable/mergeable.rb +1 -1
  111. data/lib/mongoid/criteria/queryable/optional.rb +11 -17
  112. data/lib/mongoid/criteria/queryable/options.rb +2 -2
  113. data/lib/mongoid/criteria/queryable/pipeline.rb +1 -1
  114. data/lib/mongoid/criteria/queryable/selectable.rb +47 -38
  115. data/lib/mongoid/criteria/queryable/selector.rb +93 -8
  116. data/lib/mongoid/criteria/queryable/smash.rb +40 -7
  117. data/lib/mongoid/criteria/queryable/storable.rb +1 -1
  118. data/lib/mongoid/criteria/queryable.rb +12 -7
  119. data/lib/mongoid/criteria/scopable.rb +2 -2
  120. data/lib/mongoid/criteria/translator.rb +45 -0
  121. data/lib/mongoid/criteria.rb +20 -40
  122. data/lib/mongoid/deprecable.rb +37 -0
  123. data/lib/mongoid/deprecation.rb +25 -0
  124. data/lib/mongoid/document.rb +135 -36
  125. data/lib/mongoid/equality.rb +8 -8
  126. data/lib/mongoid/errors/create_collection_failure.rb +33 -0
  127. data/lib/mongoid/errors/document_not_found.rb +10 -6
  128. data/lib/mongoid/errors/drop_collection_failure.rb +27 -0
  129. data/lib/mongoid/errors/immutable_attribute.rb +26 -0
  130. data/lib/mongoid/errors/invalid_async_query_executor.rb +25 -0
  131. data/lib/mongoid/errors/invalid_config_option.rb +1 -1
  132. data/lib/mongoid/errors/invalid_dependent_strategy.rb +1 -1
  133. data/lib/mongoid/errors/invalid_dot_dollar_assignment.rb +23 -0
  134. data/lib/mongoid/errors/invalid_field.rb +6 -2
  135. data/lib/mongoid/errors/invalid_field_type.rb +26 -0
  136. data/lib/mongoid/errors/invalid_global_executor_concurrency.rb +22 -0
  137. data/lib/mongoid/errors/invalid_relation.rb +1 -1
  138. data/lib/mongoid/errors/invalid_relation_option.rb +1 -1
  139. data/lib/mongoid/errors/invalid_session_use.rb +1 -1
  140. data/lib/mongoid/errors/invalid_storage_options.rb +1 -1
  141. data/lib/mongoid/errors/invalid_storage_parent.rb +2 -0
  142. data/lib/mongoid/errors/mongoid_error.rb +3 -3
  143. data/lib/mongoid/errors/nested_attributes_metadata_not_found.rb +1 -1
  144. data/lib/mongoid/errors/no_client_database.rb +1 -1
  145. data/lib/mongoid/errors/no_client_hosts.rb +1 -1
  146. data/lib/mongoid/errors/readonly_attribute.rb +1 -1
  147. data/lib/mongoid/errors/too_many_nested_attribute_records.rb +1 -1
  148. data/lib/mongoid/errors/unknown_attribute.rb +1 -1
  149. data/lib/mongoid/errors.rb +6 -3
  150. data/lib/mongoid/extensions/array.rb +9 -7
  151. data/lib/mongoid/extensions/big_decimal.rb +33 -10
  152. data/lib/mongoid/extensions/binary.rb +42 -0
  153. data/lib/mongoid/extensions/boolean.rb +8 -2
  154. data/lib/mongoid/extensions/date.rb +26 -20
  155. data/lib/mongoid/extensions/date_time.rb +1 -1
  156. data/lib/mongoid/extensions/false_class.rb +1 -1
  157. data/lib/mongoid/extensions/float.rb +7 -4
  158. data/lib/mongoid/extensions/hash.rb +37 -8
  159. data/lib/mongoid/extensions/integer.rb +7 -4
  160. data/lib/mongoid/extensions/module.rb +1 -1
  161. data/lib/mongoid/extensions/object.rb +10 -8
  162. data/lib/mongoid/extensions/range.rb +41 -10
  163. data/lib/mongoid/extensions/regexp.rb +11 -4
  164. data/lib/mongoid/extensions/set.rb +11 -4
  165. data/lib/mongoid/extensions/string.rb +11 -22
  166. data/lib/mongoid/extensions/symbol.rb +4 -15
  167. data/lib/mongoid/extensions/time.rb +29 -16
  168. data/lib/mongoid/extensions/time_with_zone.rb +1 -2
  169. data/lib/mongoid/extensions/true_class.rb +1 -1
  170. data/lib/mongoid/extensions.rb +1 -0
  171. data/lib/mongoid/factory.rb +55 -7
  172. data/lib/mongoid/fields/foreign_key.rb +11 -4
  173. data/lib/mongoid/fields/localized.rb +19 -4
  174. data/lib/mongoid/fields/standard.rb +17 -7
  175. data/lib/mongoid/fields/validators/macro.rb +3 -9
  176. data/lib/mongoid/fields.rb +129 -20
  177. data/lib/mongoid/findable.rb +54 -24
  178. data/lib/mongoid/indexable/specification.rb +2 -2
  179. data/lib/mongoid/indexable/validators/options.rb +6 -2
  180. data/lib/mongoid/interceptable.rb +186 -16
  181. data/lib/mongoid/matchable.rb +1 -1
  182. data/lib/mongoid/matcher/eq_impl.rb +1 -1
  183. data/lib/mongoid/matcher/type.rb +1 -1
  184. data/lib/mongoid/matcher.rb +33 -13
  185. data/lib/mongoid/persistable/creatable.rb +19 -9
  186. data/lib/mongoid/persistable/deletable.rb +2 -2
  187. data/lib/mongoid/persistable/destroyable.rb +1 -1
  188. data/lib/mongoid/persistable/savable.rb +14 -2
  189. data/lib/mongoid/persistable/unsettable.rb +2 -2
  190. data/lib/mongoid/persistable/updatable.rb +69 -12
  191. data/lib/mongoid/persistable/upsertable.rb +21 -2
  192. data/lib/mongoid/persistable.rb +6 -3
  193. data/lib/mongoid/persistence_context.rb +63 -10
  194. data/lib/mongoid/query_cache.rb +13 -261
  195. data/lib/mongoid/railties/controller_runtime.rb +1 -1
  196. data/lib/mongoid/railties/database.rake +7 -2
  197. data/lib/mongoid/reloadable.rb +10 -8
  198. data/lib/mongoid/scopable.rb +15 -13
  199. data/lib/mongoid/selectable.rb +1 -2
  200. data/lib/mongoid/serializable.rb +10 -6
  201. data/lib/mongoid/shardable.rb +35 -11
  202. data/lib/mongoid/stateful.rb +57 -10
  203. data/lib/mongoid/tasks/database.rake +12 -0
  204. data/lib/mongoid/tasks/database.rb +20 -2
  205. data/lib/mongoid/threaded/lifecycle.rb +5 -5
  206. data/lib/mongoid/threaded.rb +42 -12
  207. data/lib/mongoid/timestamps/created.rb +1 -1
  208. data/lib/mongoid/timestamps/updated.rb +2 -2
  209. data/lib/mongoid/touchable.rb +2 -3
  210. data/lib/mongoid/traversable.rb +5 -4
  211. data/lib/mongoid/utils.rb +22 -0
  212. data/lib/mongoid/validatable/localizable.rb +1 -1
  213. data/lib/mongoid/validatable/macros.rb +5 -7
  214. data/lib/mongoid/validatable/presence.rb +2 -2
  215. data/lib/mongoid/validatable/uniqueness.rb +9 -8
  216. data/lib/mongoid/validatable.rb +9 -6
  217. data/lib/mongoid/version.rb +1 -1
  218. data/lib/mongoid/warnings.rb +19 -4
  219. data/lib/mongoid.rb +17 -3
  220. data/spec/config/mongoid.yml +16 -0
  221. data/spec/integration/app_spec.rb +10 -14
  222. data/spec/integration/associations/belongs_to_spec.rb +18 -0
  223. data/spec/integration/associations/embedded_spec.rb +15 -0
  224. data/spec/integration/associations/embeds_many_spec.rb +15 -2
  225. data/spec/integration/associations/embeds_one_spec.rb +18 -0
  226. data/spec/integration/associations/foreign_key_spec.rb +9 -0
  227. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +21 -0
  228. data/spec/integration/associations/has_one_spec.rb +97 -1
  229. data/spec/integration/associations/scope_option_spec.rb +1 -1
  230. data/spec/integration/callbacks_models.rb +132 -1
  231. data/spec/integration/callbacks_spec.rb +381 -4
  232. data/spec/integration/criteria/range_spec.rb +95 -1
  233. data/spec/integration/discriminator_key_spec.rb +118 -80
  234. data/spec/integration/dots_and_dollars_spec.rb +277 -0
  235. data/spec/integration/i18n_fallbacks_spec.rb +3 -32
  236. data/spec/integration/matcher_examples_spec.rb +20 -13
  237. data/spec/integration/matcher_operator_data/type_decimal.yml +3 -2
  238. data/spec/integration/matcher_operator_spec.rb +3 -5
  239. data/spec/integration/persistence/range_field_spec.rb +350 -0
  240. data/spec/mongoid/association/counter_cache_spec.rb +1 -1
  241. data/spec/mongoid/association/depending_spec.rb +9 -9
  242. data/spec/mongoid/association/eager_spec.rb +2 -1
  243. data/spec/mongoid/association/embedded/embedded_in/binding_spec.rb +2 -1
  244. data/spec/mongoid/association/embedded/embedded_in/buildable_spec.rb +54 -0
  245. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +96 -9
  246. data/spec/mongoid/association/embedded/embeds_many/buildable_spec.rb +112 -0
  247. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +311 -65
  248. data/spec/mongoid/association/embedded/embeds_many_models.rb +158 -0
  249. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +12 -0
  250. data/spec/mongoid/association/embedded/embeds_many_spec.rb +68 -0
  251. data/spec/mongoid/association/embedded/embeds_one/buildable_spec.rb +25 -0
  252. data/spec/mongoid/association/embedded/embeds_one/proxy_spec.rb +15 -2
  253. data/spec/mongoid/association/embedded/embeds_one_models.rb +19 -0
  254. data/spec/mongoid/association/embedded/embeds_one_spec.rb +28 -0
  255. data/spec/mongoid/association/referenced/belongs_to/binding_spec.rb +2 -1
  256. data/spec/mongoid/association/referenced/belongs_to/buildable_spec.rb +54 -0
  257. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +15 -0
  258. data/spec/mongoid/association/referenced/belongs_to_models.rb +11 -0
  259. data/spec/mongoid/association/referenced/belongs_to_spec.rb +4 -20
  260. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +215 -228
  261. data/spec/mongoid/association/referenced/has_and_belongs_to_many_models.rb +25 -0
  262. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +35 -2
  263. data/spec/mongoid/association/referenced/has_many/buildable_spec.rb +109 -0
  264. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +2 -56
  265. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +235 -177
  266. data/spec/mongoid/association/referenced/has_many_models.rb +3 -1
  267. data/spec/mongoid/association/referenced/has_many_spec.rb +25 -0
  268. data/spec/mongoid/association/referenced/has_one/buildable_spec.rb +2 -2
  269. data/spec/mongoid/association/referenced/has_one/proxy_spec.rb +107 -1
  270. data/spec/mongoid/association/referenced/has_one_models.rb +16 -0
  271. data/spec/mongoid/association/syncable_spec.rb +15 -1
  272. data/spec/mongoid/atomic/paths_spec.rb +0 -14
  273. data/spec/mongoid/attributes/nested_spec.rb +80 -11
  274. data/spec/mongoid/attributes/nested_spec_models.rb +48 -0
  275. data/spec/mongoid/attributes/projector_spec.rb +1 -5
  276. data/spec/mongoid/attributes_spec.rb +554 -33
  277. data/spec/mongoid/cacheable_spec.rb +3 -3
  278. data/spec/mongoid/changeable_spec.rb +429 -37
  279. data/spec/mongoid/clients/factory_spec.rb +23 -30
  280. data/spec/mongoid/clients/sessions_spec.rb +0 -38
  281. data/spec/mongoid/clients_spec.rb +179 -15
  282. data/spec/mongoid/collection_configurable_spec.rb +158 -0
  283. data/spec/mongoid/config/defaults_spec.rb +160 -0
  284. data/spec/mongoid/config_spec.rb +220 -30
  285. data/spec/mongoid/contextual/aggregable/memory_spec.rb +396 -158
  286. data/spec/mongoid/contextual/aggregable/memory_table.yml +88 -0
  287. data/spec/mongoid/contextual/aggregable/memory_table_spec.rb +62 -0
  288. data/spec/mongoid/contextual/map_reduce_spec.rb +2 -16
  289. data/spec/mongoid/contextual/memory_spec.rb +850 -88
  290. data/spec/mongoid/contextual/mongo/documents_loader_spec.rb +187 -0
  291. data/spec/mongoid/contextual/mongo_spec.rb +2307 -1105
  292. data/spec/mongoid/contextual/none_spec.rb +60 -21
  293. data/spec/mongoid/copyable_spec.rb +453 -11
  294. data/spec/mongoid/criteria/findable_spec.rb +86 -210
  295. data/spec/mongoid/criteria/includable_spec.rb +1492 -0
  296. data/spec/mongoid/criteria/includable_spec_models.rb +54 -0
  297. data/spec/mongoid/criteria/marshalable_spec.rb +18 -1
  298. data/spec/mongoid/criteria/queryable/extensions/array_spec.rb +7 -19
  299. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +134 -26
  300. data/spec/mongoid/criteria/queryable/extensions/date_spec.rb +11 -0
  301. data/spec/mongoid/criteria/queryable/extensions/date_time_spec.rb +11 -0
  302. data/spec/mongoid/criteria/queryable/extensions/hash_spec.rb +0 -15
  303. data/spec/mongoid/criteria/queryable/extensions/numeric_spec.rb +73 -7
  304. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +4 -69
  305. data/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb +0 -59
  306. data/spec/mongoid/criteria/queryable/extensions/time_spec.rb +11 -0
  307. data/spec/mongoid/criteria/queryable/extensions/time_with_zone_spec.rb +11 -0
  308. data/spec/mongoid/criteria/queryable/optional_spec.rb +15 -484
  309. data/spec/mongoid/criteria/queryable/options_spec.rb +1 -1
  310. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +469 -0
  311. data/spec/mongoid/criteria/queryable/selectable_spec.rb +78 -86
  312. data/spec/mongoid/criteria/queryable/selector_spec.rb +90 -5
  313. data/spec/mongoid/criteria/queryable/storable_spec.rb +72 -0
  314. data/spec/mongoid/criteria/translator_spec.rb +132 -0
  315. data/spec/mongoid/criteria_projection_spec.rb +1 -5
  316. data/spec/mongoid/criteria_spec.rb +469 -1205
  317. data/spec/mongoid/document_fields_spec.rb +173 -24
  318. data/spec/mongoid/document_spec.rb +32 -41
  319. data/spec/mongoid/errors/document_not_found_spec.rb +29 -2
  320. data/spec/mongoid/errors/invalid_field_spec.rb +1 -1
  321. data/spec/mongoid/errors/invalid_field_type_spec.rb +55 -0
  322. data/spec/mongoid/errors/mongoid_error_spec.rb +3 -1
  323. data/spec/mongoid/errors/no_environment_spec.rb +3 -3
  324. data/spec/mongoid/errors/readonly_document_spec.rb +2 -2
  325. data/spec/mongoid/errors/too_many_nested_attribute_records_spec.rb +1 -1
  326. data/spec/mongoid/extensions/array_spec.rb +16 -2
  327. data/spec/mongoid/extensions/big_decimal_spec.rb +712 -212
  328. data/spec/mongoid/extensions/binary_spec.rb +44 -9
  329. data/spec/mongoid/extensions/boolean_spec.rb +68 -82
  330. data/spec/mongoid/extensions/date_class_mongoize_spec.rb +7 -3
  331. data/spec/mongoid/extensions/date_spec.rb +71 -1
  332. data/spec/mongoid/extensions/date_time_spec.rb +15 -9
  333. data/spec/mongoid/extensions/float_spec.rb +53 -74
  334. data/spec/mongoid/extensions/hash_spec.rb +33 -3
  335. data/spec/mongoid/extensions/integer_spec.rb +50 -64
  336. data/spec/mongoid/extensions/range_spec.rb +255 -54
  337. data/spec/mongoid/extensions/regexp_spec.rb +58 -33
  338. data/spec/mongoid/extensions/set_spec.rb +106 -0
  339. data/spec/mongoid/extensions/string_spec.rb +53 -25
  340. data/spec/mongoid/extensions/symbol_spec.rb +18 -25
  341. data/spec/mongoid/extensions/time_spec.rb +639 -106
  342. data/spec/mongoid/extensions/time_with_zone_spec.rb +24 -83
  343. data/spec/mongoid/factory_spec.rb +61 -1
  344. data/spec/mongoid/fields/localized_spec.rb +80 -37
  345. data/spec/mongoid/fields_spec.rb +500 -84
  346. data/spec/mongoid/findable_spec.rb +450 -58
  347. data/spec/mongoid/indexable/specification_spec.rb +2 -2
  348. data/spec/mongoid/indexable_spec.rb +55 -30
  349. data/spec/mongoid/interceptable_spec.rb +824 -22
  350. data/spec/mongoid/interceptable_spec_models.rb +235 -4
  351. data/spec/mongoid/matcher/extract_attribute_spec.rb +1 -5
  352. data/spec/mongoid/mongoizable_spec.rb +285 -0
  353. data/spec/mongoid/persistable/creatable_spec.rb +2 -2
  354. data/spec/mongoid/persistable/deletable_spec.rb +28 -8
  355. data/spec/mongoid/persistable/destroyable_spec.rb +28 -8
  356. data/spec/mongoid/persistable/incrementable_spec.rb +37 -0
  357. data/spec/mongoid/persistable/logical_spec.rb +37 -0
  358. data/spec/mongoid/persistable/poppable_spec.rb +36 -0
  359. data/spec/mongoid/persistable/pullable_spec.rb +72 -0
  360. data/spec/mongoid/persistable/pushable_spec.rb +72 -0
  361. data/spec/mongoid/persistable/renamable_spec.rb +36 -0
  362. data/spec/mongoid/persistable/savable_spec.rb +96 -0
  363. data/spec/mongoid/persistable/settable_spec.rb +37 -0
  364. data/spec/mongoid/persistable/unsettable_spec.rb +36 -0
  365. data/spec/mongoid/persistable/updatable_spec.rb +20 -28
  366. data/spec/mongoid/persistable/upsertable_spec.rb +89 -1
  367. data/spec/mongoid/persistence_context_spec.rb +57 -58
  368. data/spec/mongoid/query_cache_middleware_spec.rb +0 -18
  369. data/spec/mongoid/query_cache_spec.rb +56 -215
  370. data/spec/mongoid/reloadable_spec.rb +83 -6
  371. data/spec/mongoid/scopable_spec.rb +91 -1
  372. data/spec/mongoid/serializable_spec.rb +9 -30
  373. data/spec/mongoid/shardable_models.rb +14 -0
  374. data/spec/mongoid/shardable_spec.rb +157 -51
  375. data/spec/mongoid/stateful_spec.rb +150 -8
  376. data/spec/mongoid/tasks/database_rake_spec.rb +74 -0
  377. data/spec/mongoid/tasks/database_spec.rb +127 -0
  378. data/spec/mongoid/timestamps_spec.rb +392 -4
  379. data/spec/mongoid/timestamps_spec_models.rb +67 -0
  380. data/spec/mongoid/touchable_spec.rb +390 -2
  381. data/spec/mongoid/touchable_spec_models.rb +14 -8
  382. data/spec/mongoid/traversable_spec.rb +13 -35
  383. data/spec/mongoid/validatable/presence_spec.rb +1 -1
  384. data/spec/mongoid/validatable/uniqueness_spec.rb +58 -31
  385. data/spec/mongoid/warnings_spec.rb +35 -0
  386. data/spec/mongoid_spec.rb +34 -10
  387. data/spec/rails/controller_extension/controller_runtime_spec.rb +2 -2
  388. data/spec/rails/mongoid_spec.rb +4 -16
  389. data/spec/shared/lib/mrss/docker_runner.rb +8 -0
  390. data/spec/shared/lib/mrss/lite_constraints.rb +10 -2
  391. data/spec/shared/lib/mrss/server_version_registry.rb +16 -23
  392. data/spec/shared/lib/mrss/utils.rb +28 -6
  393. data/spec/shared/share/Dockerfile.erb +36 -40
  394. data/spec/shared/shlib/server.sh +32 -8
  395. data/spec/shared/shlib/set_env.sh +4 -4
  396. data/spec/spec_helper.rb +5 -0
  397. data/spec/support/constraints.rb +24 -0
  398. data/spec/support/immutable_ids.rb +118 -0
  399. data/spec/support/macros.rb +78 -0
  400. data/spec/support/models/artist.rb +0 -1
  401. data/spec/support/models/augmentation.rb +12 -0
  402. data/spec/support/models/band.rb +4 -0
  403. data/spec/support/models/book.rb +1 -0
  404. data/spec/support/models/building.rb +2 -0
  405. data/spec/support/models/catalog.rb +24 -0
  406. data/spec/support/models/circus.rb +3 -0
  407. data/spec/support/models/cover.rb +10 -0
  408. data/spec/support/models/fanatic.rb +8 -0
  409. data/spec/support/models/implant.rb +9 -0
  410. data/spec/support/models/label.rb +2 -0
  411. data/spec/support/models/passport.rb +9 -0
  412. data/spec/support/models/person.rb +2 -0
  413. data/spec/support/models/player.rb +2 -0
  414. data/spec/support/models/powerup.rb +12 -0
  415. data/spec/support/models/product.rb +1 -0
  416. data/spec/support/models/purse.rb +9 -0
  417. data/spec/support/models/registry.rb +1 -0
  418. data/spec/support/models/school.rb +14 -0
  419. data/spec/support/models/shield.rb +18 -0
  420. data/spec/support/models/student.rb +14 -0
  421. data/spec/support/models/weapon.rb +12 -0
  422. data.tar.gz.sig +0 -0
  423. metadata +722 -640
  424. metadata.gz.sig +0 -0
  425. data/lib/mongoid/errors/eager_load.rb +0 -23
  426. data/lib/mongoid/errors/invalid_value.rb +0 -17
  427. data/spec/mongoid/criteria/queryable/extensions/bignum_spec.rb +0 -60
  428. data/spec/mongoid/criteria/queryable/extensions/fixnum_spec.rb +0 -60
  429. data/spec/mongoid/errors/eager_load_spec.rb +0 -31
@@ -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
@@ -1561,24 +2080,6 @@ describe Mongoid::Contextual::Mongo do
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 #last' 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
1749
-
1750
- let(:criteria) do
1751
- Band.all.cache
1752
- end
2221
+ let(:criteria) do
2222
+ Band.all
2223
+ end
1753
2224
 
1754
- context "when requesting all of the documents" do
2225
+ before do
2226
+ context.first(before_limit)
2227
+ end
1755
2228
 
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,14 +2305,15 @@ describe Mongoid::Contextual::Mongo do
1883
2305
  end
1884
2306
  end
1885
2307
 
1886
- 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
1887
2310
 
1888
2311
  let(:context) do
1889
2312
  described_class.new(criteria)
1890
2313
  end
1891
2314
 
1892
2315
  let(:criteria) do
1893
- Band.all.cache
2316
+ Band.all
1894
2317
  end
1895
2318
 
1896
2319
  before do
@@ -1905,8 +2328,10 @@ describe Mongoid::Contextual::Mongo do
1905
2328
  let(:before_limit) { 2 }
1906
2329
  let(:limit) { 1 }
1907
2330
 
1908
- it "gets the correct document" do
1909
- 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
1910
2335
  end
1911
2336
  end
1912
2337
  end
@@ -1996,30 +2421,10 @@ describe Mongoid::Contextual::Mongo do
1996
2421
  context 'when calling #first' do
1997
2422
 
1998
2423
  it 'returns the first document, sorted by _id' do
1999
- pending "MONGOID-5416"
2000
2424
  expect(context.last).to eq(rolling_stones)
2001
2425
  expect(context.first).to eq(depeche_mode)
2002
2426
  end
2003
2427
  end
2004
-
2005
- context 'with option { id_sort: :none }' do
2006
- let(:opts) do
2007
- { id_sort: :none }
2008
- end
2009
-
2010
- it 'doesn\'t apply the sort on _id' do
2011
- expect(context.last(opts)).to eq(depeche_mode)
2012
- end
2013
-
2014
- context 'when calling #first' do
2015
-
2016
- it 'doesn\'t apply the sort on _id' do
2017
- pending "MONGOID-5416"
2018
- expect(context.last(opts)).to eq(rolling_stones)
2019
- expect(context.first(opts)).to eq(depeche_mode)
2020
- end
2021
- end
2022
- end
2023
2428
  end
2024
2429
 
2025
2430
  context 'when the criteria has a sort' do
@@ -2044,25 +2449,6 @@ describe Mongoid::Contextual::Mongo do
2044
2449
  expect(context.first).to eq(rolling_stones)
2045
2450
  end
2046
2451
  end
2047
-
2048
- context 'with option { id_sort: :none }' do
2049
-
2050
- let(:opts) do
2051
- { id_sort: :none }
2052
- end
2053
-
2054
- it 'uses the preexisting sort' do
2055
- expect(context.last(opts)).to eq(depeche_mode)
2056
- end
2057
-
2058
- context 'when calling #first' do
2059
-
2060
- it 'uses the preexisting sort' do
2061
- expect(context.last(opts)).to eq(depeche_mode)
2062
- expect(context.first(opts)).to eq(rolling_stones)
2063
- end
2064
- end
2065
- end
2066
2452
  end
2067
2453
 
2068
2454
  context "when using .sort" do
@@ -2091,28 +2477,17 @@ describe Mongoid::Contextual::Mongo do
2091
2477
  end
2092
2478
  end
2093
2479
 
2094
- context "when the context is cached" do
2480
+ context "when the query cache is enabled" do
2481
+ query_cache_enabled
2095
2482
 
2096
2483
  let(:criteria) do
2097
- Band.where(name: "Depeche Mode").cache
2484
+ Band.where(name: "Depeche Mode")
2098
2485
  end
2099
2486
 
2100
2487
  let(:context) do
2101
2488
  described_class.new(criteria)
2102
2489
  end
2103
2490
 
2104
- context "when the cache is loaded" do
2105
-
2106
- before do
2107
- context.to_a
2108
- end
2109
-
2110
- it "returns the last document without touching the database" do
2111
- expect(context).to receive(:view).never
2112
- expect(context.last).to eq(depeche_mode)
2113
- end
2114
- end
2115
-
2116
2491
  context "when last method was called before" do
2117
2492
 
2118
2493
  before do
@@ -2120,8 +2495,9 @@ describe Mongoid::Contextual::Mongo do
2120
2495
  end
2121
2496
 
2122
2497
  it "returns the last document without touching the database" do
2123
- expect(context).to receive(:view).never
2124
- expect(context.last).to eq(depeche_mode)
2498
+ expect_no_queries do
2499
+ expect(context.last).to eq(depeche_mode)
2500
+ end
2125
2501
  end
2126
2502
  end
2127
2503
  end
@@ -2181,71 +2557,15 @@ describe Mongoid::Contextual::Mongo do
2181
2557
  described_class.new(criteria)
2182
2558
  end
2183
2559
 
2184
- context "when the whole context is loaded" do
2185
-
2186
- before do
2187
- context.to_a
2188
- end
2189
-
2190
- context "when all of the documents are cached" do
2191
-
2192
- let(:criteria) do
2193
- Band.all.cache
2194
- end
2195
-
2196
- context "when requesting all of the documents" do
2197
-
2198
- let(:docs) do
2199
- context.last(3)
2200
- end
2201
-
2202
- it "returns all of the documents without touching the database" do
2203
- expect(context).to receive(:view).never
2204
- expect(docs).to eq([ depeche_mode, new_order, rolling_stones ])
2205
- end
2206
- end
2207
-
2208
- context "when requesting fewer than all of the documents" do
2209
-
2210
- let(:docs) do
2211
- context.last(2)
2212
- end
2213
-
2214
- it "returns all of the documents without touching the database" do
2215
- expect(context).to receive(:view).never
2216
- expect(docs).to eq([ new_order, rolling_stones ])
2217
- end
2218
- end
2219
- end
2220
-
2221
- context "when only one document is cached" do
2222
-
2223
- let(:criteria) do
2224
- Band.where(name: "Depeche Mode").cache
2225
- end
2226
-
2227
- context "when requesting one document" do
2228
-
2229
- let(:docs) do
2230
- context.last(1)
2231
- end
2232
-
2233
- it "returns one document without touching the database" do
2234
- expect(context).to receive(:view).never
2235
- expect(docs).to eq([ depeche_mode ])
2236
- end
2237
- end
2238
- end
2239
- end
2240
-
2241
- context "when the last method was called before" do
2560
+ context "when query cache is enabled" do
2561
+ query_cache_enabled
2242
2562
 
2243
2563
  let(:context) do
2244
2564
  described_class.new(criteria)
2245
2565
  end
2246
2566
 
2247
2567
  let(:criteria) do
2248
- Band.all.cache
2568
+ Band.all
2249
2569
  end
2250
2570
 
2251
2571
  before do
@@ -2262,18 +2582,20 @@ describe Mongoid::Contextual::Mongo do
2262
2582
  context "when getting all of the documents" do
2263
2583
  let(:limit) { 3 }
2264
2584
 
2265
- it "returns all documents without touching the database" do
2266
- expect(context).to receive(:view).never
2267
- 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
2268
2589
  end
2269
2590
  end
2270
2591
 
2271
2592
  context "when getting fewer documents" do
2272
2593
  let(:limit) { 2 }
2273
2594
 
2274
- it "returns the correct documents without touching the database" do
2275
- expect(context).to receive(:view).never
2276
- 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
2277
2599
  end
2278
2600
  end
2279
2601
  end
@@ -2284,9 +2606,10 @@ describe Mongoid::Contextual::Mongo do
2284
2606
  context "when getting the same number of documents" do
2285
2607
  let(:limit) { 2 }
2286
2608
 
2287
- it "returns the correct documents without touching the database" do
2288
- expect(context).to receive(:view).never
2289
- 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
2290
2613
  end
2291
2614
  end
2292
2615
 
@@ -2294,8 +2617,9 @@ describe Mongoid::Contextual::Mongo do
2294
2617
  let(:limit) { 3 }
2295
2618
 
2296
2619
  it "returns the correct documents and touches the database" do
2297
- expect(context).to receive(:view).twice.and_call_original
2298
- 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
2299
2623
  end
2300
2624
  end
2301
2625
  end
@@ -2307,8 +2631,9 @@ describe Mongoid::Contextual::Mongo do
2307
2631
  let(:limit) { 1 }
2308
2632
 
2309
2633
  it "returns the correct documents without touching the database" do
2310
- expect(context).to receive(:view).never
2311
- expect(docs).to eq([ rolling_stones ])
2634
+ expect_no_queries do
2635
+ expect(docs).to eq([ rolling_stones ])
2636
+ end
2312
2637
  end
2313
2638
  end
2314
2639
 
@@ -2316,8 +2641,9 @@ describe Mongoid::Contextual::Mongo do
2316
2641
  let(:limit) { 3 }
2317
2642
 
2318
2643
  it "returns the correct documents and touches the database" do
2319
- expect(context).to receive(:view).twice.and_call_original
2320
- 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
2321
2647
  end
2322
2648
  end
2323
2649
  end
@@ -2325,14 +2651,15 @@ describe Mongoid::Contextual::Mongo do
2325
2651
  end
2326
2652
  end
2327
2653
 
2328
- 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
2329
2656
 
2330
2657
  let(:context) do
2331
2658
  described_class.new(criteria)
2332
2659
  end
2333
2660
 
2334
2661
  let(:criteria) do
2335
- Band.all.cache
2662
+ Band.all
2336
2663
  end
2337
2664
 
2338
2665
  before do
@@ -2348,12 +2675,12 @@ describe Mongoid::Contextual::Mongo do
2348
2675
  let(:limit) { 1 }
2349
2676
 
2350
2677
  it "hits the database" do
2351
- expect(context).to receive(:view).exactly(3).times.and_call_original
2352
- docs
2678
+ expect_query(1) do
2679
+ docs
2680
+ end
2353
2681
  end
2354
2682
 
2355
2683
  it "gets the correct document" do
2356
- pending "MONGOID-5416"
2357
2684
  expect(docs).to eq([ depeche_mode ])
2358
2685
  end
2359
2686
  end
@@ -2406,37 +2733,28 @@ describe Mongoid::Contextual::Mongo do
2406
2733
  described_class.new(criteria)
2407
2734
  end
2408
2735
 
2409
- it "returns the number of documents that match" do
2410
- expect(context.send(method)).to eq(2)
2411
- end
2736
+ context "when broken_view_options is false" do
2737
+ driver_config_override :broken_view_options, false
2412
2738
 
2413
- context "when calling more than once" do
2414
- it "returns the cached value for subsequent calls" do
2415
- expect(context.view).to receive(:count_documents).once.and_return(2)
2416
- 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)
2417
2741
  end
2418
2742
  end
2419
2743
 
2420
- context "when the results have been iterated over" do
2421
-
2422
- before do
2423
- context.entries
2424
- end
2744
+ context "when broken_view_options is true" do
2745
+ driver_config_override :broken_view_options, true
2425
2746
 
2426
- it "returns the cached value for all calls" do
2427
- expect(context.view).to receive(:count_documents).once.and_return(2)
2747
+ it "returns the number of documents that match" do
2428
2748
  expect(context.send(method)).to eq(2)
2429
2749
  end
2750
+ end
2430
2751
 
2431
- context "when the results have been iterated over multiple times" do
2432
-
2433
- before do
2434
- context.entries
2435
- end
2752
+ context "when calling more than once with different limits" do
2753
+ driver_config_override :broken_view_options, false
2436
2754
 
2437
- it "resets the length on each full iteration" do
2438
- expect(context.size).to eq(2)
2439
- 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)
2440
2758
  end
2441
2759
  end
2442
2760
  end
@@ -2455,10 +2773,12 @@ describe Mongoid::Contextual::Mongo do
2455
2773
  expect(context.send(method)).to eq(1)
2456
2774
  end
2457
2775
 
2458
- context "when calling more than once" do
2459
- it "returns the cached value for subsequent calls" do
2460
- expect(context.view).to receive(:count_documents).once.and_return(1)
2461
- 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)
2462
2782
  end
2463
2783
  end
2464
2784
 
@@ -2580,7 +2900,7 @@ describe Mongoid::Contextual::Mongo do
2580
2900
  it "raises an error" do
2581
2901
  expect do
2582
2902
  Person.take!
2583
- end.to raise_error(Mongoid::Errors::DocumentNotFound)
2903
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Person./)
2584
2904
  end
2585
2905
  end
2586
2906
  end
@@ -2602,9 +2922,10 @@ describe Mongoid::Contextual::Mongo do
2602
2922
 
2603
2923
  context "when passed the symbol field name" do
2604
2924
 
2605
- it "performs mapping and warns" do
2606
- expect(Mongoid::Warnings).to receive(:warn_map_field_deprecated)
2607
- 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)
2608
2929
  end
2609
2930
  end
2610
2931
 
@@ -2833,219 +3154,948 @@ describe Mongoid::Contextual::Mongo do
2833
3154
  described_class.new(criteria)
2834
3155
  end
2835
3156
 
2836
- let(:results) do
2837
- context.map_reduce(map, reduce).out(inline: 1)
3157
+ let(:results) do
3158
+ context.map_reduce(map, reduce).out(inline: 1)
3159
+ end
3160
+
3161
+ it "returns the first aggregate result" do
3162
+ expect(results).to include(
3163
+ { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
3164
+ )
3165
+ end
3166
+
3167
+ it "returns the correct number of documents" do
3168
+ expect(results.count).to eq(1)
3169
+ end
3170
+
3171
+ it "contains the entire raw results" do
3172
+ expect(results["results"]).to eq([
3173
+ { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
3174
+ ])
3175
+ end
3176
+ end
3177
+
3178
+ context "when the output is replace" do
3179
+
3180
+ let(:criteria) do
3181
+ Band.limit(1)
3182
+ end
3183
+
3184
+ let(:context) do
3185
+ described_class.new(criteria)
3186
+ end
3187
+
3188
+ let(:results) do
3189
+ context.map_reduce(map, reduce).out(replace: "mr-output")
3190
+ end
3191
+
3192
+ it "returns the correct number of documents" do
3193
+ expect(results.count).to eq(1)
3194
+ end
3195
+
3196
+ it "contains the entire results" do
3197
+ expect(results).to eq([
3198
+ { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
3199
+ ])
3200
+ end
3201
+ end
3202
+
3203
+ context "when the output is reduce" do
3204
+
3205
+ let(:criteria) do
3206
+ Band.limit(1)
3207
+ end
3208
+
3209
+ let(:context) do
3210
+ described_class.new(criteria)
3211
+ end
3212
+
3213
+ let(:results) do
3214
+ context.map_reduce(map, reduce).out(reduce: :mr_output)
3215
+ end
3216
+
3217
+ it "returns the correct number of documents" do
3218
+ expect(results.count).to eq(1)
3219
+ end
3220
+
3221
+ it "contains the entire results" do
3222
+ expect(results).to eq([
3223
+ { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
3224
+ ])
3225
+ end
3226
+ end
3227
+
3228
+ context "when the output is merge" do
3229
+
3230
+ let(:criteria) do
3231
+ Band.limit(1)
3232
+ end
3233
+
3234
+ let(:context) do
3235
+ described_class.new(criteria)
3236
+ end
3237
+
3238
+ let(:results) do
3239
+ context.map_reduce(map, reduce).out(merge: :mr_output)
3240
+ end
3241
+
3242
+ it "returns the correct number of documents" do
3243
+ expect(results.count).to eq(1)
3244
+ end
3245
+
3246
+ it "contains the entire results" do
3247
+ expect(results).to eq([
3248
+ { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
3249
+ ])
3250
+ end
3251
+ end
3252
+
3253
+ context "when the output specifies a different db" do
3254
+ # Limit is not supported in sharded clusters
3255
+ require_topology :single, :replica_set
3256
+
3257
+ let(:criteria) do
3258
+ Band.limit(1)
3259
+ end
3260
+
3261
+ let(:context) do
3262
+ described_class.new(criteria)
3263
+ end
3264
+
3265
+ after do
3266
+ Band.with(database: 'another-db') do |b|
3267
+ b.all.delete
3268
+ end
3269
+ end
3270
+
3271
+ context 'when db is a string' do
3272
+
3273
+ let(:results) do
3274
+ context.map_reduce(map, reduce).out(merge: :mr_output, db: 'another-db')
3275
+ end
3276
+
3277
+ it "returns the correct number of documents" do
3278
+ expect(results.count).to eq(1)
3279
+ end
3280
+
3281
+ it "contains the entire results" do
3282
+ expect(results).to eq([
3283
+ { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
3284
+ ])
3285
+ end
3286
+
3287
+ it 'writes to the specified db' do
3288
+ expect(Band.mongo_client.with(database: 'another-db')[:mr_output].find.count).to eq(1)
3289
+ end
3290
+ end
3291
+
3292
+ context 'when db is a symbol' do
3293
+
3294
+ let(:results) do
3295
+ context.map_reduce(map, reduce).out(merge: :mr_output, 'db' => 'another-db')
3296
+ end
3297
+
3298
+ it "returns the correct number of documents" do
3299
+ expect(results.count).to eq(1)
3300
+ end
3301
+
3302
+ it "contains the entire results" do
3303
+ expect(results).to eq([
3304
+ { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
3305
+ ])
3306
+ end
3307
+
3308
+ it 'writes to the specified db' do
3309
+ expect(Band.mongo_client.with(database: 'another-db')[:mr_output].find.count).to eq(1)
3310
+ end
3311
+ end
3312
+ end
3313
+
3314
+ context "when providing no output" do
3315
+
3316
+ let(:criteria) do
3317
+ Band.limit(1)
3318
+ end
3319
+
3320
+ let(:context) do
3321
+ described_class.new(criteria)
3322
+ end
3323
+
3324
+ let(:results) do
3325
+ context.map_reduce(map, reduce)
3326
+ end
3327
+
3328
+ it "raises an error" do
3329
+ expect {
3330
+ results.entries
3331
+ }.to raise_error(Mongoid::Errors::NoMapReduceOutput)
3332
+ end
3333
+ end
3334
+
3335
+ context "when providing a finalize" do
3336
+
3337
+ let(:criteria) do
3338
+ Band.limit(1)
3339
+ end
3340
+
3341
+ let(:context) do
3342
+ described_class.new(criteria)
3343
+ end
3344
+
3345
+ let(:finalize) do
3346
+ %Q{
3347
+ function(key, value) {
3348
+ value.extra = true;
3349
+ return value;
3350
+ }}
3351
+ end
3352
+
3353
+ let(:results) do
3354
+ context.map_reduce(map, reduce).out(inline: 1).finalize(finalize)
3355
+ end
3356
+
3357
+ it "returns the correct number of documents" do
3358
+ expect(results.count).to eq(1)
3359
+ end
3360
+
3361
+ it "contains the entire results" do
3362
+ expect(results).to eq([
3363
+ { "_id" => "Depeche Mode", "value" => { "likes" => 200, "extra" => true }}
3364
+ ])
3365
+ end
3366
+ end
3367
+ end
3368
+
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
+ end
3750
+
3751
+ context 'when using aliased field names' do
3752
+ before do
3753
+ context.update_all('$set' => { years: 100 })
3754
+ end
3755
+
3756
+ it "updates the first matching document" do
3757
+ expect(depeche_mode.reload.years).to eq(100)
3758
+ end
3759
+
3760
+ it "updates the last matching document" do
3761
+ expect(new_order.reload.years).to eq(100)
3762
+ end
3763
+ end
3764
+
3765
+ context "when the attributes must be mongoized" do
3766
+
3767
+ before do
3768
+ context.update_all("$set" => { member_count: "1" })
3769
+ end
3770
+
3771
+ it "updates the first matching document" do
3772
+ expect(depeche_mode.reload.member_count).to eq(1)
3773
+ end
3774
+
3775
+ it "updates the last matching document" do
3776
+ expect(new_order.reload.member_count).to eq(1)
3777
+ end
3778
+ end
3779
+ end
3780
+
3781
+ context "when a mix are provided" do
3782
+
3783
+ before do
3784
+ context.update_all("$set" => { name: "Smiths" }, likes: 100)
3785
+ end
3786
+
3787
+ it "updates the first matching document's set" do
3788
+ expect(depeche_mode.reload.name).to eq("Smiths")
3789
+ end
3790
+
3791
+ it "updates the first matching document's updates" do
3792
+ expect(depeche_mode.reload.likes).to eq(100)
3793
+ end
3794
+
3795
+ it "updates the last matching document's set" do
3796
+ expect(new_order.reload.name).to eq("Smiths")
3797
+ end
3798
+
3799
+ it "updates the last matching document's updates" do
3800
+ expect(new_order.reload.likes).to eq(100)
3801
+ end
3802
+ end
3803
+ end
3804
+
3805
+ context "when providing no attributes" do
3806
+
3807
+ it "returns false" do
3808
+ expect(context.update_all).to be false
3809
+ end
3810
+ end
3811
+
3812
+ context 'when provided array filters' do
3813
+ min_server_version '3.6'
3814
+
3815
+ before do
3816
+ Band.delete_all
3817
+ b = Band.new(name: 'Depeche Mode')
3818
+ b.labels << Label.new(name: 'Warner')
3819
+ b.labels << Label.new(name: 'Sony')
3820
+ b.labels << Label.new(name: 'Cbs')
3821
+ b.save!
3822
+
3823
+ b = Band.new(name: 'FKA Twigs')
3824
+ b.labels << Label.new(name: 'Warner')
3825
+ b.labels << Label.new(name: 'Cbs')
3826
+ b.save!
3827
+ end
3828
+
3829
+
3830
+ let(:criteria) do
3831
+ Band.all
2838
3832
  end
2839
3833
 
2840
- it "returns the first aggregate result" do
2841
- expect(results).to include(
2842
- { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
2843
- )
3834
+ let!(:update) do
3835
+ context.update_all({ '$set' => { 'labels.$[i].name' => 'Sony' } },
3836
+ array_filters: [{ 'i.name' => 'Cbs' }])
2844
3837
  end
2845
3838
 
2846
- it "returns the correct number of documents" do
2847
- expect(results.count).to eq(1)
3839
+ it 'applies the array filters' do
3840
+ expect(Band.where(name: 'Depeche Mode').first.labels.collect(&:name)).to match_array(['Warner', 'Sony', 'Sony'])
2848
3841
  end
2849
3842
 
2850
- it "contains the entire raw results" do
2851
- expect(results["results"]).to eq([
2852
- { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
2853
- ])
3843
+ it 'updates all documents' do
3844
+ expect(Band.where(name: 'FKA Twigs').first.labels.collect(&:name)).to match_array(['Warner', 'Sony'])
2854
3845
  end
2855
3846
  end
3847
+ end
2856
3848
 
2857
- context "when the output is replace" do
3849
+ describe '#pipeline' do
3850
+
3851
+ context 'when the criteria has a selector' do
3852
+
3853
+ before do
3854
+ Artist.index(name: "text")
3855
+ Artist.create_indexes
3856
+ end
2858
3857
 
2859
3858
  let(:criteria) do
2860
- Band.limit(1)
3859
+ Artist.text_search("New Order")
2861
3860
  end
2862
3861
 
2863
3862
  let(:context) do
2864
3863
  described_class.new(criteria)
2865
3864
  end
2866
3865
 
2867
- let(:results) do
2868
- context.map_reduce(map, reduce).out(replace: "mr-output")
3866
+ let(:pipeline_match) do
3867
+ context.send(:pipeline, :some_field).first['$match']
2869
3868
  end
2870
3869
 
2871
- it "returns the correct number of documents" do
2872
- expect(results.count).to eq(1)
3870
+ it 'creates a pipeline with the selector as one of the $match criteria' do
3871
+ expect(pipeline_match).to include({ '$text' => { '$search' => "New Order" } })
2873
3872
  end
2874
3873
 
2875
- it "contains the entire results" do
2876
- expect(results).to eq([
2877
- { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
2878
- ])
3874
+ it 'creates a pipeline with the $exists operator as one of the $match criteria' do
3875
+ expect(pipeline_match).to include({ 'some_field' => { '$exists' => true } })
2879
3876
  end
2880
3877
  end
3878
+ end
2881
3879
 
2882
- context "when the output is reduce" do
3880
+ describe "#first!" do
2883
3881
 
2884
- let(:criteria) do
2885
- Band.limit(1)
2886
- end
3882
+ let!(:depeche_mode) do
3883
+ Band.create!(name: "Depeche Mode")
3884
+ end
2887
3885
 
2888
- let(:context) do
2889
- described_class.new(criteria)
2890
- end
3886
+ let!(:new_order) do
3887
+ Band.create!(name: "New Order")
3888
+ end
2891
3889
 
2892
- let(:results) do
2893
- context.map_reduce(map, reduce).out(reduce: :mr_output)
2894
- end
3890
+ let!(:rolling_stones) do
3891
+ Band.create!(name: "The Rolling Stones")
3892
+ end
2895
3893
 
2896
- it "returns the correct number of documents" do
2897
- expect(results.count).to eq(1)
3894
+ let!(:death_cab) do
3895
+ Band.create!(name: "Death Cab For Cutie")
3896
+ end
3897
+
3898
+ let(:context) do
3899
+ described_class.new(criteria)
3900
+ end
3901
+
3902
+ context "when there's no sort" do
3903
+ let(:criteria) do
3904
+ Band.all
2898
3905
  end
2899
3906
 
2900
- it "contains the entire results" do
2901
- expect(results).to eq([
2902
- { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
2903
- ])
3907
+ it "gets the first document" do
3908
+ expect(context.first!).to eq(depeche_mode)
2904
3909
  end
2905
3910
  end
2906
3911
 
2907
- context "when the output is merge" do
2908
-
3912
+ context "when there's a custom sort" do
2909
3913
  let(:criteria) do
2910
- Band.limit(1)
3914
+ Band.all
2911
3915
  end
2912
3916
 
2913
- let(:context) do
2914
- described_class.new(criteria)
3917
+ it "gets the first document" do
3918
+ expect(context.sort(name: 1).first!).to eq(death_cab)
2915
3919
  end
3920
+ end
2916
3921
 
2917
- let(:results) do
2918
- context.map_reduce(map, reduce).out(merge: :mr_output)
3922
+ context "when there are no documents" do
3923
+ let(:criteria) do
3924
+ Band.where(name: "bogus")
2919
3925
  end
2920
3926
 
2921
- it "returns the correct number of documents" do
2922
- expect(results.count).to eq(1)
3927
+ it "raises an error" do
3928
+ expect do
3929
+ context.first!
3930
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
2923
3931
  end
3932
+ end
3933
+ end
2924
3934
 
2925
- it "contains the entire results" do
2926
- expect(results).to eq([
2927
- { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
2928
- ])
2929
- end
3935
+ describe "#last!" do
3936
+
3937
+ let!(:depeche_mode) do
3938
+ Band.create!(name: "Depeche Mode")
2930
3939
  end
2931
3940
 
2932
- context "when the output specifies a different db" do
2933
- # Limit is not supported in sharded clusters
2934
- require_topology :single, :replica_set
3941
+ let!(:new_order) do
3942
+ Band.create!(name: "New Order")
3943
+ end
3944
+
3945
+ let!(:rolling_stones) do
3946
+ Band.create!(name: "The Rolling Stones")
3947
+ end
3948
+
3949
+ let!(:death_cab) do
3950
+ Band.create!(name: "Death Cab For Cutie")
3951
+ end
2935
3952
 
3953
+ let(:context) do
3954
+ described_class.new(criteria)
3955
+ end
3956
+
3957
+ context "when there's no sort" do
2936
3958
  let(:criteria) do
2937
- Band.limit(1)
3959
+ Band.all
2938
3960
  end
2939
3961
 
2940
- let(:context) do
2941
- described_class.new(criteria)
3962
+ it "gets the last document" do
3963
+ expect(context.last!).to eq(death_cab)
2942
3964
  end
3965
+ end
2943
3966
 
2944
- after do
2945
- Band.with(database: 'another-db') do |b|
2946
- b.all.delete
2947
- end
3967
+ context "when there's a custom sort" do
3968
+ let(:criteria) do
3969
+ Band.all
2948
3970
  end
2949
3971
 
2950
- context 'when db is a string' do
3972
+ it "gets the last document" do
3973
+ expect(context.sort(name: 1).last!).to eq(rolling_stones)
3974
+ end
3975
+ end
2951
3976
 
2952
- let(:results) do
2953
- context.map_reduce(map, reduce).out(merge: :mr_output, db: 'another-db')
2954
- end
3977
+ context "when there are no documents" do
3978
+ let(:criteria) do
3979
+ Band.where(name: "bogus")
3980
+ end
2955
3981
 
2956
- it "returns the correct number of documents" do
2957
- expect(results.count).to eq(1)
2958
- end
3982
+ it "raises an error" do
3983
+ expect do
3984
+ context.last!
3985
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
3986
+ end
3987
+ end
3988
+ end
2959
3989
 
2960
- it "contains the entire results" do
2961
- expect(results).to eq([
2962
- { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
2963
- ])
2964
- end
3990
+ describe "#second" do
2965
3991
 
2966
- it 'writes to the specified db' do
2967
- expect(Band.mongo_client.with(database: 'another-db')[:mr_output].find.count).to eq(1)
2968
- end
2969
- end
3992
+ let!(:depeche_mode) do
3993
+ Band.create!(name: "Depeche Mode")
3994
+ end
2970
3995
 
2971
- context 'when db is a symbol' do
3996
+ let!(:new_order) do
3997
+ Band.create!(name: "New Order")
3998
+ end
2972
3999
 
2973
- let(:results) do
2974
- context.map_reduce(map, reduce).out(merge: :mr_output, 'db' => 'another-db')
2975
- end
4000
+ let!(:rolling_stones) do
4001
+ Band.create!(name: "The Rolling Stones")
4002
+ end
2976
4003
 
2977
- it "returns the correct number of documents" do
2978
- expect(results.count).to eq(1)
2979
- end
4004
+ let!(:death_cab) do
4005
+ Band.create!(name: "Death Cab For Cutie")
4006
+ end
2980
4007
 
2981
- it "contains the entire results" do
2982
- expect(results).to eq([
2983
- { "_id" => "Depeche Mode", "value" => { "likes" => 200 }}
2984
- ])
2985
- end
4008
+ let(:context) do
4009
+ described_class.new(criteria)
4010
+ end
2986
4011
 
2987
- it 'writes to the specified db' do
2988
- expect(Band.mongo_client.with(database: 'another-db')[:mr_output].find.count).to eq(1)
2989
- end
4012
+ context "when there's no sort" do
4013
+ let(:criteria) do
4014
+ Band.all
2990
4015
  end
2991
- end
2992
4016
 
2993
- context "when providing no output" do
4017
+ it "gets the second document" do
4018
+ expect(context.second).to eq(new_order)
4019
+ end
4020
+ end
2994
4021
 
4022
+ context "when there's a custom sort" do
2995
4023
  let(:criteria) do
2996
- Band.limit(1)
4024
+ Band.all
2997
4025
  end
2998
4026
 
2999
- let(:context) do
3000
- described_class.new(criteria)
4027
+ it "gets the second document" do
4028
+ expect(context.sort(name: 1).second).to eq(depeche_mode)
3001
4029
  end
4030
+ end
3002
4031
 
3003
- let(:results) do
3004
- context.map_reduce(map, reduce)
4032
+ context "when there are no documents" do
4033
+ let(:criteria) do
4034
+ Band.where(name: "bogus")
3005
4035
  end
3006
4036
 
3007
- it "raises an error" do
3008
- expect {
3009
- results.entries
3010
- }.to raise_error(Mongoid::Errors::NoMapReduceOutput)
4037
+ it "returns nil" do
4038
+ expect(context.second).to be_nil
3011
4039
  end
3012
4040
  end
4041
+ end
3013
4042
 
3014
- context "when providing a finalize" do
4043
+ describe "#second!" do
4044
+
4045
+ let!(:depeche_mode) do
4046
+ Band.create!(name: "Depeche Mode")
4047
+ end
4048
+
4049
+ let!(:new_order) do
4050
+ Band.create!(name: "New Order")
4051
+ end
4052
+
4053
+ let!(:rolling_stones) do
4054
+ Band.create!(name: "The Rolling Stones")
4055
+ end
4056
+
4057
+ let!(:death_cab) do
4058
+ Band.create!(name: "Death Cab For Cutie")
4059
+ end
4060
+
4061
+ let(:context) do
4062
+ described_class.new(criteria)
4063
+ end
3015
4064
 
4065
+ context "when there's no sort" do
3016
4066
  let(:criteria) do
3017
- Band.limit(1)
4067
+ Band.all
3018
4068
  end
3019
4069
 
3020
- let(:context) do
3021
- described_class.new(criteria)
4070
+ it "gets the second document" do
4071
+ expect(context.second!).to eq(new_order)
3022
4072
  end
4073
+ end
3023
4074
 
3024
- let(:finalize) do
3025
- %Q{
3026
- function(key, value) {
3027
- value.extra = true;
3028
- return value;
3029
- }}
4075
+ context "when there's a custom sort" do
4076
+ let(:criteria) do
4077
+ Band.all
3030
4078
  end
3031
4079
 
3032
- let(:results) do
3033
- context.map_reduce(map, reduce).out(inline: 1).finalize(finalize)
4080
+ it "gets the second document" do
4081
+ expect(context.sort(name: 1).second!).to eq(depeche_mode)
3034
4082
  end
4083
+ end
3035
4084
 
3036
- it "returns the correct number of documents" do
3037
- expect(results.count).to eq(1)
4085
+ context "when there are no documents" do
4086
+ let(:criteria) do
4087
+ Band.where(name: "bogus")
3038
4088
  end
3039
4089
 
3040
- it "contains the entire results" do
3041
- expect(results).to eq([
3042
- { "_id" => "Depeche Mode", "value" => { "likes" => 200, "extra" => true }}
3043
- ])
4090
+ it "raises an error" do
4091
+ expect do
4092
+ context.second!
4093
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
3044
4094
  end
3045
4095
  end
3046
4096
  end
3047
4097
 
3048
- describe "#skip" do
4098
+ describe "#third" do
3049
4099
 
3050
4100
  let!(:depeche_mode) do
3051
4101
  Band.create!(name: "Depeche Mode")
@@ -3055,20 +4105,50 @@ describe Mongoid::Contextual::Mongo do
3055
4105
  Band.create!(name: "New Order")
3056
4106
  end
3057
4107
 
3058
- let(:criteria) do
3059
- Band.all
4108
+ let!(:rolling_stones) do
4109
+ Band.create!(name: "The Rolling Stones")
4110
+ end
4111
+
4112
+ let!(:death_cab) do
4113
+ Band.create!(name: "Death Cab For Cutie")
3060
4114
  end
3061
4115
 
3062
4116
  let(:context) do
3063
4117
  described_class.new(criteria)
3064
4118
  end
3065
4119
 
3066
- it "limits the results" do
3067
- expect(context.skip(1).entries).to eq([ new_order ])
4120
+ context "when there's no sort" do
4121
+ let(:criteria) do
4122
+ Band.all
4123
+ end
4124
+
4125
+ it "gets the third document" do
4126
+ expect(context.third).to eq(rolling_stones)
4127
+ end
4128
+ end
4129
+
4130
+ context "when there's a custom sort" do
4131
+ let(:criteria) do
4132
+ Band.all
4133
+ end
4134
+
4135
+ it "gets the third document" do
4136
+ expect(context.sort(name: 1).third).to eq(new_order)
4137
+ end
4138
+ end
4139
+
4140
+ context "when there are no documents" do
4141
+ let(:criteria) do
4142
+ Band.where(name: "bogus")
4143
+ end
4144
+
4145
+ it "returns nil" do
4146
+ expect(context.third).to be_nil
4147
+ end
3068
4148
  end
3069
4149
  end
3070
4150
 
3071
- describe "#sort" do
4151
+ describe "#third!" do
3072
4152
 
3073
4153
  let!(:depeche_mode) do
3074
4154
  Band.create!(name: "Depeche Mode")
@@ -3078,40 +4158,52 @@ describe Mongoid::Contextual::Mongo do
3078
4158
  Band.create!(name: "New Order")
3079
4159
  end
3080
4160
 
3081
- let(:criteria) do
3082
- Band.all
4161
+ let!(:rolling_stones) do
4162
+ Band.create!(name: "The Rolling Stones")
4163
+ end
4164
+
4165
+ let!(:death_cab) do
4166
+ Band.create!(name: "Death Cab For Cutie")
3083
4167
  end
3084
4168
 
3085
4169
  let(:context) do
3086
4170
  described_class.new(criteria)
3087
4171
  end
3088
4172
 
3089
- context "when providing a spec" do
3090
-
3091
- it "sorts the results" do
3092
- expect(context.sort(name: -1).entries).to eq([ new_order, depeche_mode ])
4173
+ context "when there's no sort" do
4174
+ let(:criteria) do
4175
+ Band.all
3093
4176
  end
3094
4177
 
3095
- it "returns the context" do
3096
- expect(context.sort(name: 1)).to eq(context)
4178
+ it "gets the third document" do
4179
+ expect(context.third!).to eq(rolling_stones)
3097
4180
  end
3098
4181
  end
3099
4182
 
3100
- context "when providing a block" do
4183
+ context "when there's a custom sort" do
4184
+ let(:criteria) do
4185
+ Band.all
4186
+ end
3101
4187
 
3102
- let(:sorted) do
3103
- context.sort do |a, b|
3104
- b.name <=> a.name
3105
- end
4188
+ it "gets the third document" do
4189
+ expect(context.sort(name: 1).third!).to eq(new_order)
3106
4190
  end
4191
+ end
3107
4192
 
3108
- it "sorts the results in memory" do
3109
- expect(sorted).to eq([ new_order, depeche_mode ])
4193
+ context "when there are no documents" do
4194
+ let(:criteria) do
4195
+ Band.where(name: "bogus")
4196
+ end
4197
+
4198
+ it "raises an error" do
4199
+ expect do
4200
+ context.third!
4201
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
3110
4202
  end
3111
4203
  end
3112
4204
  end
3113
4205
 
3114
- describe "#update" do
4206
+ describe "#fourth" do
3115
4207
 
3116
4208
  let!(:depeche_mode) do
3117
4209
  Band.create!(name: "Depeche Mode")
@@ -3121,388 +4213,498 @@ describe Mongoid::Contextual::Mongo do
3121
4213
  Band.create!(name: "New Order")
3122
4214
  end
3123
4215
 
3124
- let(:criteria) do
3125
- Band.all
4216
+ let!(:rolling_stones) do
4217
+ Band.create!(name: "The Rolling Stones")
4218
+ end
4219
+
4220
+ let!(:death_cab) do
4221
+ Band.create!(name: "Death Cab For Cutie")
3126
4222
  end
3127
4223
 
3128
4224
  let(:context) do
3129
4225
  described_class.new(criteria)
3130
4226
  end
3131
4227
 
3132
- context "when adding an element to a HABTM set" do
3133
-
3134
- let(:person) do
3135
- Person.create!
4228
+ context "when there's no sort" do
4229
+ let(:criteria) do
4230
+ Band.all
3136
4231
  end
3137
4232
 
3138
- let(:preference) do
3139
- Preference.create!
4233
+ it "gets the fourth document" do
4234
+ expect(context.fourth).to eq(death_cab)
3140
4235
  end
4236
+ end
3141
4237
 
3142
- before do
3143
- Person.where(id: person.id).
3144
- update("$addToSet" => { preference_ids: preference.id })
4238
+ context "when there's a custom sort" do
4239
+ let(:criteria) do
4240
+ Band.all
3145
4241
  end
3146
4242
 
3147
- it "adds a single element to the array" do
3148
- expect(person.reload.preference_ids).to eq([ preference.id ])
4243
+ it "gets the fourth document" do
4244
+ expect(context.sort(name: 1).fourth).to eq(rolling_stones)
3149
4245
  end
3150
4246
  end
3151
4247
 
3152
- context "when providing attributes" do
4248
+ context "when there are no documents" do
4249
+ let(:criteria) do
4250
+ Band.where(name: "bogus")
4251
+ end
3153
4252
 
3154
- context "when the attributes are of the correct type" do
4253
+ it "returns nil" do
4254
+ expect(context.fourth).to be_nil
4255
+ end
4256
+ end
4257
+ end
3155
4258
 
3156
- before do
3157
- context.update(name: "Smiths")
3158
- end
4259
+ describe "#fourth!" do
3159
4260
 
3160
- it "updates only the first matching document" do
3161
- expect(depeche_mode.reload.name).to eq("Smiths")
3162
- end
4261
+ let!(:depeche_mode) do
4262
+ Band.create!(name: "Depeche Mode")
4263
+ end
3163
4264
 
3164
- it "does not update the last matching document" do
3165
- expect(new_order.reload.name).to eq("New Order")
3166
- end
3167
- end
4265
+ let!(:new_order) do
4266
+ Band.create!(name: "New Order")
4267
+ end
3168
4268
 
3169
- context "when the attributes must be mongoized" do
4269
+ let!(:rolling_stones) do
4270
+ Band.create!(name: "The Rolling Stones")
4271
+ end
3170
4272
 
3171
- context "when coercing a string to integer" do
4273
+ let!(:death_cab) do
4274
+ Band.create!(name: "Death Cab For Cutie")
4275
+ end
3172
4276
 
3173
- before do
3174
- context.update(member_count: "1")
3175
- end
4277
+ let(:context) do
4278
+ described_class.new(criteria)
4279
+ end
3176
4280
 
3177
- it "updates the first matching document" do
3178
- expect(depeche_mode.reload.member_count).to eq(1)
3179
- end
4281
+ context "when there's no sort" do
4282
+ let(:criteria) do
4283
+ Band.all
4284
+ end
3180
4285
 
3181
- it "does not update the last matching document" do
3182
- expect(new_order.reload.member_count).to be_nil
3183
- end
3184
- end
4286
+ it "gets the fourth document" do
4287
+ expect(context.fourth!).to eq(death_cab)
4288
+ end
4289
+ end
3185
4290
 
3186
- context "when coercing a string to date" do
4291
+ context "when there's a custom sort" do
4292
+ let(:criteria) do
4293
+ Band.all
4294
+ end
3187
4295
 
3188
- before do
3189
- context.update(founded: "1979/1/1")
3190
- end
4296
+ it "gets the fourth document" do
4297
+ expect(context.sort(name: 1).fourth!).to eq(rolling_stones)
4298
+ end
4299
+ end
3191
4300
 
3192
- it "updates the first matching document" do
3193
- expect(depeche_mode.reload.founded).to eq(Date.new(1979, 1, 1))
3194
- end
4301
+ context "when there are no documents" do
4302
+ let(:criteria) do
4303
+ Band.where(name: "bogus")
4304
+ end
3195
4305
 
3196
- it "does not update the last matching document" do
3197
- expect(new_order.reload.founded).to be_nil
3198
- end
3199
- end
4306
+ it "raises an error" do
4307
+ expect do
4308
+ context.fourth!
4309
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
3200
4310
  end
3201
4311
  end
4312
+ end
3202
4313
 
3203
- context "when providing atomic operations" do
4314
+ describe "#fifth" do
3204
4315
 
3205
- context "when only atomic operations are provided" do
4316
+ let!(:depeche_mode) do
4317
+ Band.create!(name: "Depeche Mode")
4318
+ end
3206
4319
 
3207
- context "when the attributes are in the correct type" do
4320
+ let!(:new_order) do
4321
+ Band.create!(name: "New Order")
4322
+ end
3208
4323
 
3209
- before do
3210
- context.update("$set" => { name: "Smiths" })
3211
- end
4324
+ let!(:rolling_stones) do
4325
+ Band.create!(name: "The Rolling Stones")
4326
+ end
3212
4327
 
3213
- it "updates the first matching document" do
3214
- expect(depeche_mode.reload.name).to eq("Smiths")
3215
- end
4328
+ let!(:death_cab) do
4329
+ Band.create!(name: "Death Cab For Cutie")
4330
+ end
3216
4331
 
3217
- it "does not update the last matching document" do
3218
- expect(new_order.reload.name).to eq("New Order")
3219
- end
3220
- end
4332
+ let!(:guns_and_roses) do
4333
+ Band.create!(name: "Guns and Roses")
4334
+ end
3221
4335
 
3222
- context "when the attributes must be mongoized" do
4336
+ let(:context) do
4337
+ described_class.new(criteria)
4338
+ end
3223
4339
 
3224
- before do
3225
- context.update("$set" => { member_count: "1" })
3226
- end
4340
+ context "when there's no sort" do
4341
+ let(:criteria) do
4342
+ Band.all
4343
+ end
3227
4344
 
3228
- it "updates the first matching document" do
3229
- expect(depeche_mode.reload.member_count).to eq(1)
3230
- end
4345
+ it "gets the fifth document" do
4346
+ expect(context.fifth).to eq(guns_and_roses)
4347
+ end
4348
+ end
3231
4349
 
3232
- it "does not update the last matching document" do
3233
- expect(new_order.reload.member_count).to be_nil
3234
- end
3235
- end
4350
+ context "when there's a custom sort" do
4351
+ let(:criteria) do
4352
+ Band.all
3236
4353
  end
3237
4354
 
3238
- context "when a mix are provided" do
4355
+ it "gets the fifth document" do
4356
+ expect(context.sort(name: 1).fifth).to eq(rolling_stones)
4357
+ end
4358
+ end
3239
4359
 
3240
- before do
3241
- context.update("$set" => { name: "Smiths" }, likes: 100)
3242
- end
4360
+ context "when there are no documents" do
4361
+ let(:criteria) do
4362
+ Band.where(name: "bogus")
4363
+ end
3243
4364
 
3244
- it "updates the first matching document's set" do
3245
- expect(depeche_mode.reload.name).to eq("Smiths")
3246
- end
4365
+ it "returns nil" do
4366
+ expect(context.fifth).to be_nil
4367
+ end
4368
+ end
4369
+ end
3247
4370
 
3248
- it "updates the first matching document's updates" do
3249
- expect(depeche_mode.reload.likes).to eq(100)
3250
- end
4371
+ describe "#fifth!" do
3251
4372
 
3252
- it "does not update the last matching document's set" do
3253
- expect(new_order.reload.name).to eq("New Order")
3254
- end
4373
+ let!(:depeche_mode) do
4374
+ Band.create!(name: "Depeche Mode")
4375
+ end
3255
4376
 
3256
- it "does not update the last matching document's updates" do
3257
- expect(new_order.reload.likes).to be_nil
3258
- end
3259
- end
4377
+ let!(:new_order) do
4378
+ Band.create!(name: "New Order")
3260
4379
  end
3261
4380
 
3262
- context "when providing no attributes" do
4381
+ let!(:rolling_stones) do
4382
+ Band.create!(name: "The Rolling Stones")
4383
+ end
3263
4384
 
3264
- it "returns false" do
3265
- expect(context.update).to be false
3266
- end
4385
+ let!(:death_cab) do
4386
+ Band.create!(name: "Death Cab For Cutie")
3267
4387
  end
3268
4388
 
3269
- context 'when provided array filters' do
3270
- min_server_version '3.6'
4389
+ let!(:guns_and_roses) do
4390
+ Band.create!(name: "Guns and Roses")
4391
+ end
3271
4392
 
3272
- before do
3273
- Band.delete_all
3274
- b = Band.new(name: 'Depeche Mode')
3275
- b.labels << Label.new(name: 'Warner')
3276
- b.labels << Label.new(name: 'Sony')
3277
- b.labels << Label.new(name: 'Cbs')
3278
- b.save!
4393
+ let(:context) do
4394
+ described_class.new(criteria)
4395
+ end
3279
4396
 
3280
- b = Band.new(name: 'FKA Twigs')
3281
- b.labels << Label.new(name: 'Warner')
3282
- b.labels << Label.new(name: 'Cbs')
3283
- b.save!
4397
+ context "when there's no sort" do
4398
+ let(:criteria) do
4399
+ Band.all
3284
4400
  end
3285
4401
 
4402
+ it "gets the fifth document" do
4403
+ expect(context.fifth!).to eq(guns_and_roses)
4404
+ end
4405
+ end
3286
4406
 
4407
+ context "when there's a custom sort" do
3287
4408
  let(:criteria) do
3288
- Band.where(name: 'Depeche Mode')
4409
+ Band.all
3289
4410
  end
3290
4411
 
3291
- let!(:update) do
3292
- context.update({ '$set' => { 'labels.$[i].name' => 'Sony' } },
3293
- array_filters: [{ 'i.name' => 'Cbs' }])
4412
+ it "gets the fifth document" do
4413
+ expect(context.sort(name: 1).fifth!).to eq(rolling_stones)
3294
4414
  end
4415
+ end
3295
4416
 
3296
- it 'applies the array filters' do
3297
- expect(Band.where(name: 'Depeche Mode').first.labels.collect(&:name)).to match_array(['Warner', 'Sony', 'Sony'])
4417
+ context "when there are no documents" do
4418
+ let(:criteria) do
4419
+ Band.where(name: "bogus")
3298
4420
  end
3299
4421
 
3300
- it 'does not affect other documents' do
3301
- expect(Band.where(name: 'FKA Twigs').first.labels.collect(&:name)).to match_array(['Warner', 'Cbs'])
4422
+ it "raises an error" do
4423
+ expect do
4424
+ context.fifth!
4425
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
3302
4426
  end
3303
4427
  end
3304
4428
  end
3305
4429
 
3306
- describe "#update_all" do
4430
+ describe "#second_to_last" do
3307
4431
 
3308
4432
  let!(:depeche_mode) do
3309
- Band.create!(name: "Depeche Mode", origin: "Essex")
4433
+ Band.create!(name: "Depeche Mode")
3310
4434
  end
3311
4435
 
3312
4436
  let!(:new_order) do
3313
4437
  Band.create!(name: "New Order")
3314
4438
  end
3315
4439
 
3316
- let(:criteria) do
3317
- Band.all
4440
+ let!(:rolling_stones) do
4441
+ Band.create!(name: "The Rolling Stones")
4442
+ end
4443
+
4444
+ let!(:death_cab) do
4445
+ Band.create!(name: "Death Cab For Cutie")
3318
4446
  end
3319
4447
 
3320
4448
  let(:context) do
3321
4449
  described_class.new(criteria)
3322
4450
  end
3323
4451
 
3324
- context "when providing attributes" do
4452
+ context "when there's no sort" do
4453
+ let(:criteria) do
4454
+ Band.all
4455
+ end
3325
4456
 
3326
- context "when the attributes are of the correct type" do
4457
+ it "gets the second_to_last document" do
4458
+ expect(context.second_to_last).to eq(rolling_stones)
4459
+ end
4460
+ end
3327
4461
 
3328
- before do
3329
- context.update_all(name: "Smiths")
3330
- end
4462
+ context "when there's a custom sort" do
4463
+ let(:criteria) do
4464
+ Band.all
4465
+ end
3331
4466
 
3332
- it "updates the first matching document" do
3333
- expect(depeche_mode.reload.name).to eq("Smiths")
3334
- end
4467
+ it "gets the second_to_last document" do
4468
+ expect(context.sort(name: 1).second_to_last).to eq(new_order)
4469
+ end
4470
+ end
3335
4471
 
3336
- it "does not clear out other attributes" do
3337
- expect(depeche_mode.reload.origin).to eq("Essex")
3338
- end
4472
+ context "when there are no documents" do
4473
+ let(:criteria) do
4474
+ Band.where(name: "bogus")
4475
+ end
3339
4476
 
3340
- it "updates the last matching document" do
3341
- expect(new_order.reload.name).to eq("Smiths")
3342
- end
4477
+ it "returns nil" do
4478
+ expect(context.second_to_last).to be_nil
3343
4479
  end
4480
+ end
4481
+ end
3344
4482
 
3345
- context "when the attributes must be mongoized" do
4483
+ describe "#second_to_last!" do
3346
4484
 
3347
- before do
3348
- context.update_all(member_count: "1")
3349
- end
4485
+ let!(:depeche_mode) do
4486
+ Band.create!(name: "Depeche Mode")
4487
+ end
3350
4488
 
3351
- it "updates the first matching document" do
3352
- expect(depeche_mode.reload.member_count).to eq(1)
3353
- end
4489
+ let!(:new_order) do
4490
+ Band.create!(name: "New Order")
4491
+ end
3354
4492
 
3355
- it "updates the last matching document" do
3356
- expect(new_order.reload.member_count).to eq(1)
3357
- end
3358
- end
4493
+ let!(:rolling_stones) do
4494
+ Band.create!(name: "The Rolling Stones")
4495
+ end
3359
4496
 
3360
- context "when using aliased field names" do
4497
+ let!(:death_cab) do
4498
+ Band.create!(name: "Death Cab For Cutie")
4499
+ end
3361
4500
 
3362
- before do
3363
- context.update_all(years: 100)
3364
- end
4501
+ let(:context) do
4502
+ described_class.new(criteria)
4503
+ end
3365
4504
 
3366
- it "updates the first matching document" do
3367
- expect(depeche_mode.reload.years).to eq(100)
3368
- end
4505
+ context "when there's no sort" do
4506
+ let(:criteria) do
4507
+ Band.all
4508
+ end
3369
4509
 
3370
- it "updates the last matching document" do
3371
- expect(new_order.reload.years).to eq(100)
3372
- end
4510
+ it "gets the second_to_last document" do
4511
+ expect(context.second_to_last!).to eq(rolling_stones)
3373
4512
  end
3374
4513
  end
3375
4514
 
3376
- context "when providing atomic operations" do
4515
+ context "when there's a custom sort" do
4516
+ let(:criteria) do
4517
+ Band.all
4518
+ end
3377
4519
 
3378
- context "when only atomic operations are provided" do
4520
+ it "gets the second_to_last document" do
4521
+ expect(context.sort(name: 1).second_to_last!).to eq(new_order)
4522
+ end
4523
+ end
3379
4524
 
3380
- context "when the attributes are in the correct type" do
4525
+ context "when there are no documents" do
4526
+ let(:criteria) do
4527
+ Band.where(name: "bogus")
4528
+ end
3381
4529
 
3382
- before do
3383
- context.update_all("$set" => { name: "Smiths" })
3384
- end
4530
+ it "raises an error" do
4531
+ expect do
4532
+ context.second_to_last!
4533
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
4534
+ end
4535
+ end
4536
+ end
3385
4537
 
3386
- it "updates the first matching document" do
3387
- expect(depeche_mode.reload.name).to eq("Smiths")
3388
- end
4538
+ describe "#third_to_last" do
3389
4539
 
3390
- it "updates the last matching document" do
3391
- expect(new_order.reload.name).to eq("Smiths")
3392
- end
3393
- end
4540
+ let!(:depeche_mode) do
4541
+ Band.create!(name: "Depeche Mode")
4542
+ end
3394
4543
 
3395
- context "when the attributes must be mongoized" do
4544
+ let!(:new_order) do
4545
+ Band.create!(name: "New Order")
4546
+ end
3396
4547
 
3397
- before do
3398
- context.update_all("$set" => { member_count: "1" })
3399
- end
4548
+ let!(:rolling_stones) do
4549
+ Band.create!(name: "The Rolling Stones")
4550
+ end
3400
4551
 
3401
- it "updates the first matching document" do
3402
- expect(depeche_mode.reload.member_count).to eq(1)
3403
- end
4552
+ let!(:death_cab) do
4553
+ Band.create!(name: "Death Cab For Cutie")
4554
+ end
3404
4555
 
3405
- it "updates the last matching document" do
3406
- expect(new_order.reload.member_count).to eq(1)
3407
- end
3408
- end
3409
- end
4556
+ let(:context) do
4557
+ described_class.new(criteria)
4558
+ end
3410
4559
 
3411
- context "when a mix are provided" do
4560
+ context "when there's no sort" do
4561
+ let(:criteria) do
4562
+ Band.all
4563
+ end
3412
4564
 
3413
- before do
3414
- context.update_all("$set" => { name: "Smiths" }, likes: 100)
3415
- end
4565
+ it "gets the third_to_last document" do
4566
+ expect(context.third_to_last).to eq(new_order)
4567
+ end
4568
+ end
3416
4569
 
3417
- it "updates the first matching document's set" do
3418
- expect(depeche_mode.reload.name).to eq("Smiths")
3419
- end
4570
+ context "when there's a custom sort" do
4571
+ let(:criteria) do
4572
+ Band.all
4573
+ end
3420
4574
 
3421
- it "updates the first matching document's updates" do
3422
- expect(depeche_mode.reload.likes).to eq(100)
3423
- end
4575
+ it "gets the third_to_last document" do
4576
+ expect(context.sort(name: 1).third_to_last).to eq(depeche_mode)
4577
+ end
4578
+ end
3424
4579
 
3425
- it "updates the last matching document's set" do
3426
- expect(new_order.reload.name).to eq("Smiths")
3427
- end
4580
+ context "when there are no documents" do
4581
+ let(:criteria) do
4582
+ Band.where(name: "bogus")
4583
+ end
3428
4584
 
3429
- it "updates the last matching document's updates" do
3430
- expect(new_order.reload.likes).to eq(100)
3431
- end
4585
+ it "returns nil" do
4586
+ expect(context.third_to_last).to be_nil
3432
4587
  end
3433
4588
  end
4589
+ end
3434
4590
 
3435
- context "when providing no attributes" do
4591
+ describe "#third_to_last!" do
3436
4592
 
3437
- it "returns false" do
3438
- expect(context.update_all).to be false
3439
- end
4593
+ let!(:depeche_mode) do
4594
+ Band.create!(name: "Depeche Mode")
3440
4595
  end
3441
4596
 
3442
- context 'when provided array filters' do
3443
- min_server_version '3.6'
4597
+ let!(:new_order) do
4598
+ Band.create!(name: "New Order")
4599
+ end
3444
4600
 
3445
- before do
3446
- Band.delete_all
3447
- b = Band.new(name: 'Depeche Mode')
3448
- b.labels << Label.new(name: 'Warner')
3449
- b.labels << Label.new(name: 'Sony')
3450
- b.labels << Label.new(name: 'Cbs')
3451
- b.save!
4601
+ let!(:rolling_stones) do
4602
+ Band.create!(name: "The Rolling Stones")
4603
+ end
3452
4604
 
3453
- b = Band.new(name: 'FKA Twigs')
3454
- b.labels << Label.new(name: 'Warner')
3455
- b.labels << Label.new(name: 'Cbs')
3456
- b.save!
4605
+ let!(:death_cab) do
4606
+ Band.create!(name: "Death Cab For Cutie")
4607
+ end
4608
+
4609
+ let(:context) do
4610
+ described_class.new(criteria)
4611
+ end
4612
+
4613
+ context "when there's no sort" do
4614
+ let(:criteria) do
4615
+ Band.all
3457
4616
  end
3458
4617
 
4618
+ it "gets the third_to_last document" do
4619
+ expect(context.third_to_last!).to eq(new_order)
4620
+ end
4621
+ end
3459
4622
 
4623
+ context "when there's a custom sort" do
3460
4624
  let(:criteria) do
3461
4625
  Band.all
3462
4626
  end
3463
4627
 
3464
- let!(:update) do
3465
- context.update_all({ '$set' => { 'labels.$[i].name' => 'Sony' } },
3466
- array_filters: [{ 'i.name' => 'Cbs' }])
4628
+ it "gets the third_to_last document" do
4629
+ expect(context.sort(name: 1).third_to_last!).to eq(depeche_mode)
3467
4630
  end
4631
+ end
3468
4632
 
3469
- it 'applies the array filters' do
3470
- expect(Band.where(name: 'Depeche Mode').first.labels.collect(&:name)).to match_array(['Warner', 'Sony', 'Sony'])
4633
+ context "when there are no documents" do
4634
+ let(:criteria) do
4635
+ Band.where(name: "bogus")
3471
4636
  end
3472
4637
 
3473
- it 'updates all documents' do
3474
- expect(Band.where(name: 'FKA Twigs').first.labels.collect(&:name)).to match_array(['Warner', 'Sony'])
4638
+ it "raises an error" do
4639
+ expect do
4640
+ context.third_to_last!
4641
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Band./)
3475
4642
  end
3476
4643
  end
3477
4644
  end
3478
4645
 
3479
- describe '#pipeline' do
4646
+ describe '#load_async' do
4647
+ let!(:band) do
4648
+ Band.create!(name: "Depeche Mode")
4649
+ end
3480
4650
 
3481
- context 'when the criteria has a selector' do
4651
+ let(:criteria) do
4652
+ Band.where(name: "Depeche Mode")
4653
+ end
3482
4654
 
3483
- before do
3484
- Artist.index(name: "text")
3485
- Artist.create_indexes
3486
- end
4655
+ let(:context) do
4656
+ described_class.new(criteria)
4657
+ end
3487
4658
 
3488
- let(:criteria) do
3489
- Artist.text_search("New Order")
3490
- end
4659
+ context 'with global thread pool async query executor' do
4660
+ config_override :async_query_executor, :global_thread_pool
3491
4661
 
3492
- let(:context) do
3493
- described_class.new(criteria)
4662
+ it 'preloads the documents' do
4663
+ context.load_async
4664
+ context.documents_loader.wait
4665
+
4666
+ expect(context.view).not_to receive(:map)
4667
+ expect(context.to_a).to eq([band])
3494
4668
  end
3495
4669
 
3496
- let(:pipeline_match) do
3497
- context.send(:pipeline, :some_field).first['$match']
4670
+ it 're-raises exception during preload' do
4671
+ expect_any_instance_of(Mongoid::Contextual::Mongo::DocumentsLoader)
4672
+ .to receive(:execute)
4673
+ .at_least(:once)
4674
+ .and_raise(Mongo::Error::OperationFailure)
4675
+
4676
+ context.load_async
4677
+ context.documents_loader.wait
4678
+
4679
+ expect do
4680
+ context.to_a
4681
+ end.to raise_error(Mongo::Error::OperationFailure)
3498
4682
  end
4683
+ end
3499
4684
 
3500
- it 'creates a pipeline with the selector as one of the $match criteria' do
3501
- expect(pipeline_match).to include({ '$text' => { '$search' => "New Order" } })
4685
+ context 'with immediate thread pool async query executor' do
4686
+ config_override :async_query_executor, :immediate
4687
+
4688
+ it 'preloads the documents' do
4689
+ context.load_async
4690
+ context.documents_loader.wait
4691
+
4692
+ expect(context.view).not_to receive(:map)
4693
+ expect(context.to_a).to eq([band])
3502
4694
  end
3503
4695
 
3504
- it 'creates a pipeline with the $exists operator as one of the $match criteria' do
3505
- expect(pipeline_match).to include({ 'some_field' => { '$exists' => true } })
4696
+ it 're-raises exception during preload' do
4697
+ expect_any_instance_of(Mongoid::Contextual::Mongo::DocumentsLoader)
4698
+ .to receive(:execute)
4699
+ .at_least(:once)
4700
+ .and_raise(Mongo::Error::OperationFailure)
4701
+
4702
+ context.load_async
4703
+ context.documents_loader.wait
4704
+
4705
+ expect do
4706
+ context.to_a
4707
+ end.to raise_error(Mongo::Error::OperationFailure)
3506
4708
  end
3507
4709
  end
3508
4710
  end