mongoid 9.0.11 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1033) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -8
  3. data/README.md +5 -11
  4. data/Rakefile +35 -38
  5. data/lib/config/locales/en.yml +60 -0
  6. data/lib/mongoid/association/accessors.rb +27 -26
  7. data/lib/mongoid/association/bindable.rb +43 -45
  8. data/lib/mongoid/association/builders.rb +18 -15
  9. data/lib/mongoid/association/constrainable.rb +3 -5
  10. data/lib/mongoid/association/depending.rb +43 -43
  11. data/lib/mongoid/association/eager.rb +56 -13
  12. data/lib/mongoid/association/eager_load/discriminated_inclusion.rb +88 -0
  13. data/lib/mongoid/association/eager_load/embedded_distributor.rb +151 -0
  14. data/lib/mongoid/association/eager_load/inclusion.rb +173 -0
  15. data/lib/mongoid/association/eager_load/inclusion_tree.rb +96 -0
  16. data/lib/mongoid/association/eager_load/lookup_pipeline.rb +121 -0
  17. data/lib/mongoid/association/eager_load/polymorphic_preloader.rb +61 -0
  18. data/lib/mongoid/association/eager_load/polymorphic_targets.rb +151 -0
  19. data/lib/mongoid/association/eager_loadable.rb +108 -23
  20. data/lib/mongoid/association/embedded/batchable.rb +28 -30
  21. data/lib/mongoid/association/embedded/cyclic.rb +8 -11
  22. data/lib/mongoid/association/embedded/eager.rb +1 -6
  23. data/lib/mongoid/association/embedded/embedded_in/binding.rb +6 -9
  24. data/lib/mongoid/association/embedded/embedded_in/buildable.rb +2 -3
  25. data/lib/mongoid/association/embedded/embedded_in/proxy.rb +2 -0
  26. data/lib/mongoid/association/embedded/embedded_in.rb +25 -22
  27. data/lib/mongoid/association/embedded/embeds_many/binding.rb +0 -2
  28. data/lib/mongoid/association/embedded/embeds_many/buildable.rb +2 -3
  29. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +7 -2
  30. data/lib/mongoid/association/embedded/embeds_many.rb +24 -21
  31. data/lib/mongoid/association/embedded/embeds_one/binding.rb +0 -2
  32. data/lib/mongoid/association/embedded/embeds_one/buildable.rb +2 -6
  33. data/lib/mongoid/association/embedded/embeds_one.rb +21 -18
  34. data/lib/mongoid/association/embedded.rb +0 -1
  35. data/lib/mongoid/association/macros.rb +16 -8
  36. data/lib/mongoid/association/many.rb +75 -3
  37. data/lib/mongoid/association/marshalable.rb +0 -3
  38. data/lib/mongoid/association/nested/many.rb +52 -18
  39. data/lib/mongoid/association/nested/nested_buildable.rb +20 -9
  40. data/lib/mongoid/association/nested/one.rb +10 -8
  41. data/lib/mongoid/association/nested.rb +1 -3
  42. data/lib/mongoid/association/one.rb +0 -3
  43. data/lib/mongoid/association/options.rb +25 -7
  44. data/lib/mongoid/association/referenced/auto_save.rb +0 -2
  45. data/lib/mongoid/association/referenced/belongs_to/binding.rb +11 -15
  46. data/lib/mongoid/association/referenced/belongs_to/buildable.rb +23 -5
  47. data/lib/mongoid/association/referenced/belongs_to/eager.rb +7 -4
  48. data/lib/mongoid/association/referenced/belongs_to.rb +49 -43
  49. data/lib/mongoid/association/referenced/counter_cache.rb +7 -12
  50. data/lib/mongoid/association/referenced/has_and_belongs_to_many/binding.rb +0 -2
  51. data/lib/mongoid/association/referenced/has_and_belongs_to_many/buildable.rb +8 -4
  52. data/lib/mongoid/association/referenced/has_and_belongs_to_many/eager.rb +0 -3
  53. data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +57 -47
  54. data/lib/mongoid/association/referenced/has_many/binding.rb +0 -2
  55. data/lib/mongoid/association/referenced/has_many/buildable.rb +11 -5
  56. data/lib/mongoid/association/referenced/has_many/eager.rb +0 -3
  57. data/lib/mongoid/association/referenced/has_many/enumerable.rb +109 -38
  58. data/lib/mongoid/association/referenced/has_many/proxy.rb +110 -7
  59. data/lib/mongoid/association/referenced/has_many.rb +30 -24
  60. data/lib/mongoid/association/referenced/has_many_through/eager.rb +99 -0
  61. data/lib/mongoid/association/referenced/has_many_through/proxy.rb +68 -0
  62. data/lib/mongoid/association/referenced/has_many_through.rb +201 -0
  63. data/lib/mongoid/association/referenced/has_one/binding.rb +0 -2
  64. data/lib/mongoid/association/referenced/has_one/buildable.rb +11 -9
  65. data/lib/mongoid/association/referenced/has_one/eager.rb +0 -3
  66. data/lib/mongoid/association/referenced/has_one.rb +27 -21
  67. data/lib/mongoid/association/referenced/has_one_through/eager.rb +65 -0
  68. data/lib/mongoid/association/referenced/has_one_through/proxy.rb +30 -0
  69. data/lib/mongoid/association/referenced/has_one_through.rb +167 -0
  70. data/lib/mongoid/association/referenced/syncable.rb +30 -34
  71. data/lib/mongoid/association/referenced.rb +2 -1
  72. data/lib/mongoid/association/reflections.rb +0 -3
  73. data/lib/mongoid/association/relatable.rb +108 -65
  74. data/lib/mongoid/association.rb +19 -15
  75. data/lib/mongoid/atomic/modifiers.rb +65 -25
  76. data/lib/mongoid/atomic/paths/embedded/many.rb +4 -7
  77. data/lib/mongoid/atomic/paths/embedded/one.rb +2 -4
  78. data/lib/mongoid/atomic/paths/embedded.rb +3 -6
  79. data/lib/mongoid/atomic/paths/root.rb +1 -4
  80. data/lib/mongoid/atomic/paths.rb +2 -3
  81. data/lib/mongoid/atomic.rb +0 -3
  82. data/lib/mongoid/attributes/dynamic.rb +6 -7
  83. data/lib/mongoid/attributes/nested.rb +8 -10
  84. data/lib/mongoid/attributes/projector.rb +7 -20
  85. data/lib/mongoid/attributes/readonly.rb +6 -11
  86. data/lib/mongoid/attributes.rb +18 -34
  87. data/lib/mongoid/cacheable.rb +24 -6
  88. data/lib/mongoid/clients/factory.rb +13 -22
  89. data/lib/mongoid/clients/options.rb +2 -5
  90. data/lib/mongoid/clients/sessions.rb +50 -60
  91. data/lib/mongoid/clients/storage_options.rb +1 -4
  92. data/lib/mongoid/clients/validators/storage.rb +2 -3
  93. data/lib/mongoid/clients/validators.rb +1 -2
  94. data/lib/mongoid/clients.rb +8 -11
  95. data/lib/mongoid/collection_configurable.rb +18 -14
  96. data/lib/mongoid/composable.rb +30 -34
  97. data/lib/mongoid/config/defaults.rb +6 -4
  98. data/lib/mongoid/config/encryption.rb +36 -21
  99. data/lib/mongoid/config/environment.rb +7 -16
  100. data/lib/mongoid/config/introspection.rb +14 -16
  101. data/lib/mongoid/config/options.rb +16 -10
  102. data/lib/mongoid/config/validators/async_query_executor.rb +3 -6
  103. data/lib/mongoid/config/validators/client.rb +12 -15
  104. data/lib/mongoid/config/validators/option.rb +3 -5
  105. data/lib/mongoid/config/validators.rb +3 -4
  106. data/lib/mongoid/config.rb +188 -47
  107. data/lib/mongoid/contextual/aggregable/memory.rb +7 -5
  108. data/lib/mongoid/contextual/aggregable/mongo.rb +20 -22
  109. data/lib/mongoid/contextual/aggregable/none.rb +2 -4
  110. data/lib/mongoid/contextual/aggregable.rb +5 -7
  111. data/lib/mongoid/contextual/atomic.rb +20 -24
  112. data/lib/mongoid/contextual/command.rb +0 -3
  113. data/lib/mongoid/contextual/map_reduce.rb +12 -16
  114. data/lib/mongoid/contextual/memory.rb +87 -69
  115. data/lib/mongoid/contextual/mongo/documents_loader.rb +2 -3
  116. data/lib/mongoid/contextual/mongo.rb +169 -85
  117. data/lib/mongoid/contextual/none.rb +9 -9
  118. data/lib/mongoid/contextual/queryable.rb +1 -4
  119. data/lib/mongoid/contextual.rb +18 -7
  120. data/lib/mongoid/copyable.rb +7 -9
  121. data/lib/mongoid/criteria/findable.rb +5 -7
  122. data/lib/mongoid/criteria/includable.rb +67 -14
  123. data/lib/mongoid/criteria/inspectable.rb +2 -5
  124. data/lib/mongoid/criteria/marshalable.rb +4 -8
  125. data/lib/mongoid/criteria/modifiable.rb +3 -11
  126. data/lib/mongoid/criteria/options.rb +0 -3
  127. data/lib/mongoid/criteria/permission.rb +34 -37
  128. data/lib/mongoid/criteria/queryable/aggregable.rb +9 -10
  129. data/lib/mongoid/criteria/queryable/expandable.rb +4 -9
  130. data/lib/mongoid/criteria/queryable/extensions/array.rb +7 -10
  131. data/lib/mongoid/criteria/queryable/extensions/big_decimal.rb +3 -5
  132. data/lib/mongoid/criteria/queryable/extensions/boolean.rb +1 -4
  133. data/lib/mongoid/criteria/queryable/extensions/date.rb +2 -6
  134. data/lib/mongoid/criteria/queryable/extensions/date_time.rb +3 -7
  135. data/lib/mongoid/criteria/queryable/extensions/hash.rb +1 -4
  136. data/lib/mongoid/criteria/queryable/extensions/nil_class.rb +20 -11
  137. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +15 -11
  138. data/lib/mongoid/criteria/queryable/extensions/object.rb +7 -8
  139. data/lib/mongoid/criteria/queryable/extensions/range.rb +6 -9
  140. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +10 -12
  141. data/lib/mongoid/criteria/queryable/extensions/set.rb +4 -6
  142. data/lib/mongoid/criteria/queryable/extensions/string.rb +5 -9
  143. data/lib/mongoid/criteria/queryable/extensions/symbol.rb +3 -7
  144. data/lib/mongoid/criteria/queryable/extensions/time.rb +2 -6
  145. data/lib/mongoid/criteria/queryable/extensions/time_with_zone.rb +2 -12
  146. data/lib/mongoid/criteria/queryable/extensions.rb +18 -21
  147. data/lib/mongoid/criteria/queryable/key.rb +12 -18
  148. data/lib/mongoid/criteria/queryable/macroable.rb +0 -3
  149. data/lib/mongoid/criteria/queryable/mergeable.rb +35 -39
  150. data/lib/mongoid/criteria/queryable/optional.rb +17 -18
  151. data/lib/mongoid/criteria/queryable/options.rb +20 -9
  152. data/lib/mongoid/criteria/queryable/pipeline.rb +6 -9
  153. data/lib/mongoid/criteria/queryable/selectable.rb +253 -199
  154. data/lib/mongoid/criteria/queryable/selector.rb +20 -29
  155. data/lib/mongoid/criteria/queryable/smash.rb +1 -6
  156. data/lib/mongoid/criteria/queryable/storable.rb +20 -39
  157. data/lib/mongoid/criteria/queryable.rb +25 -15
  158. data/lib/mongoid/criteria/scopable.rb +5 -11
  159. data/lib/mongoid/criteria/translator.rb +5 -8
  160. data/lib/mongoid/criteria.rb +109 -54
  161. data/lib/mongoid/deprecable.rb +45 -5
  162. data/lib/mongoid/deprecation.rb +2 -6
  163. data/lib/mongoid/document.rb +1 -1
  164. data/lib/mongoid/encryptable.rb +47 -2
  165. data/lib/mongoid/equality.rb +4 -5
  166. data/lib/mongoid/errors/ambiguous_relationship.rb +2 -5
  167. data/lib/mongoid/errors/attribute_not_loaded.rb +1 -4
  168. data/lib/mongoid/errors/callback.rb +1 -4
  169. data/lib/mongoid/errors/config_redactor.rb +43 -0
  170. data/lib/mongoid/errors/create_collection_failure.rb +8 -11
  171. data/lib/mongoid/errors/criteria_argument_required.rb +2 -5
  172. data/lib/mongoid/errors/delete_restriction.rb +1 -4
  173. data/lib/mongoid/errors/document_not_destroyed.rb +1 -4
  174. data/lib/mongoid/errors/document_not_found.rb +16 -21
  175. data/lib/mongoid/errors/drop_collection_failure.rb +7 -10
  176. data/lib/mongoid/errors/empty_config_file.rb +1 -4
  177. data/lib/mongoid/errors/immutable_attribute.rb +1 -4
  178. data/lib/mongoid/errors/in_memory_collation_not_supported.rb +1 -4
  179. data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
  180. data/lib/mongoid/errors/invalid_async_query_executor.rb +2 -5
  181. data/lib/mongoid/errors/invalid_auto_encryption_configuration.rb +2 -5
  182. data/lib/mongoid/errors/invalid_collection.rb +1 -3
  183. data/lib/mongoid/errors/invalid_config_file.rb +1 -4
  184. data/lib/mongoid/errors/invalid_config_option.rb +2 -5
  185. data/lib/mongoid/errors/invalid_dependent_strategy.rb +6 -10
  186. data/lib/mongoid/errors/invalid_discriminator_key_target.rb +2 -4
  187. data/lib/mongoid/errors/invalid_dot_dollar_assignment.rb +1 -4
  188. data/lib/mongoid/errors/invalid_elem_match_operator.rb +5 -9
  189. data/lib/mongoid/errors/invalid_estimated_count_criteria.rb +1 -4
  190. data/lib/mongoid/errors/invalid_estimated_count_scoping.rb +1 -4
  191. data/lib/mongoid/errors/invalid_expression_operator.rb +3 -7
  192. data/lib/mongoid/errors/invalid_field.rb +2 -5
  193. data/lib/mongoid/errors/invalid_field_operator.rb +5 -9
  194. data/lib/mongoid/errors/invalid_field_option.rb +2 -5
  195. data/lib/mongoid/errors/invalid_field_type.rb +1 -4
  196. data/lib/mongoid/errors/invalid_find.rb +1 -4
  197. data/lib/mongoid/errors/invalid_global_executor_concurrency.rb +1 -4
  198. data/lib/mongoid/errors/invalid_includes.rb +3 -6
  199. data/lib/mongoid/errors/invalid_index.rb +1 -4
  200. data/lib/mongoid/errors/invalid_options.rb +1 -4
  201. data/lib/mongoid/errors/invalid_path.rb +1 -4
  202. data/lib/mongoid/errors/invalid_persistence_option.rb +4 -7
  203. data/lib/mongoid/errors/invalid_query.rb +6 -11
  204. data/lib/mongoid/errors/invalid_relation.rb +10 -13
  205. data/lib/mongoid/errors/invalid_relation_option.rb +4 -7
  206. data/lib/mongoid/errors/invalid_scope.rb +1 -4
  207. data/lib/mongoid/errors/invalid_session_nesting.rb +0 -3
  208. data/lib/mongoid/errors/invalid_set_polymorphic_relation.rb +2 -4
  209. data/lib/mongoid/errors/invalid_storage_options.rb +1 -4
  210. data/lib/mongoid/errors/invalid_time.rb +1 -4
  211. data/lib/mongoid/errors/invalid_transaction_nesting.rb +0 -3
  212. data/lib/mongoid/errors/inverse_not_found.rb +1 -4
  213. data/lib/mongoid/errors/mixed_client_configuration.rb +2 -5
  214. data/lib/mongoid/errors/mixed_relations.rb +1 -3
  215. data/lib/mongoid/errors/mongoid_error.rb +7 -11
  216. data/lib/mongoid/errors/nested_attributes_metadata_not_found.rb +1 -4
  217. data/lib/mongoid/errors/no_client_config.rb +1 -4
  218. data/lib/mongoid/errors/no_client_database.rb +2 -5
  219. data/lib/mongoid/errors/no_client_hosts.rb +2 -5
  220. data/lib/mongoid/errors/no_clients_config.rb +1 -4
  221. data/lib/mongoid/errors/no_default_client.rb +1 -4
  222. data/lib/mongoid/errors/no_encryption_schema.rb +28 -0
  223. data/lib/mongoid/errors/no_environment.rb +1 -4
  224. data/lib/mongoid/errors/no_map_reduce_output.rb +1 -4
  225. data/lib/mongoid/errors/no_metadata.rb +1 -4
  226. data/lib/mongoid/errors/no_parent.rb +1 -4
  227. data/lib/mongoid/errors/readonly_association.rb +29 -0
  228. data/lib/mongoid/errors/readonly_attribute.rb +1 -4
  229. data/lib/mongoid/errors/readonly_document.rb +1 -4
  230. data/lib/mongoid/errors/rollback.rb +0 -2
  231. data/lib/mongoid/errors/scope_overwrite.rb +2 -4
  232. data/lib/mongoid/errors/sessions_not_supported.rb +0 -3
  233. data/lib/mongoid/errors/too_many_nested_attribute_records.rb +1 -3
  234. data/lib/mongoid/errors/transaction_error.rb +0 -3
  235. data/lib/mongoid/errors/transactions_not_supported.rb +0 -3
  236. data/lib/mongoid/errors/unknown_attribute.rb +1 -4
  237. data/lib/mongoid/errors/unknown_model.rb +1 -4
  238. data/lib/mongoid/errors/unregistered_class.rb +1 -1
  239. data/lib/mongoid/errors/unsaved_document.rb +1 -3
  240. data/lib/mongoid/errors/unsupported_isolation_level.rb +22 -0
  241. data/lib/mongoid/errors/unsupported_javascript.rb +1 -4
  242. data/lib/mongoid/errors/validations.rb +3 -5
  243. data/lib/mongoid/errors.rb +76 -72
  244. data/lib/mongoid/evolvable.rb +0 -3
  245. data/lib/mongoid/extensions/array.rb +5 -7
  246. data/lib/mongoid/extensions/big_decimal.rb +6 -7
  247. data/lib/mongoid/extensions/binary.rb +3 -6
  248. data/lib/mongoid/extensions/boolean.rb +4 -7
  249. data/lib/mongoid/extensions/bson_document.rb +28 -0
  250. data/lib/mongoid/extensions/date.rb +10 -14
  251. data/lib/mongoid/extensions/date_time.rb +2 -6
  252. data/lib/mongoid/extensions/decimal128.rb +1 -5
  253. data/lib/mongoid/extensions/false_class.rb +3 -5
  254. data/lib/mongoid/extensions/float.rb +5 -10
  255. data/lib/mongoid/extensions/hash.rb +5 -10
  256. data/lib/mongoid/extensions/integer.rb +5 -10
  257. data/lib/mongoid/extensions/module.rb +1 -4
  258. data/lib/mongoid/extensions/nil_class.rb +0 -1
  259. data/lib/mongoid/extensions/object.rb +9 -14
  260. data/lib/mongoid/extensions/object_id.rb +2 -6
  261. data/lib/mongoid/extensions/range.rb +14 -15
  262. data/lib/mongoid/extensions/raw_value.rb +0 -3
  263. data/lib/mongoid/extensions/regexp.rb +3 -6
  264. data/lib/mongoid/extensions/set.rb +3 -4
  265. data/lib/mongoid/extensions/string.rb +8 -12
  266. data/lib/mongoid/extensions/symbol.rb +3 -7
  267. data/lib/mongoid/extensions/time.rb +24 -26
  268. data/lib/mongoid/extensions/time_with_zone.rb +4 -7
  269. data/lib/mongoid/extensions/true_class.rb +3 -5
  270. data/lib/mongoid/extensions/vector.rb +64 -0
  271. data/lib/mongoid/extensions.rb +27 -26
  272. data/lib/mongoid/factory.rb +5 -9
  273. data/lib/mongoid/field_readable.rb +69 -0
  274. data/lib/mongoid/fields/encrypted.rb +0 -1
  275. data/lib/mongoid/fields/foreign_key.rb +9 -10
  276. data/lib/mongoid/fields/localized.rb +12 -12
  277. data/lib/mongoid/fields/standard.rb +5 -7
  278. data/lib/mongoid/fields/validators/macro.rb +23 -34
  279. data/lib/mongoid/fields/validators.rb +1 -2
  280. data/lib/mongoid/fields.rb +151 -58
  281. data/lib/mongoid/findable.rb +82 -53
  282. data/lib/mongoid/indexable/specification.rb +20 -6
  283. data/lib/mongoid/indexable/validators/options.rb +33 -40
  284. data/lib/mongoid/indexable.rb +53 -40
  285. data/lib/mongoid/inspectable.rb +4 -7
  286. data/lib/mongoid/interceptable.rb +41 -40
  287. data/lib/mongoid/loadable.rb +17 -18
  288. data/lib/mongoid/loggable.rb +4 -8
  289. data/lib/mongoid/matchable.rb +6 -3
  290. data/lib/mongoid/matcher/all.rb +2 -5
  291. data/lib/mongoid/matcher/and.rb +0 -3
  292. data/lib/mongoid/matcher/bits.rb +13 -8
  293. data/lib/mongoid/matcher/bits_all_clear.rb +1 -3
  294. data/lib/mongoid/matcher/bits_all_set.rb +1 -3
  295. data/lib/mongoid/matcher/bits_any_clear.rb +1 -3
  296. data/lib/mongoid/matcher/bits_any_set.rb +1 -3
  297. data/lib/mongoid/matcher/elem_match.rb +13 -15
  298. data/lib/mongoid/matcher/elem_match_expression.rb +0 -3
  299. data/lib/mongoid/matcher/eq.rb +0 -3
  300. data/lib/mongoid/matcher/eq_impl.rb +12 -17
  301. data/lib/mongoid/matcher/eq_impl_with_regexp.rb +4 -9
  302. data/lib/mongoid/matcher/exists.rb +1 -4
  303. data/lib/mongoid/matcher/expression.rb +5 -10
  304. data/lib/mongoid/matcher/expression_operator.rb +1 -3
  305. data/lib/mongoid/matcher/field_expression.rb +10 -13
  306. data/lib/mongoid/matcher/field_operator.rb +4 -6
  307. data/lib/mongoid/matcher/gt.rb +0 -3
  308. data/lib/mongoid/matcher/gte.rb +0 -3
  309. data/lib/mongoid/matcher/in.rb +9 -12
  310. data/lib/mongoid/matcher/lt.rb +0 -3
  311. data/lib/mongoid/matcher/lte.rb +0 -3
  312. data/lib/mongoid/matcher/mod.rb +4 -8
  313. data/lib/mongoid/matcher/ne.rb +0 -3
  314. data/lib/mongoid/matcher/nin.rb +0 -3
  315. data/lib/mongoid/matcher/nor.rb +1 -6
  316. data/lib/mongoid/matcher/not.rb +0 -3
  317. data/lib/mongoid/matcher/or.rb +0 -3
  318. data/lib/mongoid/matcher/regex.rb +9 -13
  319. data/lib/mongoid/matcher/regexp_budget.rb +372 -0
  320. data/lib/mongoid/matcher/size.rb +2 -5
  321. data/lib/mongoid/matcher/type.rb +32 -37
  322. data/lib/mongoid/matcher.rb +8 -15
  323. data/lib/mongoid/model_resolver.rb +1 -0
  324. data/lib/mongoid/persistable/creatable.rb +23 -7
  325. data/lib/mongoid/persistable/deletable.rb +6 -7
  326. data/lib/mongoid/persistable/destroyable.rb +2 -6
  327. data/lib/mongoid/persistable/incrementable.rb +1 -3
  328. data/lib/mongoid/persistable/logical.rb +3 -5
  329. data/lib/mongoid/persistable/maxable.rb +2 -4
  330. data/lib/mongoid/persistable/minable.rb +2 -4
  331. data/lib/mongoid/persistable/multipliable.rb +1 -3
  332. data/lib/mongoid/persistable/poppable.rb +2 -4
  333. data/lib/mongoid/persistable/pullable.rb +3 -5
  334. data/lib/mongoid/persistable/pushable.rb +5 -7
  335. data/lib/mongoid/persistable/renamable.rb +1 -3
  336. data/lib/mongoid/persistable/savable.rb +0 -3
  337. data/lib/mongoid/persistable/settable.rb +4 -13
  338. data/lib/mongoid/persistable/unsettable.rb +1 -3
  339. data/lib/mongoid/persistable/updatable.rb +23 -33
  340. data/lib/mongoid/persistable/upsertable.rb +8 -10
  341. data/lib/mongoid/persistable.rb +28 -27
  342. data/lib/mongoid/persistence_context.rb +69 -41
  343. data/lib/mongoid/pluckable.rb +0 -2
  344. data/lib/mongoid/positional.rb +5 -11
  345. data/lib/mongoid/railtie.rb +16 -22
  346. data/lib/mongoid/railties/console_sandbox.rb +6 -8
  347. data/lib/mongoid/railties/controller_runtime.rb +7 -11
  348. data/lib/mongoid/railties/database.rake +42 -44
  349. data/lib/mongoid/scopable.rb +23 -31
  350. data/lib/mongoid/search_indexable.rb +378 -7
  351. data/lib/mongoid/selectable.rb +1 -3
  352. data/lib/mongoid/serializable.rb +18 -12
  353. data/lib/mongoid/shardable.rb +8 -17
  354. data/lib/mongoid/stateful.rb +5 -10
  355. data/lib/mongoid/stringified_symbol.rb +5 -9
  356. data/lib/mongoid/tasks/database.rake +31 -32
  357. data/lib/mongoid/tasks/database.rb +51 -39
  358. data/lib/mongoid/tasks/encryption.rb +10 -15
  359. data/lib/mongoid/threaded/lifecycle.rb +6 -9
  360. data/lib/mongoid/threaded.rb +168 -32
  361. data/lib/mongoid/timestamps/created/short.rb +2 -3
  362. data/lib/mongoid/timestamps/created.rb +1 -2
  363. data/lib/mongoid/timestamps/short.rb +0 -1
  364. data/lib/mongoid/timestamps/timeless.rb +92 -23
  365. data/lib/mongoid/timestamps/updated/short.rb +2 -3
  366. data/lib/mongoid/timestamps/updated.rb +2 -5
  367. data/lib/mongoid/timestamps.rb +4 -6
  368. data/lib/mongoid/touchable.rb +19 -11
  369. data/lib/mongoid/traversable.rb +5 -4
  370. data/lib/mongoid/validatable/associated.rb +19 -7
  371. data/lib/mongoid/validatable/format.rb +0 -2
  372. data/lib/mongoid/validatable/length.rb +0 -2
  373. data/lib/mongoid/validatable/localizable.rb +0 -3
  374. data/lib/mongoid/validatable/macros.rb +0 -2
  375. data/lib/mongoid/validatable/numericality.rb +24 -3
  376. data/lib/mongoid/validatable/presence.rb +8 -10
  377. data/lib/mongoid/validatable/queryable.rb +0 -3
  378. data/lib/mongoid/validatable/uniqueness.rb +12 -14
  379. data/lib/mongoid/validatable.rb +15 -20
  380. data/lib/mongoid/version.rb +1 -1
  381. data/lib/mongoid/warnings.rb +10 -11
  382. data/lib/mongoid.rb +30 -34
  383. data/lib/rails/generators/mongoid/config/config_generator.rb +2 -4
  384. data/lib/rails/generators/mongoid/config/templates/mongoid.rb +4 -5
  385. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +2 -2
  386. data/lib/rails/generators/mongoid/model/model_generator.rb +5 -8
  387. data/lib/rails/generators/mongoid_generator.rb +6 -10
  388. data/lib/rails/mongoid.rb +1 -3
  389. data/spec/integration/active_job_spec.rb +1 -2
  390. data/spec/integration/app_spec.rb +149 -179
  391. data/spec/integration/associations/belongs_to_spec.rb +43 -28
  392. data/spec/integration/associations/embedded_dirty_spec.rb +0 -1
  393. data/spec/integration/associations/embedded_spec.rb +41 -43
  394. data/spec/integration/associations/embeds_many_spec.rb +46 -29
  395. data/spec/integration/associations/embeds_one_spec.rb +2 -1
  396. data/spec/integration/associations/foreign_key_spec.rb +10 -11
  397. data/spec/integration/associations/foreign_key_spec_models.rb +8 -9
  398. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +33 -15
  399. data/spec/integration/associations/has_many_spec.rb +17 -18
  400. data/spec/integration/associations/has_one_spec.rb +42 -45
  401. data/spec/integration/associations/nested_attributes_assignment_spec.rb +36 -39
  402. data/spec/integration/associations/reverse_population_spec.rb +4 -5
  403. data/spec/integration/associations/reverse_population_spec_models.rb +0 -1
  404. data/spec/integration/associations/scope_option_spec.rb +18 -20
  405. data/spec/integration/atomic/modifiers_spec.rb +72 -52
  406. data/spec/integration/bson_regexp_raw_spec.rb +2 -3
  407. data/spec/integration/caching_spec.rb +68 -0
  408. data/spec/integration/callbacks_models.rb +11 -9
  409. data/spec/integration/callbacks_spec.rb +78 -80
  410. data/spec/integration/contextual/empty_spec.rb +19 -20
  411. data/spec/integration/criteria/alias_query_spec.rb +12 -19
  412. data/spec/integration/criteria/date_field_spec.rb +1 -2
  413. data/spec/integration/criteria/default_scope_spec.rb +23 -18
  414. data/spec/integration/criteria/logical_spec.rb +18 -19
  415. data/spec/integration/criteria/range_spec.rb +77 -86
  416. data/spec/integration/criteria/raw_value_spec.rb +122 -109
  417. data/spec/integration/criteria/time_with_zone_spec.rb +33 -24
  418. data/spec/integration/discriminator_key_spec.rb +85 -85
  419. data/spec/integration/discriminator_value_spec.rb +70 -73
  420. data/spec/integration/document_spec.rb +9 -10
  421. data/spec/integration/dots_and_dollars_spec.rb +115 -120
  422. data/spec/integration/encryption_spec.rb +157 -9
  423. data/spec/integration/i18n_fallbacks_spec.rb +7 -8
  424. data/spec/integration/isolation_state_spec.rb +241 -0
  425. data/spec/integration/matcher_examples_spec.rb +145 -168
  426. data/spec/integration/matcher_operator_data/in.yml +6 -6
  427. data/spec/integration/matcher_operator_data/regex.yml +21 -0
  428. data/spec/integration/matcher_operator_spec.rb +16 -26
  429. data/spec/integration/matcher_regexp_timeout_spec.rb +215 -0
  430. data/spec/integration/matcher_spec.rb +31 -32
  431. data/spec/integration/persistence/collection_options_spec.rb +0 -2
  432. data/spec/integration/persistence/range_field_spec.rb +24 -2
  433. data/spec/integration/query_operator_guard_spec.rb +89 -0
  434. data/spec/integration/server_query_spec.rb +43 -37
  435. data/spec/integration/shardable_spec.rb +10 -11
  436. data/spec/integration/stringified_symbol_field_spec.rb +28 -43
  437. data/spec/lite_spec_helper.rb +11 -12
  438. data/spec/mongoid/association/accessors_spec.rb +197 -285
  439. data/spec/mongoid/association/auto_save_spec.rb +62 -93
  440. data/spec/mongoid/association/builders_spec.rb +121 -74
  441. data/spec/mongoid/association/constrainable_spec.rb +9 -22
  442. data/spec/mongoid/association/counter_cache_spec.rb +71 -104
  443. data/spec/mongoid/association/depending_spec.rb +166 -208
  444. data/spec/mongoid/association/eager_loadable_spec.rb +47 -0
  445. data/spec/mongoid/association/eager_spec.rb +416 -80
  446. data/spec/mongoid/association/embedded/cyclic_spec.rb +36 -48
  447. data/spec/mongoid/association/embedded/dirty_spec.rb +15 -20
  448. data/spec/mongoid/association/embedded/embedded_in/binding_spec.rb +36 -54
  449. data/spec/mongoid/association/embedded/embedded_in/buildable_spec.rb +8 -15
  450. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +135 -175
  451. data/spec/mongoid/association/embedded/embedded_in_spec.rb +25 -127
  452. data/spec/mongoid/association/embedded/embeds_many/binding_spec.rb +9 -14
  453. data/spec/mongoid/association/embedded/embeds_many/buildable_spec.rb +38 -51
  454. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +1184 -1340
  455. data/spec/mongoid/association/embedded/embeds_many_models.rb +17 -17
  456. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +11 -11
  457. data/spec/mongoid/association/embedded/embeds_many_spec.rb +25 -138
  458. data/spec/mongoid/association/embedded/embeds_one/binding_spec.rb +15 -23
  459. data/spec/mongoid/association/embedded/embeds_one/buildable_spec.rb +18 -26
  460. data/spec/mongoid/association/embedded/embeds_one/proxy_spec.rb +204 -273
  461. data/spec/mongoid/association/embedded/embeds_one_dnl_models.rb +0 -1
  462. data/spec/mongoid/association/embedded/embeds_one_models.rb +0 -1
  463. data/spec/mongoid/association/embedded/embeds_one_query_spec.rb +5 -6
  464. data/spec/mongoid/association/embedded/embeds_one_spec.rb +28 -143
  465. data/spec/mongoid/association/fallback_spec.rb +425 -0
  466. data/spec/mongoid/association/macros_spec.rb +239 -300
  467. data/spec/mongoid/association/nested/many_spec.rb +51 -73
  468. data/spec/mongoid/association/nested/one_spec.rb +56 -80
  469. data/spec/mongoid/association/options_spec.rb +187 -310
  470. data/spec/mongoid/association/polymorphic_spec.rb +35 -53
  471. data/spec/mongoid/association/referenced/belongs_to/binding_spec.rb +55 -76
  472. data/spec/mongoid/association/referenced/belongs_to/buildable_spec.rb +36 -41
  473. data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +98 -101
  474. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +258 -345
  475. data/spec/mongoid/association/referenced/belongs_to_models.rb +4 -3
  476. data/spec/mongoid/association/referenced/belongs_to_query_spec.rb +3 -4
  477. data/spec/mongoid/association/referenced/belongs_to_spec.rb +91 -294
  478. data/spec/mongoid/association/referenced/has_and_belongs_to_many/binding_spec.rb +42 -56
  479. data/spec/mongoid/association/referenced/has_and_belongs_to_many/buildable_spec.rb +41 -32
  480. data/spec/mongoid/association/referenced/has_and_belongs_to_many/eager_spec.rb +36 -42
  481. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_persistence_spec.rb +8 -9
  482. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +819 -975
  483. data/spec/mongoid/association/referenced/has_and_belongs_to_many_models.rb +9 -9
  484. data/spec/mongoid/association/referenced/has_and_belongs_to_many_query_spec.rb +5 -6
  485. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +109 -177
  486. data/spec/mongoid/association/referenced/has_many/binding_spec.rb +29 -43
  487. data/spec/mongoid/association/referenced/has_many/buildable_spec.rb +53 -53
  488. data/spec/mongoid/association/referenced/has_many/eager_spec.rb +137 -67
  489. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +431 -675
  490. data/spec/mongoid/association/referenced/has_many/proxy_query_spec.rb +1 -2
  491. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +318 -11
  492. data/spec/mongoid/association/referenced/has_many_models.rb +1 -2
  493. data/spec/mongoid/association/referenced/has_many_query_spec.rb +3 -4
  494. data/spec/mongoid/association/referenced/has_many_spec.rb +44 -188
  495. data/spec/mongoid/association/referenced/has_many_through/eager_spec.rb +58 -0
  496. data/spec/mongoid/association/referenced/has_many_through/proxy_spec.rb +62 -0
  497. data/spec/mongoid/association/referenced/has_many_through_spec.rb +399 -0
  498. data/spec/mongoid/association/referenced/has_one/binding_spec.rb +26 -38
  499. data/spec/mongoid/association/referenced/has_one/buildable_spec.rb +28 -29
  500. data/spec/mongoid/association/referenced/has_one/eager_spec.rb +29 -42
  501. data/spec/mongoid/association/referenced/has_one/proxy_spec.rb +270 -332
  502. data/spec/mongoid/association/referenced/has_one_models.rb +1 -2
  503. data/spec/mongoid/association/referenced/has_one_query_spec.rb +3 -4
  504. data/spec/mongoid/association/referenced/has_one_spec.rb +40 -188
  505. data/spec/mongoid/association/referenced/has_one_through/eager_spec.rb +58 -0
  506. data/spec/mongoid/association/referenced/has_one_through/proxy_spec.rb +49 -0
  507. data/spec/mongoid/association/referenced/has_one_through_spec.rb +211 -0
  508. data/spec/mongoid/association/reflections_spec.rb +14 -24
  509. data/spec/mongoid/association/syncable_spec.rb +102 -125
  510. data/spec/mongoid/association_spec.rb +47 -70
  511. data/spec/mongoid/atomic/modifiers_spec.rb +184 -197
  512. data/spec/mongoid/atomic/paths/embedded/many_spec.rb +39 -52
  513. data/spec/mongoid/atomic/paths/embedded/one_spec.rb +35 -49
  514. data/spec/mongoid/atomic/paths/root_spec.rb +9 -15
  515. data/spec/mongoid/atomic/paths_spec.rb +81 -117
  516. data/spec/mongoid/atomic_spec.rb +156 -171
  517. data/spec/mongoid/attributes/dynamic_spec.rb +5 -6
  518. data/spec/mongoid/attributes/nested_spec.rb +1461 -1693
  519. data/spec/mongoid/attributes/nested_spec_models.rb +8 -9
  520. data/spec/mongoid/attributes/projector_spec.rb +2 -6
  521. data/spec/mongoid/attributes/readonly_spec.rb +73 -97
  522. data/spec/mongoid/attributes_spec.rb +669 -826
  523. data/spec/mongoid/cacheable_spec.rb +74 -44
  524. data/spec/mongoid/changeable_spec.rb +591 -733
  525. data/spec/mongoid/clients/factory_spec.rb +122 -154
  526. data/spec/mongoid/clients/options_spec.rb +40 -59
  527. data/spec/mongoid/clients/sessions_spec.rb +25 -44
  528. data/spec/mongoid/clients/transactions_spec.rb +33 -50
  529. data/spec/mongoid/clients/transactions_spec_models.rb +3 -4
  530. data/spec/mongoid/clients_spec.rb +265 -355
  531. data/spec/mongoid/collection_configurable_spec.rb +9 -12
  532. data/spec/mongoid/composable_spec.rb +2 -9
  533. data/spec/mongoid/config/defaults_spec.rb +56 -30
  534. data/spec/mongoid/config/encryption_spec.rb +282 -57
  535. data/spec/mongoid/config/environment_spec.rb +30 -40
  536. data/spec/mongoid/config/introspection_spec.rb +42 -37
  537. data/spec/mongoid/config/options_spec.rb +14 -21
  538. data/spec/mongoid/config_spec.rb +145 -195
  539. data/spec/mongoid/contextual/aggregable/memory_spec.rb +189 -135
  540. data/spec/mongoid/contextual/aggregable/memory_table_spec.rb +4 -7
  541. data/spec/mongoid/contextual/aggregable/mongo_spec.rb +149 -187
  542. data/spec/mongoid/contextual/aggregable/none_spec.rb +14 -16
  543. data/spec/mongoid/contextual/atomic_spec.rb +223 -271
  544. data/spec/mongoid/contextual/map_reduce_spec.rb +110 -138
  545. data/spec/mongoid/contextual/memory_spec.rb +883 -854
  546. data/spec/mongoid/contextual/mongo/documents_loader_spec.rb +12 -13
  547. data/spec/mongoid/contextual/mongo_spec.rb +1008 -1213
  548. data/spec/mongoid/contextual/none_spec.rb +54 -59
  549. data/spec/mongoid/contextual/short_circuit_spec.rb +139 -0
  550. data/spec/mongoid/copyable_spec.rb +254 -300
  551. data/spec/mongoid/copyable_spec_models.rb +0 -1
  552. data/spec/mongoid/criteria/findable_spec.rb +322 -387
  553. data/spec/mongoid/criteria/includable_spec.rb +1356 -432
  554. data/spec/mongoid/criteria/includable_spec_models.rb +16 -7
  555. data/spec/mongoid/criteria/inspectable_spec.rb +11 -14
  556. data/spec/mongoid/criteria/marshalable_spec.rb +11 -14
  557. data/spec/mongoid/criteria/modifiable_spec.rb +366 -512
  558. data/spec/mongoid/criteria/options_spec.rb +2 -7
  559. data/spec/mongoid/criteria/queryable/aggregable_spec.rb +112 -141
  560. data/spec/mongoid/criteria/queryable/expandable_spec.rb +8 -11
  561. data/spec/mongoid/criteria/queryable/extensions/array_spec.rb +127 -184
  562. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +123 -60
  563. data/spec/mongoid/criteria/queryable/extensions/boolean_spec.rb +84 -117
  564. data/spec/mongoid/criteria/queryable/extensions/date_spec.rb +44 -65
  565. data/spec/mongoid/criteria/queryable/extensions/date_time_spec.rb +86 -108
  566. data/spec/mongoid/criteria/queryable/extensions/float_spec.rb +94 -33
  567. data/spec/mongoid/criteria/queryable/extensions/hash_spec.rb +103 -138
  568. data/spec/mongoid/criteria/queryable/extensions/integer_spec.rb +95 -34
  569. data/spec/mongoid/criteria/queryable/extensions/nil_class_spec.rb +25 -38
  570. data/spec/mongoid/criteria/queryable/extensions/numeric_spec.rb +26 -28
  571. data/spec/mongoid/criteria/queryable/extensions/object_spec.rb +32 -54
  572. data/spec/mongoid/criteria/queryable/extensions/range_spec.rb +136 -130
  573. data/spec/mongoid/criteria/queryable/extensions/regexp_raw_spec.rb +16 -24
  574. data/spec/mongoid/criteria/queryable/extensions/regexp_spec.rb +15 -24
  575. data/spec/mongoid/criteria/queryable/extensions/set_spec.rb +12 -19
  576. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +46 -63
  577. data/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb +28 -42
  578. data/spec/mongoid/criteria/queryable/extensions/time_spec.rb +73 -96
  579. data/spec/mongoid/criteria/queryable/extensions/time_with_zone_spec.rb +71 -92
  580. data/spec/mongoid/criteria/queryable/key_spec.rb +32 -36
  581. data/spec/mongoid/criteria/queryable/mergeable_spec.rb +44 -49
  582. data/spec/mongoid/criteria/queryable/optional_spec.rb +373 -486
  583. data/spec/mongoid/criteria/queryable/options_spec.rb +172 -123
  584. data/spec/mongoid/criteria/queryable/pipeline_spec.rb +65 -87
  585. data/spec/mongoid/criteria/queryable/queryable_spec.rb +41 -53
  586. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +589 -679
  587. data/spec/mongoid/criteria/queryable/selectable_shared_examples.rb +7 -11
  588. data/spec/mongoid/criteria/queryable/selectable_spec.rb +807 -998
  589. data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +354 -190
  590. data/spec/mongoid/criteria/queryable/selector_spec.rb +294 -390
  591. data/spec/mongoid/criteria/queryable/smash_spec.rb +6 -11
  592. data/spec/mongoid/criteria/queryable/storable_spec.rb +54 -81
  593. data/spec/mongoid/criteria/queryable_mql_spec.rb +92 -0
  594. data/spec/mongoid/criteria/scopable_spec.rb +102 -138
  595. data/spec/mongoid/criteria/translator_spec.rb +59 -60
  596. data/spec/mongoid/criteria_projection_spec.rb +81 -99
  597. data/spec/mongoid/criteria_spec.rb +937 -949
  598. data/spec/mongoid/document_fields_spec.rb +28 -30
  599. data/spec/mongoid/document_persistence_context_spec.rb +13 -15
  600. data/spec/mongoid/document_query_spec.rb +9 -12
  601. data/spec/mongoid/document_spec.rb +317 -409
  602. data/spec/mongoid/encryptable_spec.rb +61 -0
  603. data/spec/mongoid/equality_spec.rb +69 -102
  604. data/spec/mongoid/errors/ambiguous_relationship_spec.rb +8 -11
  605. data/spec/mongoid/errors/attribute_not_loaded_spec.rb +7 -10
  606. data/spec/mongoid/errors/callback_spec.rb +8 -11
  607. data/spec/mongoid/errors/config_redactor_spec.rb +112 -0
  608. data/spec/mongoid/errors/delete_restriction_spec.rb +5 -8
  609. data/spec/mongoid/errors/document_not_destroyed_spec.rb +7 -10
  610. data/spec/mongoid/errors/document_not_found_spec.rb +51 -61
  611. data/spec/mongoid/errors/invalid_collection_spec.rb +9 -13
  612. data/spec/mongoid/errors/invalid_config_file_spec.rb +8 -11
  613. data/spec/mongoid/errors/invalid_config_option_spec.rb +8 -11
  614. data/spec/mongoid/errors/invalid_field_option_spec.rb +7 -10
  615. data/spec/mongoid/errors/invalid_field_spec.rb +10 -11
  616. data/spec/mongoid/errors/invalid_field_type_spec.rb +9 -12
  617. data/spec/mongoid/errors/invalid_find_spec.rb +8 -11
  618. data/spec/mongoid/errors/invalid_includes_spec.rb +9 -13
  619. data/spec/mongoid/errors/invalid_index_spec.rb +8 -11
  620. data/spec/mongoid/errors/invalid_options_spec.rb +8 -11
  621. data/spec/mongoid/errors/invalid_path_spec.rb +7 -10
  622. data/spec/mongoid/errors/invalid_relation_spec.rb +15 -16
  623. data/spec/mongoid/errors/invalid_scope_spec.rb +8 -11
  624. data/spec/mongoid/errors/invalid_set_polymorphic_relation_spec.rb +3 -6
  625. data/spec/mongoid/errors/invalid_storage_options_spec.rb +8 -11
  626. data/spec/mongoid/errors/invalid_time_spec.rb +8 -11
  627. data/spec/mongoid/errors/inverse_not_found_spec.rb +9 -12
  628. data/spec/mongoid/errors/mixed_client_configuration_spec.rb +40 -12
  629. data/spec/mongoid/errors/mixed_relations_spec.rb +8 -11
  630. data/spec/mongoid/errors/mongoid_error_spec.rb +21 -24
  631. data/spec/mongoid/errors/nested_attributes_metadata_not_found_spec.rb +6 -9
  632. data/spec/mongoid/errors/no_client_config_spec.rb +7 -10
  633. data/spec/mongoid/errors/no_client_database_spec.rb +38 -12
  634. data/spec/mongoid/errors/no_client_hosts_spec.rb +35 -12
  635. data/spec/mongoid/errors/no_clients_config_spec.rb +7 -10
  636. data/spec/mongoid/errors/no_environment_spec.rb +8 -11
  637. data/spec/mongoid/errors/no_map_reduce_output_spec.rb +8 -11
  638. data/spec/mongoid/errors/no_metadata_spec.rb +7 -10
  639. data/spec/mongoid/errors/no_parent_spec.rb +7 -10
  640. data/spec/mongoid/errors/readonly_association_spec.rb +34 -0
  641. data/spec/mongoid/errors/readonly_attribute_spec.rb +7 -10
  642. data/spec/mongoid/errors/readonly_document_spec.rb +6 -9
  643. data/spec/mongoid/errors/scope_overwrite_spec.rb +9 -12
  644. data/spec/mongoid/errors/too_many_nested_attribute_records_spec.rb +9 -12
  645. data/spec/mongoid/errors/unknown_attribute_spec.rb +5 -8
  646. data/spec/mongoid/errors/unsaved_document_spec.rb +8 -11
  647. data/spec/mongoid/errors/unsupported_javascript_spec.rb +9 -12
  648. data/spec/mongoid/errors/validations_spec.rb +11 -14
  649. data/spec/mongoid/extensions/array_spec.rb +70 -92
  650. data/spec/mongoid/extensions/big_decimal_spec.rb +233 -313
  651. data/spec/mongoid/extensions/binary_spec.rb +21 -32
  652. data/spec/mongoid/extensions/boolean_spec.rb +52 -75
  653. data/spec/mongoid/extensions/date_class_mongoize_spec.rb +43 -54
  654. data/spec/mongoid/extensions/date_spec.rb +26 -38
  655. data/spec/mongoid/extensions/date_time_spec.rb +22 -32
  656. data/spec/mongoid/extensions/decimal128_spec.rb +12 -18
  657. data/spec/mongoid/extensions/false_class_spec.rb +10 -17
  658. data/spec/mongoid/extensions/float_spec.rb +40 -61
  659. data/spec/mongoid/extensions/hash_spec.rb +46 -64
  660. data/spec/mongoid/extensions/integer_spec.rb +40 -61
  661. data/spec/mongoid/extensions/module_spec.rb +13 -18
  662. data/spec/mongoid/extensions/nil_class_spec.rb +2 -5
  663. data/spec/mongoid/extensions/object_id_spec.rb +159 -247
  664. data/spec/mongoid/extensions/object_spec.rb +31 -47
  665. data/spec/mongoid/extensions/range_spec.rb +112 -115
  666. data/spec/mongoid/extensions/raw_value_spec.rb +10 -12
  667. data/spec/mongoid/extensions/regexp_spec.rb +23 -36
  668. data/spec/mongoid/extensions/set_spec.rb +38 -49
  669. data/spec/mongoid/extensions/string_spec.rb +118 -167
  670. data/spec/mongoid/extensions/stringified_symbol_spec.rb +29 -43
  671. data/spec/mongoid/extensions/symbol_spec.rb +12 -20
  672. data/spec/mongoid/extensions/time_spec.rb +160 -202
  673. data/spec/mongoid/extensions/time_with_zone_spec.rb +79 -105
  674. data/spec/mongoid/extensions/true_class_spec.rb +10 -17
  675. data/spec/mongoid/extensions/vector_spec.rb +175 -0
  676. data/spec/mongoid/factory_spec.rb +125 -148
  677. data/spec/mongoid/fields/foreign_key_spec.rb +124 -169
  678. data/spec/mongoid/fields/localized_spec.rb +140 -185
  679. data/spec/mongoid/fields/standard_spec.rb +43 -59
  680. data/spec/mongoid/fields_spec.rb +751 -620
  681. data/spec/mongoid/findable_spec.rb +260 -228
  682. data/spec/mongoid/indexable/specification_spec.rb +42 -58
  683. data/spec/mongoid/indexable_spec.rb +260 -264
  684. data/spec/mongoid/inspectable_spec.rb +45 -55
  685. data/spec/mongoid/interceptable_spec.rb +784 -922
  686. data/spec/mongoid/interceptable_spec_models.rb +35 -35
  687. data/spec/mongoid/loading_spec.rb +47 -43
  688. data/spec/mongoid/loggable_spec.rb +3 -6
  689. data/spec/mongoid/matcher/extract_attribute_spec.rb +2 -6
  690. data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
  691. data/spec/mongoid/mongoizable_spec.rb +81 -90
  692. data/spec/mongoid/monkey_patches_spec.rb +4 -0
  693. data/spec/mongoid/persistable/creatable_spec.rb +181 -220
  694. data/spec/mongoid/persistable/deletable_spec.rb +139 -179
  695. data/spec/mongoid/persistable/destroyable_spec.rb +135 -164
  696. data/spec/mongoid/persistable/incrementable_spec.rb +65 -84
  697. data/spec/mongoid/persistable/logical_spec.rb +48 -61
  698. data/spec/mongoid/persistable/maxable_spec.rb +45 -44
  699. data/spec/mongoid/persistable/minable_spec.rb +45 -44
  700. data/spec/mongoid/persistable/multipliable_spec.rb +54 -69
  701. data/spec/mongoid/persistable/poppable_spec.rb +47 -60
  702. data/spec/mongoid/persistable/pullable_spec.rb +79 -103
  703. data/spec/mongoid/persistable/pushable_spec.rb +106 -131
  704. data/spec/mongoid/persistable/renamable_spec.rb +52 -65
  705. data/spec/mongoid/persistable/savable_spec.rb +152 -189
  706. data/spec/mongoid/persistable/settable_spec.rb +121 -145
  707. data/spec/mongoid/persistable/unsettable_spec.rb +56 -73
  708. data/spec/mongoid/persistable/updatable_spec.rb +185 -232
  709. data/spec/mongoid/persistable/upsertable_spec.rb +66 -77
  710. data/spec/mongoid/persistable_spec.rb +132 -139
  711. data/spec/mongoid/persistence_context_spec.rb +31 -89
  712. data/spec/mongoid/positional_spec.rb +67 -79
  713. data/spec/mongoid/railties/bson_object_id_serializer_spec.rb +0 -3
  714. data/spec/mongoid/railties/console_sandbox_spec.rb +0 -1
  715. data/spec/mongoid/relations/proxy_spec.rb +0 -1
  716. data/spec/mongoid/reloadable_spec.rb +250 -145
  717. data/spec/mongoid/scopable_spec.rb +319 -398
  718. data/spec/mongoid/search_indexable_spec.rb +735 -44
  719. data/spec/mongoid/selectable_spec.rb +36 -51
  720. data/spec/mongoid/serializable_spec.rb +269 -312
  721. data/spec/mongoid/shardable_models.rb +5 -5
  722. data/spec/mongoid/shardable_spec.rb +44 -46
  723. data/spec/mongoid/stateful_spec.rb +55 -75
  724. data/spec/mongoid/tasks/database_rake_spec.rb +138 -141
  725. data/spec/mongoid/tasks/database_spec.rb +98 -80
  726. data/spec/mongoid/tasks/encryption_spec.rb +22 -20
  727. data/spec/mongoid/threaded_spec.rb +57 -83
  728. data/spec/mongoid/timestamps/created/short_spec.rb +12 -16
  729. data/spec/mongoid/timestamps/created_spec.rb +16 -20
  730. data/spec/mongoid/timestamps/timeless_spec.rb +228 -48
  731. data/spec/mongoid/timestamps/updated/short_spec.rb +21 -28
  732. data/spec/mongoid/timestamps/updated_spec.rb +18 -25
  733. data/spec/mongoid/timestamps_spec.rb +171 -188
  734. data/spec/mongoid/timestamps_spec_models.rb +12 -13
  735. data/spec/mongoid/touchable_spec.rb +355 -294
  736. data/spec/mongoid/touchable_spec_models.rb +45 -37
  737. data/spec/mongoid/traversable_spec.rb +435 -471
  738. data/spec/mongoid/validatable/associated_spec.rb +60 -59
  739. data/spec/mongoid/validatable/format_spec.rb +24 -34
  740. data/spec/mongoid/validatable/length_spec.rb +62 -86
  741. data/spec/mongoid/validatable/numericality_spec.rb +114 -23
  742. data/spec/mongoid/validatable/presence_spec.rb +149 -203
  743. data/spec/mongoid/validatable/uniqueness_spec.rb +632 -830
  744. data/spec/mongoid/validatable_spec.rb +71 -103
  745. data/spec/mongoid/warnings_spec.rb +5 -9
  746. data/spec/mongoid_spec.rb +25 -41
  747. data/spec/rails/controller_extension/controller_runtime_spec.rb +45 -49
  748. data/spec/rails/mongoid_spec.rb +15 -15
  749. data/spec/shared/lib/mrss/cluster_config.rb +5 -40
  750. data/spec/shared/lib/mrss/constraints.rb +7 -46
  751. data/spec/shared/lib/mrss/docker_runner.rb +10 -21
  752. data/spec/shared/lib/mrss/eg_config_utils.rb +0 -31
  753. data/spec/shared/lib/mrss/release/candidate.rb +18 -12
  754. data/spec/shared/lib/mrss/server_version_registry.rb +1 -5
  755. data/spec/shared/share/Dockerfile.erb +6 -8
  756. data/spec/shared/shlib/server.sh +0 -50
  757. data/spec/shared/shlib/set_env.sh +4 -10
  758. data/spec/spec_helper.rb +59 -32
  759. data/spec/support/authorization.rb +0 -1
  760. data/spec/support/client_registry.rb +1 -2
  761. data/spec/support/constraints.rb +8 -25
  762. data/spec/support/crypt/models.rb +164 -8
  763. data/spec/support/crypt.rb +6 -9
  764. data/spec/support/expectations.rb +21 -7
  765. data/spec/support/feature_sandbox.rb +1 -4
  766. data/spec/support/helpers.rb +0 -1
  767. data/spec/support/immutable_ids.rb +20 -21
  768. data/spec/support/macros.rb +1 -3
  769. data/spec/support/models/account.rb +4 -5
  770. data/spec/support/models/acolyte.rb +5 -5
  771. data/spec/support/models/actor.rb +3 -3
  772. data/spec/support/models/actress.rb +0 -1
  773. data/spec/support/models/address.rb +11 -11
  774. data/spec/support/models/address_component.rb +1 -1
  775. data/spec/support/models/address_number.rb +1 -1
  776. data/spec/support/models/agency.rb +1 -1
  777. data/spec/support/models/agent.rb +1 -1
  778. data/spec/support/models/album.rb +1 -2
  779. data/spec/support/models/alert.rb +1 -1
  780. data/spec/support/models/animal.rb +4 -5
  781. data/spec/support/models/answer.rb +1 -1
  782. data/spec/support/models/appointment.rb +2 -2
  783. data/spec/support/models/armrest.rb +0 -1
  784. data/spec/support/models/array_field.rb +0 -1
  785. data/spec/support/models/article.rb +0 -1
  786. data/spec/support/models/artist.rb +16 -12
  787. data/spec/support/models/artwork.rb +1 -1
  788. data/spec/support/models/audible_sound.rb +0 -1
  789. data/spec/support/models/audio.rb +2 -2
  790. data/spec/support/models/augmentation.rb +1 -2
  791. data/spec/support/models/author.rb +1 -1
  792. data/spec/support/models/baby.rb +1 -1
  793. data/spec/support/models/band.rb +0 -1
  794. data/spec/support/models/bar.rb +2 -2
  795. data/spec/support/models/basic.rb +0 -1
  796. data/spec/support/models/bed.rb +0 -1
  797. data/spec/support/models/big_palette.rb +0 -1
  798. data/spec/support/models/birthday.rb +3 -5
  799. data/spec/support/models/bolt.rb +0 -1
  800. data/spec/support/models/bomb.rb +1 -1
  801. data/spec/support/models/book.rb +1 -1
  802. data/spec/support/models/breed.rb +1 -1
  803. data/spec/support/models/browser.rb +1 -2
  804. data/spec/support/models/building.rb +0 -1
  805. data/spec/support/models/building_address.rb +1 -1
  806. data/spec/support/models/bus.rb +1 -1
  807. data/spec/support/models/business.rb +2 -2
  808. data/spec/support/models/callback_test.rb +1 -1
  809. data/spec/support/models/canvas.rb +5 -6
  810. data/spec/support/models/car.rb +0 -1
  811. data/spec/support/models/cat.rb +0 -2
  812. data/spec/support/models/catalog.rb +0 -1
  813. data/spec/support/models/category.rb +1 -1
  814. data/spec/support/models/child.rb +1 -1
  815. data/spec/support/models/child_doc.rb +5 -6
  816. data/spec/support/models/church.rb +1 -1
  817. data/spec/support/models/circle.rb +0 -1
  818. data/spec/support/models/circuit.rb +2 -2
  819. data/spec/support/models/circus.rb +0 -1
  820. data/spec/support/models/code.rb +2 -1
  821. data/spec/support/models/coding/pull_request.rb +0 -1
  822. data/spec/support/models/coding.rb +0 -1
  823. data/spec/support/models/comment.rb +0 -1
  824. data/spec/support/models/company.rb +0 -1
  825. data/spec/support/models/consumption_period.rb +0 -1
  826. data/spec/support/models/contextable_item.rb +1 -1
  827. data/spec/support/models/contractor.rb +1 -1
  828. data/spec/support/models/cookie.rb +0 -1
  829. data/spec/support/models/country_code.rb +2 -3
  830. data/spec/support/models/courier_job.rb +2 -2
  831. data/spec/support/models/cover.rb +0 -1
  832. data/spec/support/models/crate.rb +0 -1
  833. data/spec/support/models/customer.rb +0 -1
  834. data/spec/support/models/customer_address.rb +0 -1
  835. data/spec/support/models/deed.rb +1 -1
  836. data/spec/support/models/definition.rb +1 -1
  837. data/spec/support/models/delegating_patient.rb +0 -1
  838. data/spec/support/models/description.rb +0 -1
  839. data/spec/support/models/dictionary.rb +1 -1
  840. data/spec/support/models/division.rb +2 -2
  841. data/spec/support/models/doctor.rb +0 -1
  842. data/spec/support/models/dog.rb +2 -2
  843. data/spec/support/models/dokument.rb +1 -1
  844. data/spec/support/models/draft.rb +0 -1
  845. data/spec/support/models/dragon.rb +1 -1
  846. data/spec/support/models/driver.rb +2 -2
  847. data/spec/support/models/drug.rb +1 -1
  848. data/spec/support/models/dungeon.rb +1 -1
  849. data/spec/support/models/edit.rb +1 -1
  850. data/spec/support/models/email.rb +1 -1
  851. data/spec/support/models/employer.rb +1 -2
  852. data/spec/support/models/entry.rb +1 -1
  853. data/spec/support/models/eraser.rb +0 -1
  854. data/spec/support/models/even.rb +1 -1
  855. data/spec/support/models/event.rb +3 -4
  856. data/spec/support/models/exhibition.rb +1 -1
  857. data/spec/support/models/exhibitor.rb +1 -1
  858. data/spec/support/models/explosion.rb +1 -1
  859. data/spec/support/models/eye.rb +0 -1
  860. data/spec/support/models/eye_bowl.rb +2 -3
  861. data/spec/support/models/face.rb +2 -3
  862. data/spec/support/models/fanatic.rb +1 -1
  863. data/spec/support/models/favorite.rb +1 -1
  864. data/spec/support/models/filesystem.rb +1 -1
  865. data/spec/support/models/fire_hydrant.rb +1 -1
  866. data/spec/support/models/firefox.rb +0 -1
  867. data/spec/support/models/fish.rb +0 -1
  868. data/spec/support/models/folder.rb +0 -2
  869. data/spec/support/models/folder_item.rb +1 -3
  870. data/spec/support/models/fruits.rb +9 -5
  871. data/spec/support/models/game.rb +3 -4
  872. data/spec/support/models/ghost.rb +0 -1
  873. data/spec/support/models/guitar.rb +0 -1
  874. data/spec/support/models/hole.rb +0 -1
  875. data/spec/support/models/home.rb +1 -1
  876. data/spec/support/models/house.rb +2 -2
  877. data/spec/support/models/html_writer.rb +0 -1
  878. data/spec/support/models/id_key.rb +3 -3
  879. data/spec/support/models/idnodef.rb +0 -1
  880. data/spec/support/models/image.rb +0 -1
  881. data/spec/support/models/implant.rb +1 -2
  882. data/spec/support/models/instrument.rb +2 -3
  883. data/spec/support/models/item.rb +2 -2
  884. data/spec/support/models/jar.rb +1 -2
  885. data/spec/support/models/kaleidoscope.rb +1 -1
  886. data/spec/support/models/kangaroo.rb +1 -1
  887. data/spec/support/models/label.rb +2 -2
  888. data/spec/support/models/language.rb +1 -1
  889. data/spec/support/models/lat_lng.rb +0 -1
  890. data/spec/support/models/league.rb +1 -1
  891. data/spec/support/models/learner.rb +0 -1
  892. data/spec/support/models/line_item.rb +1 -1
  893. data/spec/support/models/location.rb +1 -1
  894. data/spec/support/models/login.rb +1 -2
  895. data/spec/support/models/manufacturer.rb +0 -1
  896. data/spec/support/models/meat.rb +1 -1
  897. data/spec/support/models/membership.rb +1 -1
  898. data/spec/support/models/message.rb +1 -2
  899. data/spec/support/models/minim.rb +0 -1
  900. data/spec/support/models/mixed_drink.rb +1 -1
  901. data/spec/support/models/modifier_models.rb +25 -0
  902. data/spec/support/models/mop.rb +0 -1
  903. data/spec/support/models/movie.rb +1 -1
  904. data/spec/support/models/my_hash.rb +1 -2
  905. data/spec/support/models/name.rb +4 -5
  906. data/spec/support/models/name_only.rb +0 -1
  907. data/spec/support/models/node.rb +1 -1
  908. data/spec/support/models/note.rb +3 -3
  909. data/spec/support/models/nut.rb +0 -1
  910. data/spec/support/models/odd.rb +1 -1
  911. data/spec/support/models/order.rb +1 -1
  912. data/spec/support/models/ordered_post.rb +1 -1
  913. data/spec/support/models/ordered_preference.rb +1 -1
  914. data/spec/support/models/oscar.rb +1 -1
  915. data/spec/support/models/other_owner_object.rb +0 -1
  916. data/spec/support/models/override.rb +3 -11
  917. data/spec/support/models/ownable.rb +1 -1
  918. data/spec/support/models/owner.rb +1 -1
  919. data/spec/support/models/pack.rb +0 -1
  920. data/spec/support/models/page.rb +3 -3
  921. data/spec/support/models/page_question.rb +1 -1
  922. data/spec/support/models/palette.rb +2 -2
  923. data/spec/support/models/parent.rb +2 -2
  924. data/spec/support/models/parent_doc.rb +1 -1
  925. data/spec/support/models/passport.rb +0 -1
  926. data/spec/support/models/patient.rb +2 -2
  927. data/spec/support/models/pdf_writer.rb +0 -1
  928. data/spec/support/models/pencil.rb +0 -1
  929. data/spec/support/models/person.rb +32 -28
  930. data/spec/support/models/pet.rb +1 -1
  931. data/spec/support/models/pet_owner.rb +1 -1
  932. data/spec/support/models/phone.rb +1 -2
  933. data/spec/support/models/piano.rb +0 -1
  934. data/spec/support/models/pizza.rb +1 -1
  935. data/spec/support/models/player.rb +5 -5
  936. data/spec/support/models/post.rb +12 -12
  937. data/spec/support/models/post_genre.rb +1 -1
  938. data/spec/support/models/powerup.rb +1 -2
  939. data/spec/support/models/preference.rb +2 -2
  940. data/spec/support/models/princess.rb +1 -1
  941. data/spec/support/models/product.rb +3 -3
  942. data/spec/support/models/profile.rb +2 -1
  943. data/spec/support/models/pronunciation.rb +1 -1
  944. data/spec/support/models/pub.rb +2 -2
  945. data/spec/support/models/publication/encyclopedia.rb +0 -1
  946. data/spec/support/models/publication/review.rb +0 -1
  947. data/spec/support/models/publication.rb +0 -1
  948. data/spec/support/models/purchase.rb +1 -1
  949. data/spec/support/models/purchased_item.rb +1 -1
  950. data/spec/support/models/question.rb +2 -2
  951. data/spec/support/models/quiz.rb +1 -1
  952. data/spec/support/models/rating.rb +1 -1
  953. data/spec/support/models/record.rb +2 -3
  954. data/spec/support/models/registry.rb +1 -1
  955. data/spec/support/models/role.rb +1 -1
  956. data/spec/support/models/root_category.rb +1 -1
  957. data/spec/support/models/sandbox/app/models/app_models_message.rb +0 -1
  958. data/spec/support/models/sandbox/lib/models/lib_models_message.rb +0 -1
  959. data/spec/support/models/sandbox/sandbox_message.rb +0 -1
  960. data/spec/support/models/sandbox/sandbox_user.rb +0 -1
  961. data/spec/support/models/sandbox/subdir/sandbox_comment.rb +0 -1
  962. data/spec/support/models/sandwich.rb +1 -1
  963. data/spec/support/models/scheduler.rb +0 -1
  964. data/spec/support/models/school.rb +0 -1
  965. data/spec/support/models/scribe.rb +1 -1
  966. data/spec/support/models/sealer.rb +0 -1
  967. data/spec/support/models/seat.rb +0 -1
  968. data/spec/support/models/seo.rb +1 -1
  969. data/spec/support/models/series.rb +1 -1
  970. data/spec/support/models/server.rb +1 -1
  971. data/spec/support/models/service.rb +2 -2
  972. data/spec/support/models/shape.rb +3 -3
  973. data/spec/support/models/shelf.rb +1 -1
  974. data/spec/support/models/shield.rb +2 -2
  975. data/spec/support/models/shipment_address.rb +0 -1
  976. data/spec/support/models/shipping_container.rb +1 -1
  977. data/spec/support/models/shipping_pack.rb +0 -1
  978. data/spec/support/models/shirt.rb +0 -1
  979. data/spec/support/models/shop.rb +2 -2
  980. data/spec/support/models/short_agent.rb +0 -1
  981. data/spec/support/models/short_quiz.rb +1 -1
  982. data/spec/support/models/simple.rb +1 -1
  983. data/spec/support/models/slave.rb +1 -1
  984. data/spec/support/models/song.rb +1 -2
  985. data/spec/support/models/sound.rb +2 -2
  986. data/spec/support/models/spacer.rb +0 -1
  987. data/spec/support/models/square.rb +0 -1
  988. data/spec/support/models/staff.rb +0 -1
  989. data/spec/support/models/store_as_dup_test1.rb +2 -2
  990. data/spec/support/models/store_as_dup_test2.rb +1 -1
  991. data/spec/support/models/store_as_dup_test3.rb +2 -2
  992. data/spec/support/models/store_as_dup_test4.rb +1 -1
  993. data/spec/support/models/strategy.rb +0 -1
  994. data/spec/support/models/student.rb +1 -2
  995. data/spec/support/models/sub_item.rb +0 -1
  996. data/spec/support/models/subscription.rb +2 -2
  997. data/spec/support/models/survey.rb +2 -2
  998. data/spec/support/models/symptom.rb +2 -2
  999. data/spec/support/models/system_role.rb +2 -3
  1000. data/spec/support/models/tag.rb +2 -2
  1001. data/spec/support/models/target.rb +1 -1
  1002. data/spec/support/models/template.rb +1 -1
  1003. data/spec/support/models/thing.rb +1 -1
  1004. data/spec/support/models/threadlocker.rb +0 -1
  1005. data/spec/support/models/title.rb +0 -1
  1006. data/spec/support/models/tool.rb +3 -3
  1007. data/spec/support/models/topping.rb +1 -1
  1008. data/spec/support/models/toy.rb +0 -1
  1009. data/spec/support/models/track.rb +1 -1
  1010. data/spec/support/models/translation.rb +1 -1
  1011. data/spec/support/models/tree.rb +2 -3
  1012. data/spec/support/models/truck.rb +0 -1
  1013. data/spec/support/models/updatable.rb +0 -1
  1014. data/spec/support/models/user.rb +3 -4
  1015. data/spec/support/models/user_account.rb +3 -3
  1016. data/spec/support/models/validation_callback.rb +4 -4
  1017. data/spec/support/models/vehicle.rb +2 -3
  1018. data/spec/support/models/version.rb +1 -1
  1019. data/spec/support/models/vertex.rb +0 -1
  1020. data/spec/support/models/vet_visit.rb +1 -1
  1021. data/spec/support/models/video.rb +2 -2
  1022. data/spec/support/models/video_game.rb +0 -1
  1023. data/spec/support/models/washer.rb +0 -1
  1024. data/spec/support/models/weapon.rb +1 -2
  1025. data/spec/support/models/wiki_page.rb +2 -3
  1026. data/spec/support/models/word.rb +2 -2
  1027. data/spec/support/models/word_origin.rb +1 -2
  1028. data/spec/support/models/writer.rb +3 -3
  1029. data/spec/support/rails_mock.rb +7 -12
  1030. data/spec/support/shared/time.rb +0 -1
  1031. data/spec/support/sinatra_mock.rb +4 -2
  1032. data/spec/support/spec_config.rb +19 -18
  1033. metadata +64 -2
