mongoid 8.0.9 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +3 -3
  4. data/README.md +3 -3
  5. data/Rakefile +18 -67
  6. data/lib/config/locales/en.yml +46 -14
  7. data/lib/mongoid/association/accessors.rb +3 -7
  8. data/lib/mongoid/association/builders.rb +1 -1
  9. data/lib/mongoid/association/eager_loadable.rb +0 -3
  10. data/lib/mongoid/association/embedded/batchable.rb +2 -2
  11. data/lib/mongoid/association/embedded/embedded_in/buildable.rb +2 -2
  12. data/lib/mongoid/association/embedded/embedded_in/proxy.rb +2 -1
  13. data/lib/mongoid/association/embedded/embeds_many/buildable.rb +3 -2
  14. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +6 -6
  15. data/lib/mongoid/association/embedded/embeds_one/buildable.rb +1 -1
  16. data/lib/mongoid/association/embedded/embeds_one/proxy.rb +1 -1
  17. data/lib/mongoid/association/macros.rb +0 -6
  18. data/lib/mongoid/association/nested/one.rb +40 -2
  19. data/lib/mongoid/association/proxy.rb +1 -1
  20. data/lib/mongoid/association/referenced/counter_cache.rb +2 -2
  21. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +1 -1
  22. data/lib/mongoid/association/referenced/has_many/enumerable.rb +2 -2
  23. data/lib/mongoid/association/referenced/has_many/proxy.rb +3 -3
  24. data/lib/mongoid/association/reflections.rb +2 -2
  25. data/lib/mongoid/atomic.rb +7 -16
  26. data/lib/mongoid/attributes/dynamic.rb +1 -1
  27. data/lib/mongoid/attributes/nested.rb +2 -2
  28. data/lib/mongoid/attributes/processing.rb +5 -29
  29. data/lib/mongoid/attributes/projector.rb +1 -1
  30. data/lib/mongoid/attributes/readonly.rb +1 -1
  31. data/lib/mongoid/attributes.rb +8 -2
  32. data/lib/mongoid/changeable.rb +107 -5
  33. data/lib/mongoid/clients/storage_options.rb +2 -5
  34. data/lib/mongoid/clients/validators/storage.rb +1 -13
  35. data/lib/mongoid/collection_configurable.rb +58 -0
  36. data/lib/mongoid/composable.rb +2 -0
  37. data/lib/mongoid/config/defaults.rb +60 -0
  38. data/lib/mongoid/config/options.rb +0 -3
  39. data/lib/mongoid/config/validators/async_query_executor.rb +24 -0
  40. data/lib/mongoid/config/validators.rb +1 -0
  41. data/lib/mongoid/config.rb +88 -27
  42. data/lib/mongoid/contextual/atomic.rb +1 -1
  43. data/lib/mongoid/contextual/memory.rb +233 -33
  44. data/lib/mongoid/contextual/mongo/documents_loader.rb +177 -0
  45. data/lib/mongoid/contextual/mongo.rb +370 -133
  46. data/lib/mongoid/contextual/none.rb +162 -7
  47. data/lib/mongoid/contextual.rb +12 -0
  48. data/lib/mongoid/criteria/findable.rb +2 -2
  49. data/lib/mongoid/criteria/includable.rb +4 -3
  50. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -15
  51. data/lib/mongoid/criteria/queryable/key.rb +1 -1
  52. data/lib/mongoid/criteria/queryable/mergeable.rb +1 -1
  53. data/lib/mongoid/criteria/queryable/optional.rb +8 -8
  54. data/lib/mongoid/criteria/queryable/selectable.rb +43 -12
  55. data/lib/mongoid/criteria/queryable/selector.rb +1 -1
  56. data/lib/mongoid/criteria/queryable/storable.rb +1 -1
  57. data/lib/mongoid/criteria.rb +6 -5
  58. data/lib/mongoid/deprecable.rb +1 -2
  59. data/lib/mongoid/deprecation.rb +3 -3
  60. data/lib/mongoid/document.rb +1 -8
  61. data/lib/mongoid/errors/create_collection_failure.rb +33 -0
  62. data/lib/mongoid/errors/drop_collection_failure.rb +27 -0
  63. data/lib/mongoid/errors/immutable_attribute.rb +26 -0
  64. data/lib/mongoid/errors/invalid_async_query_executor.rb +25 -0
  65. data/lib/mongoid/errors/invalid_global_executor_concurrency.rb +22 -0
  66. data/lib/mongoid/errors/invalid_storage_parent.rb +2 -0
  67. data/lib/mongoid/errors.rb +4 -1
  68. data/lib/mongoid/extensions/hash.rb +2 -24
  69. data/lib/mongoid/extensions/object.rb +2 -2
  70. data/lib/mongoid/extensions/time.rb +2 -0
  71. data/lib/mongoid/fields/localized.rb +10 -0
  72. data/lib/mongoid/fields/standard.rb +10 -0
  73. data/lib/mongoid/fields.rb +59 -35
  74. data/lib/mongoid/findable.rb +27 -3
  75. data/lib/mongoid/interceptable.rb +6 -116
  76. data/lib/mongoid/matcher/eq_impl.rb +1 -1
  77. data/lib/mongoid/matcher/type.rb +1 -1
  78. data/lib/mongoid/persistable/creatable.rb +1 -0
  79. data/lib/mongoid/persistable/deletable.rb +1 -1
  80. data/lib/mongoid/persistable/savable.rb +13 -1
  81. data/lib/mongoid/persistable/unsettable.rb +2 -2
  82. data/lib/mongoid/persistable/updatable.rb +51 -1
  83. data/lib/mongoid/persistable/upsertable.rb +20 -1
  84. data/lib/mongoid/persistable.rb +3 -0
  85. data/lib/mongoid/query_cache.rb +5 -1
  86. data/lib/mongoid/railties/database.rake +7 -2
  87. data/lib/mongoid/reloadable.rb +5 -3
  88. data/lib/mongoid/stateful.rb +22 -1
  89. data/lib/mongoid/tasks/database.rake +12 -0
  90. data/lib/mongoid/tasks/database.rb +20 -0
  91. data/lib/mongoid/timestamps/created.rb +1 -8
  92. data/lib/mongoid/traversable.rb +0 -12
  93. data/lib/mongoid/utils.rb +22 -0
  94. data/lib/mongoid/validatable/associated.rb +17 -98
  95. data/lib/mongoid/validatable/macros.rb +5 -5
  96. data/lib/mongoid/validatable.rb +4 -9
  97. data/lib/mongoid/version.rb +1 -1
  98. data/lib/mongoid/warnings.rb +17 -1
  99. data/lib/mongoid.rb +16 -3
  100. data/spec/integration/app_spec.rb +2 -2
  101. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +0 -40
  102. data/spec/integration/callbacks_spec.rb +99 -12
  103. data/spec/integration/discriminator_key_spec.rb +4 -5
  104. data/spec/integration/i18n_fallbacks_spec.rb +3 -2
  105. data/spec/mongoid/association/eager_spec.rb +2 -24
  106. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +27 -0
  107. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +20 -25
  108. data/spec/mongoid/association/embedded/embeds_many_models.rb +1 -0
  109. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +0 -4
  110. data/spec/mongoid/association/embedded/embeds_one/proxy_spec.rb +15 -2
  111. data/spec/mongoid/association/referenced/belongs_to_spec.rb +2 -18
  112. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +5 -27
  113. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +9 -50
  114. data/spec/mongoid/association/syncable_spec.rb +1 -1
  115. data/spec/mongoid/association_spec.rb +0 -60
  116. data/spec/mongoid/attributes_spec.rb +3 -33
  117. data/spec/mongoid/changeable_spec.rb +299 -24
  118. data/spec/mongoid/clients_spec.rb +122 -13
  119. data/spec/mongoid/collection_configurable_spec.rb +158 -0
  120. data/spec/mongoid/config/defaults_spec.rb +160 -0
  121. data/spec/mongoid/config_spec.rb +154 -27
  122. data/spec/mongoid/contextual/memory_spec.rb +332 -76
  123. data/spec/mongoid/contextual/mongo/documents_loader_spec.rb +187 -0
  124. data/spec/mongoid/contextual/mongo_spec.rb +1009 -125
  125. data/spec/mongoid/contextual/none_spec.rb +49 -2
  126. data/spec/mongoid/copyable_spec.rb +2 -10
  127. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +4 -10
  128. data/spec/mongoid/criteria/queryable/options_spec.rb +1 -1
  129. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +419 -0
  130. data/spec/mongoid/criteria/queryable/selectable_spec.rb +1 -1
  131. data/spec/mongoid/criteria/queryable/selector_spec.rb +3 -76
  132. data/spec/mongoid/criteria/queryable/storable_spec.rb +0 -72
  133. data/spec/mongoid/criteria_projection_spec.rb +1 -4
  134. data/spec/mongoid/criteria_spec.rb +5 -9
  135. data/spec/mongoid/document_spec.rb +0 -27
  136. data/spec/mongoid/errors/readonly_document_spec.rb +2 -2
  137. data/spec/mongoid/extensions/hash_spec.rb +3 -3
  138. data/spec/mongoid/extensions/time_spec.rb +8 -43
  139. data/spec/mongoid/extensions/time_with_zone_spec.rb +7 -52
  140. data/spec/mongoid/fields/localized_spec.rb +46 -28
  141. data/spec/mongoid/fields_spec.rb +136 -77
  142. data/spec/mongoid/findable_spec.rb +391 -34
  143. data/spec/mongoid/indexable_spec.rb +16 -10
  144. data/spec/mongoid/interceptable_spec.rb +153 -442
  145. data/spec/mongoid/interceptable_spec_models.rb +111 -51
  146. data/spec/mongoid/persistable/deletable_spec.rb +26 -6
  147. data/spec/mongoid/persistable/destroyable_spec.rb +26 -6
  148. data/spec/mongoid/persistable/incrementable_spec.rb +37 -0
  149. data/spec/mongoid/persistable/logical_spec.rb +37 -0
  150. data/spec/mongoid/persistable/poppable_spec.rb +36 -0
  151. data/spec/mongoid/persistable/pullable_spec.rb +72 -0
  152. data/spec/mongoid/persistable/pushable_spec.rb +72 -0
  153. data/spec/mongoid/persistable/renamable_spec.rb +36 -0
  154. data/spec/mongoid/persistable/savable_spec.rb +96 -0
  155. data/spec/mongoid/persistable/settable_spec.rb +37 -0
  156. data/spec/mongoid/persistable/unsettable_spec.rb +36 -0
  157. data/spec/mongoid/persistable/updatable_spec.rb +20 -28
  158. data/spec/mongoid/persistable/upsertable_spec.rb +80 -6
  159. data/spec/mongoid/persistence_context_spec.rb +7 -57
  160. data/spec/mongoid/query_cache_spec.rb +56 -61
  161. data/spec/mongoid/reloadable_spec.rb +24 -28
  162. data/spec/mongoid/scopable_spec.rb +70 -0
  163. data/spec/mongoid/serializable_spec.rb +23 -44
  164. data/spec/mongoid/stateful_spec.rb +122 -8
  165. data/spec/mongoid/tasks/database_rake_spec.rb +74 -0
  166. data/spec/mongoid/tasks/database_spec.rb +127 -0
  167. data/spec/mongoid/timestamps/created_spec.rb +0 -23
  168. data/spec/mongoid/timestamps_spec.rb +9 -11
  169. data/spec/mongoid/touchable_spec.rb +277 -5
  170. data/spec/mongoid/touchable_spec_models.rb +3 -1
  171. data/spec/mongoid/traversable_spec.rb +9 -24
  172. data/spec/mongoid/validatable/associated_spec.rb +34 -27
  173. data/spec/mongoid/validatable/uniqueness_spec.rb +2 -3
  174. data/spec/mongoid_spec.rb +36 -10
  175. data/spec/shared/LICENSE +20 -0
  176. data/spec/shared/bin/get-mongodb-download-url +17 -0
  177. data/spec/shared/bin/s3-copy +45 -0
  178. data/spec/shared/bin/s3-upload +69 -0
  179. data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
  180. data/spec/shared/lib/mrss/cluster_config.rb +231 -0
  181. data/spec/shared/lib/mrss/constraints.rb +378 -0
  182. data/spec/shared/lib/mrss/docker_runner.rb +298 -0
  183. data/spec/shared/lib/mrss/eg_config_utils.rb +51 -0
  184. data/spec/shared/lib/mrss/event_subscriber.rb +210 -0
  185. data/spec/shared/lib/mrss/lite_constraints.rb +238 -0
  186. data/spec/shared/lib/mrss/server_version_registry.rb +113 -0
  187. data/spec/shared/lib/mrss/session_registry.rb +69 -0
  188. data/spec/shared/lib/mrss/session_registry_legacy.rb +60 -0
  189. data/spec/shared/lib/mrss/spec_organizer.rb +179 -0
  190. data/spec/shared/lib/mrss/utils.rb +37 -0
  191. data/spec/shared/share/Dockerfile.erb +321 -0
  192. data/spec/shared/share/haproxy-1.conf +16 -0
  193. data/spec/shared/share/haproxy-2.conf +17 -0
  194. data/spec/shared/shlib/config.sh +27 -0
  195. data/spec/shared/shlib/distro.sh +74 -0
  196. data/spec/shared/shlib/server.sh +416 -0
  197. data/spec/shared/shlib/set_env.sh +169 -0
  198. data/spec/spec_helper.rb +5 -0
  199. data/spec/support/immutable_ids.rb +118 -0
  200. data/spec/support/macros.rb +47 -15
  201. data/spec/support/models/artist.rb +0 -1
  202. data/spec/support/models/band.rb +1 -0
  203. data/spec/support/models/book.rb +1 -0
  204. data/spec/support/models/building.rb +2 -0
  205. data/spec/support/models/cover.rb +10 -0
  206. data/spec/support/models/name.rb +0 -10
  207. data/spec/support/models/person.rb +0 -1
  208. data/spec/support/models/product.rb +1 -0
  209. data.tar.gz.sig +0 -0
  210. metadata +746 -636
  211. metadata.gz.sig +2 -0
  212. data/spec/mongoid/criteria/queryable/extensions/bignum_spec.rb +0 -60
  213. data/spec/mongoid/criteria/queryable/extensions/fixnum_spec.rb +0 -60
  214. data/spec/support/models/purse.rb +0 -9
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "mongoid/config/defaults"
3
4
  require "mongoid/config/environment"
