mongoid 7.5.4 → 8.1.6

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