@@ -17,10 +17,10 @@ class SearchIndexHelper
17
17
 
18
18
  # Wait for all of the indexes with the given names to be ready; then return
19
19
  # the list of index definitions corresponding to those names.
20
- def wait_for(*names, &condition)
20
+ def wait_for(*names, timeout: 300, &condition)
21
21
  names.flatten!
22
22
 
23
- timeboxed_wait do
23
+ timeboxed_wait(max: timeout) do
24
24
  result = collection.search_indexes
25
25
  return filter_results(result, names) if names.all? { |name| ready?(result, name, &condition) }
26
26
  end
@@ -28,7 +28,7 @@ class SearchIndexHelper
28
28
 
29
29
  # Wait until all of the indexes with the given names are absent from the
30
30
  # search index list.
31
- def wait_for_absense_of(*names)
31
+ def wait_for_absence_of(*names)
32
32
  names.flatten.each do |name|
33
33
  timeboxed_wait do
34
34
  break if collection.search_indexes(name: name).empty?
@@ -36,6 +36,14 @@ class SearchIndexHelper
36
36
  end
37
37
  end
38
38
 
39
+ # Atlas normalizes latestDefinition by adding "fields"=>{} inside mappings
40
+ # even when no fields were declared. Strip it before comparing against specs.
41
+ def normalize_definition(defn)
42
+ return defn unless defn.is_a?(Hash) && defn['mappings'].is_a?(Hash)
43
+
44
+ defn.merge('mappings' => defn['mappings'].reject { |k, v| k.to_s == 'fields' && v == {} })
45
+ end
46
+
39
47
  private