4
5
  require "mongoid/config/options"
5
6
  require "mongoid/config/validators"
@@ -11,6 +12,7 @@ module Mongoid
11
12
  module Config
12
13
  extend Forwardable
13
14
  extend Options
15
+ extend Defaults
14
16
  extend self
15
17
 
16
18
  def_delegators ::Mongoid, :logger, :logger=
@@ -70,6 +72,7 @@ module Mongoid
70
72
 
71
73
  # Use ActiveSupport's time zone in time operations instead of the
72
74
  # Ruby default time zone.
75
+ # @deprecated
73
76
  option :use_activesupport_time_zone, default: true
74
77
 
75
78
  # Return stored times as UTC.
@@ -125,22 +128,35 @@ module Mongoid
125
128
  # always return a Hash.
126
129
  option :legacy_attributes, default: false
127
130
 
128
- # Allow BSON::Decimal128 to be parsed and returned directly in
129
- # field values. When BSON 5 is present and the this option is set to false
130
- # (the default), BSON::Decimal128 values in the database will be returned
131
- # as BigDecimal.
132
- #
133
- # @note this option only has effect when BSON 5+ is present. Otherwise,
134
- # the setting is ignored.
135
- option :allow_bson5_decimal128, default: false, on_change: -> (allow) do
136
- if BSON::VERSION >= '5.0.0'
137
- if allow
138
- BSON::Registry.register(BSON::Decimal128::BSON_TYPE, BSON::Decimal128)
139
- else
140
- BSON::Registry.register(BSON::Decimal128::BSON_TYPE, BigDecimal)
141
- end
142
- end
143
- end
131
+ # Sets the async_query_executor for the application. By default the thread pool executor
132
+ # is set to `:immediate. Options are:
133
+ #
134
+ # - :immediate - Initializes a single +Concurrent::ImmediateExecutor+
135
+ # - :global_thread_pool - Initializes a single +Concurrent::ThreadPoolExecutor+
136
+ # that uses the +async_query_concurrency+ for the +max_threads+ value.
137
+ option :async_query_executor, default: :immediate
138
+
139
+ # Defines how many asynchronous queries can be executed concurrently.
140
+ # This option should be set only if `async_query_executor` is set
141
+ # to `:global_thread_pool`.
142
+ option :global_executor_concurrency, default: nil
143
+
144
+ # When this flag is false, a document will become read-only only once the
145
+ # #readonly! method is called, and an error will be raised on attempting
146
+ # to save or update such documents, instead of just on delete. When this
147
+ # flag is true, a document is only read-only if it has been projected
148
+ # using #only or #without, and read-only documents will not be
149
+ # deletable/destroyable, but they will be savable/updatable.
150
+ # When this feature flag is turned on, the read-only state will be reset on
151
+ # reload, but when it is turned off, it won't be.
152
+ option :legacy_readonly, default: true
153
+
154
+ # When this flag is true, any attempt to change the _id of a persisted
155
+ # document will raise an exception (`Errors::ImmutableAttribute`).
156
+ # This will be the default in 9.0. When this flag is false (the default
157
+ # in 8.x), changing the _id of a persisted document might be ignored,
158
+ # or it might work, depending on the situation.
159
+ option :immutable_ids, default: false
144
160
 
145
161
  # When this flag is true, callbacks for every embedded document will be
146
162
  # called only once, even if the embedded document is embedded in multiple
@@ -152,18 +168,12 @@ module Mongoid
152
168
  # See https://jira.mongodb.org/browse/MONGOID-5542
153
169
  option :prevent_multiple_calls_of_embedded_callbacks, default: false
154
170
 
155
- # When this flag is true, callbacks for embedded documents will not be
156
- # called. This is the default in 8.x, but will be changed to false in 9.0.
157
- #
158
- # Setting this flag to true (as it is in 8.x) may lead to stack
159
- # overflow errors if there are more than cicrca 1000 embedded
160
- # documents in the root document's dependencies graph.
171
+ # Returns the Config singleton, for use in the configure DSL.
161
172
  #
162
- # It is strongly recommended to set this flag to false in 8.x, if you
163
- # are not using around callbacks for embedded documents.
164
- #
165
- # See https://jira.mongodb.org/browse/MONGOID-5658 for more details.
166
- option :around_callbacks_for_embeds, default: true
173
+ # @return [ self ] The Config singleton.
174
+ def config
175
+ self
176
+ end
167
177
 
168
178
  # Has Mongoid been configured? This is checking that at least a valid
169
179
  # client config exists.
@@ -246,6 +256,17 @@ module Mongoid
246
256
  end