40
48
 
41
49
  def timeboxed_wait(step: 5, max: 300)
@@ -57,29 +65,34 @@ class SearchIndexHelper
57
65
  end
58
66
 
59
67
  def filter_results(result, names)
60
- result.select { |index| names.include?(index['name']) }
68
+ names.filter_map { |name| result.find { |index| index['name'] == name } }
61
69
  end
62
70
  end
63
71
 
64
- # rubocop:disable RSpec/MultipleMemoizedHelpers
65
72
  describe Mongoid::SearchIndexable do
66
- before do
67
- skip "#{described_class} requires at Atlas environment (set ATLAS_URI)" if ENV['ATLAS_URI'].nil?
68
- end
73
+ # Unit tests — no Atlas connection required.
69
74
 
70
- let(:model) do
71
- Class.new do
72
- include Mongoid::Document
73
- store_in collection: BSON::ObjectId.new.to_s
75
+ describe '.search_index_specs' do
76
+ let(:model) do
77
+ Class.new do
78
+ include Mongoid::Document
74
79
 
75
- search_index mappings: { dynamic: false }
76
- search_index :with_dynamic_mappings, mappings: { dynamic: true }
80
+ store_in collection: BSON::ObjectId.new.to_s
81
+ search_index mappings: { dynamic: false }
82
+ search_index :with_dynamic_mappings, mappings: { dynamic: true }
83
+ end
77
84
  end