247
257
  end
248
258
 
259
+ # Deregister a model in the application with Mongoid.
260
+ #
261
+ # @param [ Class ] klass The model to deregister.
262
+ #
263
+ # @api private
264
+ def deregister_model(klass)
265
+ LOCK.synchronize do
266
+ models.delete(klass)
267
+ end
268
+ end
269
+
249
270
  # From a hash of settings, load all the configuration.
250
271
  #
251
272
  # @example Load the configuration.
@@ -318,6 +339,7 @@ module Mongoid
318
339
  # @param [ Hash ] options The configuration options.
319
340
  def options=(options)
320
341
  if options
342
+ Validators::AsyncQueryExecutor.validate(options)
321
343
  options.each_pair do |option, value|
322
344
  Validators::Option.validate(option)
323
345
  send("#{option}=", value)
@@ -385,5 +407,44 @@ module Mongoid
385
407
  client
386
408
  end
387
409
  end
410
+
411
+ module DeprecatedOptions
412
+ OPTIONS = %i[ use_activesupport_time_zone
413
+ broken_aggregables
414
+ broken_alias_handling
415
+ broken_and
416
+ broken_scoping
417
+ broken_updates
418
+ compare_time_by_ms
419
+ legacy_attributes
420
+ legacy_pluck_distinct
421
+ legacy_triple_equals
422
+ object_id_as_json_oid
423
+ overwrite_chained_operators ]
424
+
425
+ if RUBY_VERSION < '3.0'
426
+ def self.prepended(klass)
427
+ klass.class_eval do
428
+ OPTIONS.each do |option|
429
+ alias_method :"#{option}_without_deprecation=", :"#{option}="
430
+
431
+ define_method(:"#{option}=") do |value|
432
+ Mongoid::Warnings.send(:"warn_#{option}_deprecated")
433
+ send(:"#{option}_without_deprecation=", value)
434
+ end
435
+ end
436
+ end
437
+ end
438
+ else
439
+ OPTIONS.each do |option|
440
+ define_method(:"#{option}=") do |value|
441
+ Mongoid::Warnings.send(:"warn_#{option}_deprecated")
442
+ super(value)
443
+ end
444
+ end
445
+ end
446
+ end
447
+
448
+ prepend DeprecatedOptions
388
449
  end
389
450
  end
@@ -150,7 +150,7 @@ module Mongoid
150
150
  # @example Unset the field on the matches.
151
151
  # context.unset(:name)
152
152
  #
153
- # @param [ String | Symbol | Array<String | Symbol> | Hash ] args
153
+ # @param [ [ String | Symbol | Array<String | Symbol> | Hash ]... ] *args
154
154
  # The name(s) of the field(s) to unset.
155
155
  # If a Hash is specified, its keys will be used irrespective of what
156
156
  # each key's value is, even if the value is nil or false.
@@ -110,9 +110,24 @@ module Mongoid
110
110
  # @example Do any documents exist for the context.
111
111
  # context.exists?
112
112
  #
113
+ # @example Do any documents exist for given _id.
114
+ # context.exists?(BSON::ObjectId(...))
115
+ #
116
+ # @example Do any documents exist for given conditions.
117
+ # context.exists?(name: "...")
118
+ #
119
+ # @param [ Hash | Object | false ] id_or_conditions an _id to
120
+ # search for, a hash of conditions, nil or false.
121
+ #
113
122
  # @return [ true | false ] If the count is more than zero.
114
- def exists?
115
- any?
123
+ # Always false if passed nil or false.
124
+ def exists?(id_or_conditions = :none)
125
+ case id_or_conditions
126
+ when :none then any?
127
+ when nil, false then false
128
+ when Hash then Memory.new(criteria.where(id_or_conditions)).exists?
129
+ else Memory.new(criteria.where(_id: id_or_conditions)).exists?
130
+ end
116
131
  end
117
132
 
118
133
  # Get the first document in the database for the criteria's selector.
@@ -133,6 +148,20 @@ module Mongoid
133
148
  alias :one :first
134
149
  alias :find_first :first
135
150
 
151
+ # Get the first document in the database for the criteria's selector or
152
+ # raise an error if none is found.
153
+ #
154
+ # @example Get the first document.
155
+ # context.first!
156
+ #
157
+ # @return [ Document ] The first document.
158
+ #
159
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
160
+ # documents to take.
161
+ def first!
162
+ first || raise_document_not_found_error
163
+ end
164
+
136
165
  # Create the new in memory context.
137
166
  #
138
167
  # @example Create the new context.
@@ -180,37 +209,18 @@ module Mongoid
180
209
  end
181
210
  end
182
211
 
183
- # Take the given number of documents from the database.
184
- #
185
- # @example Take a document.
186
- # context.take
187
- #
188
- # @param [ Integer | nil ] limit The number of documents to take or nil.
212
+ # Get the last document in the database for the criteria's selector or
213
+ # raise an error if none is found.
189
214
  #
190
- # @return [ Document ] The document.
191
- def take(limit = nil)
192
- if limit
193
- eager_load(documents.take(limit))
194
- else
195
- eager_load([documents.first]).first
196
- end
197
- end
198
-
199
- # Take the given number of documents from the database.
200
- #
201
- # @example Take a document.
202
- # context.take
215
+ # @example Get the last document.
216
+ # context.last!
203
217
  #
204
- # @return [ Document ] The document.
218
+ # @return [ Document ] The last document.
205
219
  #
206
220
  # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
207
221
  # documents to take.