78
- end
79
85
 
80
- let(:helper) { SearchIndexHelper.new(model) }
86
+ let(:vector_model) do
87
+ Class.new do
88
+ include Mongoid::Document
89
+
90
+ store_in collection: BSON::ObjectId.new.to_s
91
+ vector_search_index fields: [ { type: 'vector', path: 'embedding', numDimensions: 1536, similarity: 'cosine' } ]
92
+ vector_search_index :named_vector_index, fields: [ { type: 'vector', path: 'other', numDimensions: 768, similarity: 'euclidean' } ]
93
+ end
94
+ end
81
95
 
82
- describe '.search_index_specs' do
83
96
  context 'when no search indexes have been defined' do
84
97
  it 'has no search index specs' do
85
98
  expect(Person.search_index_specs).to be_empty
@@ -88,60 +101,738 @@ describe Mongoid::SearchIndexable do
88
101
 
89
102
  context 'when search indexes have been defined' do
90
103
  it 'has search index specs' do
91
- expect(model.search_index_specs).to be == [
104
+ expect(model.search_index_specs).to eq [
92
105
  { definition: { mappings: { dynamic: false } } },
93
106
  { name: 'with_dynamic_mappings', definition: { mappings: { dynamic: true } } }
94
107
  ]
95
108
  end
96
109
  end
110
+
111
+ context 'when vector search indexes have been defined' do
112
+ it 'has search index specs with vectorSearch type' do
113
+ expect(vector_model.search_index_specs).to eq [
114
+ { type: 'vectorSearch', definition: { fields: [ { type: 'vector', path: 'embedding', numDimensions: 1536, similarity: 'cosine' } ] } },
115
+ { type: 'vectorSearch', name: 'named_vector_index', definition: { fields: [ { type: 'vector', path: 'other', numDimensions: 768, similarity: 'euclidean' } ] } }
116
+ ]
117
+ end
118
+ end
119
+ end
120
+
121
+ describe '.vector_search_index' do
122
+ let(:vector_model) do
123
+ Class.new do
124
+ include Mongoid::Document
125
+
126
+ store_in collection: BSON::ObjectId.new.to_s
127
+ field :embedding, type: Array
128
+ vector_search_index fields: [ { type: 'vector', path: 'embedding', numDimensions: 3, similarity: 'cosine' } ]
129
+ end
130
+ end
131
+
132
+ it 'adds a vector_search_score field to the model' do
133
+ expect(vector_model.fields).to have_key('vector_search_score')
134
+ end
135
+
136
+ it 'marks vector_search_score as readonly' do
137
+ expect(vector_model.readonly_attributes).to include('vector_search_score')
138
+ end
139
+
140
+ it 'defines vector_search_score only once across multiple declarations' do
141
+ model = Class.new do
142
+ include Mongoid::Document
143
+
144
+ store_in collection: BSON::ObjectId.new.to_s
145
+ vector_search_index :idx1, fields: [ { type: 'vector', path: 'a', numDimensions: 3, similarity: 'cosine' } ]
146
+ vector_search_index :idx2, fields: [ { type: 'vector', path: 'b', numDimensions: 3, similarity: 'cosine' } ]
147
+ end
148
+ expect(model.fields.count { |k, _| k == 'vector_search_score' }).to eq 1
149
+ end
150
+
151
+ context 'when only a regular search_index is declared' do
152
+ it 'does not add vector_search_score' do
153
+ model = Class.new do
154
+ include Mongoid::Document
155
+
156
+ store_in collection: BSON::ObjectId.new.to_s
157
+ search_index mappings: { dynamic: true }
158
+ end
159
+ expect(model.fields).not_to have_key('vector_search_score')
160
+ end
161
+ end
162
+ end
163
+
164
+ describe '.vector_search argument validation' do
165
+ let(:no_index_model) do
166
+ Class.new do
167
+ include Mongoid::Document
168
+
169
+ store_in collection: BSON::ObjectId.new.to_s
170
+ end
171
+ end
172
+
173
+ let(:multi_index_model) do
174
+ Class.new do
175
+ include Mongoid::Document
176
+
177
+ store_in collection: BSON::ObjectId.new.to_s
178
+ vector_search_index :idx1, fields: [ { type: 'vector', path: 'a', numDimensions: 3, similarity: 'cosine' } ]
179
+ vector_search_index :idx2, fields: [ { type: 'vector', path: 'b', numDimensions: 3, similarity: 'cosine' } ]
180
+ end
181
+ end
182
+
183
+ let(:single_index_model) do
184
+ Class.new do
185
+ include Mongoid::Document
186
+
187
+ store_in collection: BSON::ObjectId.new.to_s
188
+ field :embedding, type: Array
189
+ vector_search_index fields: [ { type: 'vector', path: 'embedding', numDimensions: 3, similarity: 'cosine' } ]
190
+ end
191
+ end
192
+
193
+ it 'raises ArgumentError when no vector search indexes are declared' do
194
+ expect { no_index_model.vector_search([ 0.1, 0.2, 0.3 ]) }
195
+ .to raise_error(ArgumentError, /No vector search indexes declared/)
196
+ end
197
+
198
+ it 'raises ArgumentError when multiple indexes exist and none is specified' do
199
+ expect { multi_index_model.vector_search([ 0.1, 0.2, 0.3 ]) }
200
+ .to raise_error(ArgumentError, /multiple vector search indexes/)
201
+ end
202
+
203
+ it 'raises ArgumentError when the specified index name does not exist' do
204
+ expect { single_index_model.vector_search([ 0.1, 0.2, 0.3 ], index: 'nonexistent') }
205
+ .to raise_error(ArgumentError, /No vector search index 'nonexistent'/)
206
+ end
207
+ end
208
+
209
+ describe '#vector_search argument validation' do
210
+ let(:model) do
211
+ Class.new do
212
+ include Mongoid::Document
213
+
214
+ store_in collection: BSON::ObjectId.new.to_s
215
+ field :embedding, type: Array
216
+ vector_search_index fields: [ { type: 'vector', path: 'embedding', numDimensions: 3, similarity: 'cosine' } ]
217
+ end
218
+ end
219
+
220
+ it 'raises ArgumentError when the vector field is nil' do
221
+ doc = model.new(embedding: nil)
222
+ expect { doc.vector_search }.to raise_error(ArgumentError, /embedding is nil/)
223
+ end
224
+ end
225
+
226
+ describe '#auto_embed_search argument validation' do
227
+ let(:model) do
228
+ Class.new do
229
+ include Mongoid::Document
230
+
231
+ store_in collection: BSON::ObjectId.new.to_s
232
+ auto_embed_field :description, model: 'voyage-4'
233
+ end
234
+ end
235
+
236
+ it 'raises ArgumentError when the text field is nil' do
237
+ doc = model.new(description: nil)
238
+ expect { doc.auto_embed_search }.to raise_error(ArgumentError, /description is nil/)
239
+ end
240
+
241
+ it 'raises ArgumentError when no auto-embed indexes are declared on the model' do
242
+ bare_model = Class.new do
243
+ include Mongoid::Document
244
+
245
+ store_in collection: BSON::ObjectId.new.to_s
246
+ field :description, type: String
247
+ end
248
+ doc = bare_model.new(description: 'hello')
249
+ expect { doc.auto_embed_search }.to raise_error(ArgumentError, /No auto-embed indexes declared/)
250
+ end
251
+
252
+ it 'raises ArgumentError when multiple indexes exist and none is specified' do
253
+ multi_model = Class.new do
254
+ include Mongoid::Document
255
+
256
+ store_in collection: BSON::ObjectId.new.to_s
257
+ auto_embed_field :description, model: 'voyage-4', index: :idx1
258
+ auto_embed_field :summary, model: 'voyage-4', index: :idx2
259
+ end
260
+ doc = multi_model.new(description: 'hello')
261
+ expect { doc.auto_embed_search }.to raise_error(ArgumentError, /multiple auto-embed indexes/)
262
+ end
263
+ end
264
+
265
+ describe '.auto_embed_search argument validation' do
266
+ let(:no_index_model) do
267
+ Class.new do
268
+ include Mongoid::Document
269
+
270
+ store_in collection: BSON::ObjectId.new.to_s
271
+ end
272
+ end
273
+
274
+ let(:single_embed_model) do
275
+ Class.new do
276
+ include Mongoid::Document
277
+
278
+ store_in collection: BSON::ObjectId.new.to_s
279
+ auto_embed_field :description, model: 'voyage-4'
280
+ end
281
+ end
282
+
283
+ let(:multi_embed_model) do
284
+ Class.new do
285
+ include Mongoid::Document
286
+
287
+ store_in collection: BSON::ObjectId.new.to_s
288
+ auto_embed_field :description, model: 'voyage-4', index: :idx1
289
+ auto_embed_field :summary, model: 'voyage-4', index: :idx2
290
+ end
291
+ end
292
+
293
+ it 'raises ArgumentError when no auto-embed indexes are declared' do
294
+ expect { no_index_model.auto_embed_search('hello') }
295
+ .to raise_error(ArgumentError, /No auto-embed indexes declared/)
296
+ end
297
+
298
+ it 'raises ArgumentError when multiple indexes exist and none is specified' do
299
+ expect { multi_embed_model.auto_embed_search('hello') }
300
+ .to raise_error(ArgumentError, /multiple auto-embed indexes/)
301
+ end
302
+
303
+ it 'raises ArgumentError when the specified index name does not exist' do
304
+ expect { single_embed_model.auto_embed_search('hello', index: 'nonexistent') }
305
+ .to raise_error(ArgumentError, /No auto-embed index 'nonexistent'/)
306
+ end
307
+
308
+ it 'raises ArgumentError when a model with only vector (non-autoEmbed) indexes is used' do
309
+ model = Class.new do
310
+ include Mongoid::Document
311
+
312
+ store_in collection: BSON::ObjectId.new.to_s
313
+ vector_search_index fields: [ { type: 'vector', path: 'emb', numDimensions: 3, similarity: 'cosine' } ]
314
+ end
315
+ expect { model.auto_embed_search('hello') }
316
+ .to raise_error(ArgumentError, /No auto-embed indexes declared/)
317
+ end
318
+ end
319
+
320
+ describe '.vector_search_index with flat indexingMethod' do
321
+ it 'raises ArgumentError when flat index includes hnswOptions' do
322
+ expect do
323
+ Class.new do
324
+ include Mongoid::Document
325
+
326
+ store_in collection: BSON::ObjectId.new.to_s
327
+ vector_search_index fields: [
328
+ {
329
+ type: 'vector',
330
+ path: 'embedding',
331
+ numDimensions: 3,
332
+ similarity: 'cosine',
333
+ indexingMethod: 'flat',
334
+ hnswOptions: { m: 16, efConstruction: 150 }
335
+ }
336
+ ]
337
+ end
338
+ end.to raise_error(ArgumentError, /hnswOptions is only supported with indexingMethod: hnsw/)
339
+ end
340
+
341
+ it 'does not raise when flat index has no hnswOptions' do
342
+ expect do
343
+ Class.new do
344
+ include Mongoid::Document
345
+
346
+ store_in collection: BSON::ObjectId.new.to_s
347
+ vector_search_index fields: [
348
+ {
349
+ type: 'vector',
350
+ path: 'embedding',
351
+ numDimensions: 3,
352
+ similarity: 'cosine',
353
+ indexingMethod: 'flat'
354
+ }
355
+ ]
356
+ end
357
+ end.not_to raise_error
358
+ end
359
+
360
+ it 'does not raise when hnsw index has hnswOptions' do
361
+ expect do
362
+ Class.new do
363
+ include Mongoid::Document
364
+
365
+ store_in collection: BSON::ObjectId.new.to_s
366
+ vector_search_index fields: [
367
+ {
368
+ type: 'vector',
369
+ path: 'embedding',
370
+ numDimensions: 3,
371
+ similarity: 'cosine',
372
+ indexingMethod: 'hnsw',
373
+ hnswOptions: { m: 16, efConstruction: 150 }
374
+ }
375
+ ]
376
+ end
377
+ end.not_to raise_error
378
+ end
379
+ end
380
+
381
+ describe '.vector_search pipeline construction' do
382
+ let(:model) do
383
+ Class.new do
384
+ include Mongoid::Document
385
+
386
+ store_in collection: BSON::ObjectId.new.to_s
387
+ field :embedding, type: Array
388
+ vector_search_index fields: [ { type: 'vector', path: 'embedding', numDimensions: 3, similarity: 'cosine' } ]
389
+ end
390
+ end
391
+
392
+ let(:fake_collection) { instance_double(Mongo::Collection) }
393
+ let(:fake_cursor) { double(map: []) }
394
+
395
+ before do
396
+ allow(model).to receive(:collection).and_return(fake_collection)
397
+ allow(fake_collection).to receive(:aggregate).and_return(fake_cursor)
398
+ end
399
+
400
+ it 'includes numCandidates by default' do
401
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
402
+ vs = pipeline.find { |s| s['$vectorSearch'] }
403
+ expect(vs['$vectorSearch']).to have_key('numCandidates')
404
+ fake_cursor
405
+ end
406
+
407
+ model.vector_search([ 0.1, 0.2, 0.3 ], limit: 5)
408
+ end
409
+
410
+ it 'omits numCandidates and sends exact: true when exact: true' do
411
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
412
+ vs = pipeline.find { |s| s['$vectorSearch'] }
413
+ expect(vs['$vectorSearch']).not_to have_key('numCandidates')
414
+ expect(vs['$vectorSearch']['exact']).to be true
415
+ fake_cursor
416
+ end
417
+
418
+ model.vector_search([ 0.1, 0.2, 0.3 ], exact: true)
419
+ end
420
+
421
+ it 'uses limit * 10 as the default numCandidates' do
422
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
423
+ vs = pipeline.find { |s| s['$vectorSearch'] }
424
+ expect(vs['$vectorSearch']['numCandidates']).to eq(50)
425
+ fake_cursor
426
+ end
427
+
428
+ model.vector_search([ 0.1, 0.2, 0.3 ], limit: 5)
429
+ end
430
+ end
431
+
432
+ describe '#vector_search pipeline construction' do
433
+ let(:model) do
434
+ Class.new do
435
+ include Mongoid::Document
436
+
437
+ store_in collection: BSON::ObjectId.new.to_s
438
+ field :embedding, type: Array
439
+ vector_search_index fields: [ { type: 'vector', path: 'embedding', numDimensions: 3, similarity: 'cosine' } ]
440
+ end
441
+ end
442
+
443
+ let(:fake_collection) { instance_double(Mongo::Collection) }
444
+ let(:fake_cursor) { double(map: []) }
445
+ let(:doc) { model.new(embedding: [ 0.1, 0.2, 0.3 ]) }
446
+
447
+ before do
448
+ allow(model).to receive(:collection).and_return(fake_collection)
449
+ allow(fake_collection).to receive(:aggregate).and_return(fake_cursor)
450
+ end
451
+
452
+ it 'omits numCandidates and sends exact: true when exact: true' do
453
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
454
+ vs = pipeline.find { |s| s['$vectorSearch'] }
455
+ expect(vs['$vectorSearch']).not_to have_key('numCandidates')
456
+ expect(vs['$vectorSearch']['exact']).to be true
457
+ fake_cursor
458
+ end
459
+
460
+ doc.vector_search(exact: true)
461
+ end
462
+
463
+ it 'passes limit + 1 to $vectorSearch so the post-filter never short-counts' do
464
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
465
+ vs = pipeline.find { |s| s['$vectorSearch'] }
466
+ expect(vs['$vectorSearch']['limit']).to eq 6
467
+ fake_cursor
468
+ end
469
+
470
+ doc.vector_search(limit: 5)
471
+ end
472
+
473
+ it 'does not use filter in $vectorSearch for self-exclusion' do
474
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
475
+ vs = pipeline.find { |s| s['$vectorSearch'] }
476
+ expect(vs['$vectorSearch']).not_to have_key('filter')
477
+ fake_cursor
478
+ end
479
+
480
+ doc.vector_search(limit: 5)
481
+ end
482
+
483
+ it 'adds a $match stage after $vectorSearch to exclude self' do
484
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
485
+ match = pipeline.find { |s| s['$match'] }
486
+ expect(match).to eq({ '$match' => { '_id' => { '$ne' => doc.id } } })
487
+ fake_cursor
488
+ end
489
+
490
+ doc.vector_search(limit: 5)
491
+ end
492
+
493
+ it 'adds a $limit stage after $match to cap results at the requested limit' do
494
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
495
+ match_idx = pipeline.index { |s| s['$match'] }
496
+ limit_stage = pipeline[match_idx + 1]
497
+ expect(limit_stage).to eq({ '$limit' => 5 })
498
+ fake_cursor
499
+ end
500
+
501
+ doc.vector_search(limit: 5)
502
+ end
503
+
504
+ it 'passes a user-provided filter through to $vectorSearch' do
505
+ user_filter = { 'status' => 'published' }
506
+
507
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
508
+ vs = pipeline.find { |s| s['$vectorSearch'] }
509
+ expect(vs['$vectorSearch']['filter']).to eq user_filter
510
+ fake_cursor
511
+ end
512
+
513
+ doc.vector_search(limit: 5, filter: user_filter)
514
+ end
97
515
  end
98
516
 
99
- context 'when needing to first create search indexes' do
100
- let(:requested_definitions) { model.search_index_specs.map { |spec| spec[:definition].with_indifferent_access } }
101
- let(:index_names) { model.create_search_indexes }
102
- let(:actual_indexes) { helper.wait_for(*index_names) }
103
- let(:actual_definitions) { actual_indexes.map { |i| i['latestDefinition'] } }
517
+ describe '#auto_embed_search pipeline construction' do
518
+ let(:model) do
519
+ Class.new do
520
+ include Mongoid::Document
521
+
522
+ store_in collection: BSON::ObjectId.new.to_s
523
+ auto_embed_field :description, model: 'voyage-4'
524
+ end
525
+ end
526
+
527
+ let(:fake_collection) { instance_double(Mongo::Collection) }
528
+ let(:fake_cursor) { double(map: []) }
529
+ let(:doc) { model.new(description: 'hello world') }
530
+
531
+ before do
532
+ allow(model).to receive(:collection).and_return(fake_collection)
533
+ allow(fake_collection).to receive(:aggregate).and_return(fake_cursor)
534
+ end
104
535
 
105
- describe '.create_search_indexes' do
106
- it 'creates the indexes' do
107
- expect(actual_definitions).to be == requested_definitions
536
+ it 'passes limit + 1 to $vectorSearch so the post-filter never short-counts' do
537
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
538
+ vs = pipeline.find { |s| s['$vectorSearch'] }
539
+ expect(vs['$vectorSearch']['limit']).to eq 6
540
+ fake_cursor
108
541
  end
542
+
543
+ doc.auto_embed_search(limit: 5)
109
544
  end
110
545
 
111
- describe '.search_indexes' do
112
- before { actual_indexes } # wait for the indices to be created
546
+ it 'does not use filter in $vectorSearch for self-exclusion' do
547
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
548
+ vs = pipeline.find { |s| s['$vectorSearch'] }
549
+ expect(vs['$vectorSearch']).not_to have_key('filter')
550
+ fake_cursor
551
+ end
113
552
 
114
- let(:queried_definitions) { model.search_indexes.map { |i| i['latestDefinition'] } }
553
+ doc.auto_embed_search(limit: 5)
554
+ end
115
555
 
116
- it 'queries the available search indexes' do
117
- expect(queried_definitions).to be == requested_definitions
556
+ it 'adds a $match stage after $vectorSearch to exclude self' do
557
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
558
+ match = pipeline.find { |s| s['$match'] }
559
+ expect(match).to eq({ '$match' => { '_id' => { '$ne' => doc.id } } })
560
+ fake_cursor
118
561
  end
562
+
563
+ doc.auto_embed_search(limit: 5)
119
564
  end
120
565
 
121
- describe '.remove_search_index' do
122
- let(:target_index) { actual_indexes.first }
566
+ it 'adds a $limit stage after $match to cap results at the requested limit' do
567
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
568
+ match_idx = pipeline.index { |s| s['$match'] }
569
+ limit_stage = pipeline[match_idx + 1]
570
+ expect(limit_stage).to eq({ '$limit' => 5 })
571
+ fake_cursor
572
+ end
573
+
574
+ doc.auto_embed_search(limit: 5)
575
+ end
576
+
577
+ it 'passes a user-provided filter through to $vectorSearch' do
578
+ user_filter = { 'status' => 'published' }
579
+
580
+ expect(fake_collection).to receive(:aggregate) do |pipeline|
581
+ vs = pipeline.find { |s| s['$vectorSearch'] }
582
+ expect(vs['$vectorSearch']['filter']).to eq user_filter
583
+ fake_cursor
584
+ end
585
+
586
+ doc.auto_embed_search(limit: 5, filter: user_filter)
587
+ end
588
+ end
589
+
590
+ # Atlas integration tests — skipped when ATLAS_URI is not set.
591
+
592
+ context 'Atlas integration' do
593
+ before do
594
+ skip "#{described_class} requires an Atlas environment (set ATLAS_URI)" if ENV['ATLAS_URI'].nil?
595
+ end
596
+
597
+ let(:model) do
598
+ Class.new do
599
+ include Mongoid::Document
600
+
601
+ store_in collection: BSON::ObjectId.new.to_s
602
+ search_index mappings: { dynamic: false }
603
+ search_index :with_dynamic_mappings, mappings: { dynamic: true }
604
+ end
605
+ end
606
+
607
+ let(:helper) { SearchIndexHelper.new(model) }
608
+
609
+ context 'when needing to first create search indexes' do
610
+ let(:requested_definitions) { model.search_index_specs.map { |spec| spec[:definition].with_indifferent_access } }
611
+ let(:index_names) { model.create_search_indexes }
612
+ let(:actual_indexes) { helper.wait_for(*index_names) }
613
+ let(:actual_definitions) { actual_indexes.map { |i| helper.normalize_definition(i['latestDefinition']) } }
614
+
615
+ describe '.create_search_indexes' do
616
+ it 'creates the indexes' do
617
+ expect(actual_definitions).to eq requested_definitions
618
+ end
619
+ end
620
+
621
+ describe '.search_indexes' do
622
+ before { actual_indexes } # wait for the indices to be created
623
+
624
+ let(:queried_definitions) { model.search_indexes.map { |i| helper.normalize_definition(i['latestDefinition']) } }
625
+
626
+ it 'queries the available search indexes' do
627
+ expect(queried_definitions).to match_array(requested_definitions)
628
+ end
629
+ end
630
+
631
+ describe '.remove_search_index' do
632
+ let(:target_index) { actual_indexes.first }
633
+
634
+ before do
635
+ model.remove_search_index id: target_index['id']
636
+ helper.wait_for_absence_of target_index['name']
637
+ end
638
+
639
+ it 'removes the requested index' do
640
+ expect(model.search_indexes(id: target_index['id'])).to be_empty
641
+ end
642
+ end
643
+
644
+ describe '.remove_search_indexes' do
645
+ before do
646
+ actual_indexes # wait for the indexes to be created
647
+ model.remove_search_indexes
648
+ helper.wait_for_absence_of(actual_indexes.map { |i| i['name'] })
649
+ end
650
+
651
+ it 'removes the indexes' do
652
+ expect(model.search_indexes).to be_empty
653
+ end
654
+ end
655
+ end
656
+
657
+ context 'with a vector search index' do
658
+ let(:vector_model) do
659
+ Class.new do
660
+ include Mongoid::Document
661
+
662
+ store_in collection: BSON::ObjectId.new.to_s
663
+ field :embedding, type: Array
664
+ vector_search_index fields: [ { type: 'vector', path: 'embedding', numDimensions: 3, similarity: 'cosine' } ]
665
+ end
666
+ end
667
+
668
+ let(:vector_helper) { SearchIndexHelper.new(vector_model) }
669
+
670
+ # Three orthogonal unit vectors as a minimal, predictable dataset.
671
+ let(:doc_a) { vector_model.create!(embedding: [ 1.0, 0.0, 0.0 ]) }
672
+ let(:doc_b) { vector_model.create!(embedding: [ 0.0, 1.0, 0.0 ]) }
673
+ let(:doc_c) { vector_model.create!(embedding: [ 0.0, 0.0, 1.0 ]) }
123
674
 
124
675
  before do
125
- model.remove_search_index id: target_index['id']
126
- helper.wait_for_absense_of target_index['name']
676
+ vector_helper # force collection to be dropped, created
677
+
678
+ # once the collection has been recreated, populate it with documents
679
+ doc_a
680
+ doc_b
681
+ doc_c
682
+
683
+ # prepare the search indexes
684
+ names = vector_model.create_search_indexes
685
+ vector_helper.wait_for(*names)
127
686
  end
128
687
 
129
- it 'removes the requested index' do
130
- expect(model.search_indexes(id: target_index['id'])).to be_empty
688
+ describe '.vector_search' do
689
+ let(:results) { vector_model.vector_search([ 1.0, 0.0, 0.0 ], limit: 3) }
690
+
691
+ it 'returns Mongoid document instances' do
692
+ expect(results).to all(be_a(vector_model))
693
+ end
694
+
695
+ it 'populates vector_search_score on each result' do
696
+ expect(results.map(&:vector_search_score)).to all(be_a(Float))
697
+ end
698
+
699
+ it 'excludes the vector field from results' do
700
+ expect(results.map(&:embedding)).to all(be_nil)
701
+ end
702
+
703
+ it 'orders results by descending score' do
704
+ scores = results.map(&:vector_search_score)
705
+ expect(scores).to eq(scores.sort.reverse)
706
+ end
707
+ end
708
+
709
+ describe '#vector_search' do
710
+ let(:results) { doc_a.vector_search(limit: 3) }
711
+
712
+ it 'returns Mongoid document instances' do
713
+ expect(results).to all(be_a(vector_model))
714
+ end
715
+
716
+ it 'populates vector_search_score on each result' do
717
+ expect(results.map(&:vector_search_score)).to all(be_a(Float))
718
+ end
719
+
720
+ it 'excludes the source document from results' do
721
+ expect(results.map(&:id)).not_to include(doc_a.id)
722
+ end
131
723
  end
132
724
  end
133
725
 
134
- describe '.remove_search_indexes' do
726
+ context 'with an auto-embed search index' do
135
727
  before do
136
- actual_indexes # wait for the indexes to be created
137
- model.remove_search_indexes
138
- helper.wait_for_absense_of(actual_indexes.map { |i| i['name'] })
728
+ skip 'per DRIVERS-3315: "Integration tests can be deferred to follow up work to complete once a CI-compatible server is available with this feature (autoembedding indexes)"'
139
729
  end
140
730
 
141
- it 'removes the indexes' do
142
- expect(model.search_indexes).to be_empty
731
+ let(:embed_model) do
732
+ Class.new do
733
+ include Mongoid::Document
734
+
735
+ store_in collection: BSON::ObjectId.new.to_s
736
+ auto_embed_field :description, model: 'voyage-4'
737
+ end
738
+ end
739
+
740
+ let(:embed_helper) { SearchIndexHelper.new(embed_model) }
741
+
742
+ context 'when creating an auto-embed index' do
743
+ before { embed_helper } # ensure collection is set up before indexes are created
744
+
745
+ let(:index_names) { embed_model.create_search_indexes }
746
+ let(:actual_indexes) { embed_helper.wait_for(*index_names) }
747
+
748
+ describe '.create_search_indexes' do
749
+ it 'creates the auto-embed index' do
750
+ expect(actual_indexes).not_to be_empty
751
+ end
752
+
753
+ it 'creates an index with the autoEmbed field type' do
754
+ field_types = actual_indexes
755
+ .flat_map { |i| i.dig('latestDefinition', 'fields') }
756
+ .map { |f| f['type'] }
757
+ expect(field_types).to include('autoEmbed')
758
+ end
759
+ end
760
+
761
+ describe '.auto_embed_search' do
762
+ before do
763
+ actual_indexes # wait for index to be ready
764
+ embed_model.create(description: 'machine learning and neural networks')
765
+ embed_model.create(description: 'recipe for chocolate cake')
766
+ embed_model.create(description: 'deep learning for natural language processing')
767
+ # Atlas may need a moment to index new documents
768
+ sleep 5
769
+ end
770
+
771
+ it 'returns documents ranked by text similarity' do
772
+ results = embed_model.auto_embed_search('AI and machine learning', limit: 2)
773
+ expect(results).not_to be_empty
774
+ expect(results.first).to be_a(embed_model)
775
+ expect(results.first.vector_search_score).to be_a(Float)
776
+ end
777
+
778
+ it 'does not return more documents than the limit' do
779
+ results = embed_model.auto_embed_search('AI', limit: 1)
780
+ expect(results.size).to be <= 1
781
+ end
782
+
783
+ it 'supports exact nearest-neighbor search' do
784
+ results = embed_model.auto_embed_search('neural networks', exact: true, limit: 2)
785
+ expect(results).not_to be_empty
786
+ end
787
+ end
788
+
789
+ describe '#auto_embed_search' do
790
+ before do
791
+ actual_indexes # wait for index to be ready
792
+ embed_model.create(description: 'machine learning and neural networks')
793
+ embed_model.create(description: 'recipe for chocolate cake')
794
+ sleep 5
795
+ end
796
+
797
+ it 'returns similar documents excluding self' do
798
+ doc = embed_model.create(description: 'deep learning research')
799
+ sleep 5 # wait for the new document to be indexed
800
+ results = doc.auto_embed_search(limit: 5)
801
+ expect(results).not_to be_empty
802
+ expect(results.map(&:id)).not_to include(doc.id)
803
+ end
804
+ end
805
+ end
806
+
807
+ context 'with numDimensions and quantization options' do
808
+ let(:embed_model_with_opts) do
809
+ Class.new do
810
+ include Mongoid::Document
811
+
812
+ store_in collection: BSON::ObjectId.new.to_s
813
+ auto_embed_field :description,
814
+ model: 'voyage-4',
815
+ num_dimensions: 512,
816
+ quantization: 'scalar'
817
+ end
818
+ end
819
+
820
+ let(:opts_helper) { SearchIndexHelper.new(embed_model_with_opts) }
821
+
822
+ before { opts_helper } # initialize (drop/create collection) before index creation
823
+
824
+ it 'creates the index with the specified numDimensions and quantization' do
825
+ index_names = embed_model_with_opts.create_search_indexes
826
+ actual_indexes = opts_helper.wait_for(*index_names)
827
+
828
+ field_spec = actual_indexes
829
+ .flat_map { |i| i.dig('latestDefinition', 'fields') }
830
+ .find { |f| f['type'] == 'autoEmbed' }
831
+
832
+ expect(field_spec['numDimensions']).to eq 512
833
+ expect(field_spec['quantization']).to eq 'scalar'
834
+ end
143
835
  end
144
836
  end
145
837
  end
146
838
  end
147
- # rubocop:enable RSpec/MultipleMemoizedHelpers