208
- def take!
209
- if documents.empty?
210
- raise Errors::DocumentNotFound.new(klass, nil, nil)
211
- else
212
- eager_load([documents.first]).first
213
- end
222
+ def last!
223
+ last || raise_document_not_found_error
214
224
  end
215
225
 
216
226
  # Get the length of matching documents in the context.
@@ -242,7 +252,7 @@ module Mongoid
242
252
  # @example Get the values in memory.
243
253
  # context.pluck(:name)
244
254
  #
245
- # @param [ String | Symbol ] *fields Field(s) to pluck.
255
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pluck.
246
256
  #
247
257
  # @return [ Array<Object> | Array<Array<Object>> ] The plucked values.
248
258
  def pluck(*fields)
@@ -260,9 +270,9 @@ module Mongoid
260
270
  # @example Get the values in memory.
261
271
  # context.pick(:name)
262
272
  #
263
- # @param [ String | Symbol ] *fields Field(s) to pick.
273
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pick.
264
274
  #
265
- # @return [ Object, Array<Object> ] The picked values.
275
+ # @return [ Object | Array<Object> ] The picked values.
266
276
  def pick(*fields)
267
277
  if doc = documents.first
268
278
  pluck_from_doc(doc, *fields)
@@ -285,6 +295,36 @@ module Mongoid
285
295
  end
286
296
  end
287
297
 
298
+ # Take the given number of documents from the database.
299
+ #
300
+ # @example Take a document.
301
+ # context.take
302
+ #
303
+ # @param [ Integer | nil ] limit The number of documents to take or nil.
304
+ #
305
+ # @return [ Document ] The document.
306
+ def take(limit = nil)
307
+ if limit
308
+ eager_load(documents.take(limit))
309
+ else
310
+ eager_load([documents.first]).first
311
+ end
312
+ end
313
+
314
+ # Take the given number of documents from the database or raise an error
315
+ # if none are found.
316
+ #
317
+ # @example Take a document.
318
+ # context.take
319
+ #
320
+ # @return [ Document ] The document.
321
+ #
322
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
323
+ # documents to take.
324
+ def take!
325
+ take || raise_document_not_found_error
326
+ end
327
+
288
328
  # Skips the provided number of documents.
289
329
  #
290
330
  # @example Skip the documents.
@@ -335,6 +375,162 @@ module Mongoid
335
375
  update_documents(attributes, entries)
336
376
  end
337
377
 
378
+ # Get the second document in the database for the criteria's selector.
379
+ #
380
+ # @example Get the second document.
381
+ # context.second
382
+ #
383
+ # @param [ Integer ] limit The number of documents to return.
384
+ #
385
+ # @return [ Document ] The second document.
386
+ def second
387
+ eager_load([documents.second]).first
388
+ end
389
+
390
+ # Get the second document in the database for the criteria's selector or
391
+ # raise an error if none is found.
392
+ #
393
+ # @example Get the second document.
394
+ # context.second!
395
+ #
396
+ # @return [ Document ] The second document.
397
+ #
398
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
399
+ # documents to take.
400
+ def second!
401
+ second || raise_document_not_found_error
402
+ end
403
+
404
+ # Get the third document in the database for the criteria's selector.
405
+ #
406
+ # @example Get the third document.
407
+ # context.third
408
+ #
409
+ # @param [ Integer ] limit The number of documents to return.
410
+ #
411
+ # @return [ Document ] The third document.
412
+ def third
413
+ eager_load([documents.third]).first
414
+ end
415
+
416
+ # Get the third document in the database for the criteria's selector or
417
+ # raise an error if none is found.
418
+ #
419
+ # @example Get the third document.
420
+ # context.third!
421
+ #
422
+ # @return [ Document ] The third document.
423
+ #
424
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
425
+ # documents to take.
426
+ def third!
427
+ third || raise_document_not_found_error
428
+ end
429
+
430
+ # Get the fourth document in the database for the criteria's selector.
431
+ #
432
+ # @example Get the fourth document.
433
+ # context.fourth
434
+ #
435
+ # @param [ Integer ] limit The number of documents to return.
436
+ #
437
+ # @return [ Document ] The fourth document.
438
+ def fourth
439
+ eager_load([documents.fourth]).first
440
+ end
441
+
442
+ # Get the fourth document in the database for the criteria's selector or
443
+ # raise an error if none is found.
444
+ #
445
+ # @example Get the fourth document.
446
+ # context.fourth!
447
+ #
448
+ # @return [ Document ] The fourth document.
449
+ #
450
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
451
+ # documents to take.
452
+ def fourth!
453
+ fourth || raise_document_not_found_error
454
+ end
455
+
456
+ # Get the fifth document in the database for the criteria's selector.
457
+ #
458
+ # @example Get the fifth document.
459
+ # context.fifth
460
+ #
461
+ # @param [ Integer ] limit The number of documents to return.
462
+ #
463
+ # @return [ Document ] The fifth document.
464
+ def fifth
465
+ eager_load([documents.fifth]).first
466
+ end
467
+
468
+ # Get the fifth document in the database for the criteria's selector or
469
+ # raise an error if none is found.
470
+ #
471
+ # @example Get the fifth document.
472
+ # context.fifth!
473
+ #
474
+ # @return [ Document ] The fifth document.
475
+ #
476
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
477
+ # documents to take.
478
+ def fifth!
479
+ fifth || raise_document_not_found_error
480
+ end
481
+
482
+ # Get the second to last document in the database for the criteria's selector.
483
+ #
484
+ # @example Get the second to last document.
485
+ # context.second_to_last
486
+ #
487
+ # @param [ Integer ] limit The number of documents to return.
488
+ #
489
+ # @return [ Document ] The second to last document.
490
+ def second_to_last
491
+ eager_load([documents.second_to_last]).first
492
+ end
493
+
494
+ # Get the second to last document in the database for the criteria's selector or
495
+ # raise an error if none is found.
496
+ #
497
+ # @example Get the second to last document.
498
+ # context.second_to_last!
499
+ #
500
+ # @return [ Document ] The second to last document.
501
+ #
502
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
503
+ # documents to take.
504
+ def second_to_last!
505
+ second_to_last || raise_document_not_found_error
506
+ end
507
+
508
+ # Get the third to last document in the database for the criteria's selector.
509
+ #
510
+ # @example Get the third to last document.
511
+ # context.third_to_last
512
+ #
513
+ # @param [ Integer ] limit The number of documents to return.
514
+ #
515
+ # @return [ Document ] The third to last document.
516
+ def third_to_last
517
+ eager_load([documents.third_to_last]).first
518
+ end
519
+
520
+ # Get the third to last document in the database for the criteria's selector or
521
+ # raise an error if none is found.
522
+ #
523
+ # @example Get the third to last document.
524
+ # context.third_to_last!
525
+ #
526
+ # @return [ Document ] The third to last document.
527
+ #
528
+ # @raises [ Mongoid::Errors::DocumentNotFound ] raises when there are no
529
+ # documents to take.
530
+ def third_to_last!
531
+ third_to_last || raise_document_not_found_error
532
+ end
533
+
338
534
  private
339
535
 
340
536
  # Get the documents the context should iterate. This follows 3 rules:
@@ -561,9 +757,9 @@ module Mongoid
561
757
  # Pluck the field values from the given document.
562
758
  #
563
759
  # @param [ Document ] doc The document to pluck from.
564
- # @param [ String | Symbol ] *fields Field(s) to pluck.
760
+ # @param [ [ String | Symbol ]... ] *fields Field(s) to pluck.
565
761
  #
566
- # @return [ Object, Array<Object> ] The plucked values.
762
+ # @return [ Object | Array<Object> ] The plucked values.
567
763
  def pluck_from_doc(doc, *fields)
568
764
  if fields.length == 1
569
765
  retrieve_value_at_path(doc, fields.first)
@@ -573,6 +769,10 @@ module Mongoid
573
769
  end
574
770
  end
575
771
  end
772
+
773
+ def raise_document_not_found_error
774
+ raise Errors::DocumentNotFound.new(klass, nil, nil)
775
+ end
576
776
  end
577
777
  end
578
778
  end
@@ -0,0 +1,177 @@
1
+ require "mongoid/association/eager_loadable"
2
+
3
+ module Mongoid
4
+ module Contextual
5
+ class Mongo
6
+ # Loads documents for the provided criteria.
7
+ #
8
+ # @api private
9
+ class DocumentsLoader
10
+ extend Forwardable
11
+ include Association::EagerLoadable
12
+
13
+ def_delegators :@future, :value!, :value, :wait!, :wait
14
+
15
+ # Returns synchronous executor to be used when async_query_executor config option
16
+ # is set to :immediate. This executor runs all operations on the current
17
+ # thread, blocking as necessary.
18
+ #
19
+ # @return [ Concurrent::ImmediateExecutor ] The executor
20
+ # to be used to execute document loading tasks.
21
+ def self.immediate_executor
22
+ @@immediate_executor ||= Concurrent::ImmediateExecutor.new
23
+ end
24
+
25
+ # Returns asynchronous executor to be used when async_query_executor config option
26
+ # is set to :global_thread_pool. This executor runs operations on background threads
27
+ # using a thread pool.
28
+ #
29
+ # @return [ Concurrent::ThreadPoolExecutor ] The executor
30
+ # to be used to execute document loading tasks.
31
+ def self.global_thread_pool_async_query_executor
32
+ create_pool = Proc.new do |concurrency|
33
+ Concurrent::ThreadPoolExecutor.new(
34
+ min_threads: 0,
35
+ max_threads: concurrency,
36
+ max_queue: concurrency * 4,
37
+ fallback_policy: :caller_runs
38
+ )
39
+ end
40
+ concurrency = Mongoid.global_executor_concurrency || 4
41
+ @@global_thread_pool_async_query_executor ||= create_pool.call(concurrency)
42
+ if @@global_thread_pool_async_query_executor.max_length != concurrency
43
+ old_pool = @@global_thread_pool_async_query_executor
44
+ @@global_thread_pool_async_query_executor = create_pool.call(concurrency)
45
+ old_pool.shutdown
46
+ end
47
+ @@global_thread_pool_async_query_executor
48
+ end
49
+
50
+ # Returns suitable executor according to Mongoid config options.
51
+ #
52
+ # @param [ String | Symbol] name The query executor name, can be either
53
+ # :immediate or :global_thread_pool. Defaulted to `async_query_executor`
54
+ # config option.
55
+ #
56
+ # @return [ Concurrent::ImmediateExecutor | Concurrent::ThreadPoolExecutor ] The executor
57
+ # to be used to execute document loading tasks.
58
+ #
59
+ # @raise [ Errors::InvalidQueryExecutor ] If an unknown name is provided.
60
+ def self.executor(name = Mongoid.async_query_executor)
61
+ case name.to_sym
62
+ when :immediate
63
+ immediate_executor
64
+ when :global_thread_pool
65
+ global_thread_pool_async_query_executor
66
+ else
67
+ raise Errors::InvalidQueryExecutor.new(name)
68
+ end
69
+ end
70
+
71
+ # @return [ Mongoid::Criteria ] Criteria that specifies which documents should
72
+ # be loaded. Exposed here because `eager_loadable?` method from
73
+ # `Association::EagerLoadable` expects this to be available.
74
+ attr_accessor :criteria
75
+
76
+ # Instantiates the document loader instance and immediately schedules
77
+ # its execution using the provided executor.
78
+ #
79
+ # @param [ Mongo::Collection::View ] view The collection view to get
80
+ # records from the database.
81
+ # @param [ Class ] klass Mongoid model class to instantiate documents.
82
+ # All records obtained from the database will be converted to an
83
+ # instance of this class, if possible.
84
+ # @param [ Mongoid::Criteria ] criteria. Criteria that specifies which
85
+ # documents should be loaded.
86
+ # @param [ Concurrent::AbstractExecutorService ] executor. Executor that
87
+ # is capable of running `Concurrent::Promises::Future` instances.
88
+ def initialize(view, klass, criteria, executor: self.class.executor)
89
+ @view = view
90
+ @klass = klass
91
+ @criteria = criteria
92
+ @mutex = Mutex.new
93
+ @state = :pending
94
+ @future = Concurrent::Promises.future_on(executor) do
95
+ start && execute
96
+ end
97
+ end
98
+
99
+ # Returns false or true whether the loader is in pending state.
100
+ #
101
+ # Pending state means that the loader execution has been scheduled,
102
+ # but has not been started yet.
103
+ #
104
+ # @return [ true | false ] true if the loader is in pending state,
105
+ # otherwise false.
106
+ def pending?
107
+ @mutex.synchronize do
108
+ @state == :pending
109
+ end
110
+ end
111
+
112
+ # Returns false or true whether the loader is in started state.
113
+ #
114
+ # Started state means that the loader execution has been started.
115
+ # Note that the loader stays in this state even after the execution
116
+ # completed (successfully or failed).
117
+ #
118
+ # @return [ true | false ] true if the loader is in started state,
119
+ # otherwise false.
120
+ def started?
121
+ @mutex.synchronize do
122
+ @state == :started
123
+ end
124
+ end
125
+
126
+ # Mark the loader as unscheduled.
127
+ #
128
+ # If the loader is marked unscheduled, it will not be executed. The only
129
+ # option to load the documents is to call `execute` method directly.
130
+ #
131
+ # Please note that if execution of a task has been already started,
132
+ # unscheduling does not have any effect.
133
+ def unschedule
134
+ @mutex.synchronize do
135
+ @state = :cancelled unless @state == :started
136
+ end
137
+ end
138
+
139
+ # Loads records specified by `@criteria` from the database, and convert
140
+ # them to Mongoid documents of `@klass` type.
141
+ #
142
+ # This method is called by the task (possibly asynchronous) scheduled
143
+ # when creating an instance of the loader. However, this method can be
144
+ # called directly, if it is desired to execute loading on the caller
145
+ # thread immediately.
146
+ #
147
+ # Calling this method does not change the state of the loader.
148
+ #
149
+ # @return [ Array<Mongoid::Document> ] Array of document loaded from
150
+ # the database.
151
+ def execute
152
+ documents = @view.map do |doc|
153
+ Factory.from_db(@klass, doc, @criteria)
154
+ end
155
+ eager_load(documents) if eager_loadable?
156
+ documents
157
+ end
158
+
159
+ private
160
+
161
+ # Mark the loader as started if possible.
162
+ #
163
+ # @return [ true | false ] Whether the state was changed to :started.
164
+ def start
165
+ @mutex.synchronize do
166
+ if @state == :pending
167
+ @state = :started
168
+ true
169
+ else
170
+ false
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end