mongoid 6.3.0 → 6.4.0

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 (42) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/config/locales/en.yml +21 -0
  5. data/lib/mongoid.rb +1 -1
  6. data/lib/mongoid/clients.rb +2 -0
  7. data/lib/mongoid/clients/sessions.rb +113 -0
  8. data/lib/mongoid/clients/storage_options.rb +1 -0
  9. data/lib/mongoid/contextual/aggregable/mongo.rb +1 -1
  10. data/lib/mongoid/contextual/map_reduce.rb +6 -2
  11. data/lib/mongoid/contextual/memory.rb +7 -2
  12. data/lib/mongoid/contextual/mongo.rb +11 -2
  13. data/lib/mongoid/criteria.rb +1 -0
  14. data/lib/mongoid/criteria/queryable/mergeable.rb +3 -1
  15. data/lib/mongoid/errors.rb +1 -0
  16. data/lib/mongoid/errors/invalid_session_use.rb +24 -0
  17. data/lib/mongoid/indexable.rb +4 -4
  18. data/lib/mongoid/persistable.rb +1 -1
  19. data/lib/mongoid/persistable/creatable.rb +4 -2
  20. data/lib/mongoid/persistable/deletable.rb +4 -2
  21. data/lib/mongoid/persistable/destroyable.rb +1 -5
  22. data/lib/mongoid/persistable/updatable.rb +2 -2
  23. data/lib/mongoid/persistable/upsertable.rb +2 -1
  24. data/lib/mongoid/relations/embedded/batchable.rb +10 -4
  25. data/lib/mongoid/relations/many.rb +4 -0
  26. data/lib/mongoid/relations/referenced/many.rb +1 -1
  27. data/lib/mongoid/relations/touchable.rb +1 -1
  28. data/lib/mongoid/reloadable.rb +1 -1
  29. data/lib/mongoid/tasks/database.rb +3 -2
  30. data/lib/mongoid/threaded.rb +38 -0
  31. data/lib/mongoid/version.rb +1 -1
  32. data/spec/mongoid/attributes/nested_spec.rb +4 -0
  33. data/spec/mongoid/clients/sessions_spec.rb +325 -0
  34. data/spec/mongoid/contextual/mongo_spec.rb +38 -0
  35. data/spec/mongoid/criteria/queryable/selectable_spec.rb +32 -3
  36. data/spec/mongoid/interceptable_spec.rb +1 -1
  37. data/spec/mongoid/persistable/deletable_spec.rb +19 -0
  38. data/spec/mongoid/persistable/destroyable_spec.rb +19 -0
  39. data/spec/mongoid/persistable_spec.rb +16 -16
  40. data/spec/spec_helper.rb +70 -0
  41. metadata +17 -7
  42. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5c7e1fbb3fda280fb0e0f74c312b36b7a73dad79
4
- data.tar.gz: 92dcab3023e9005560efd4209e3eecd26032b259
2
+ SHA256:
3
+ metadata.gz: 496715e8829978d85bd903477e9d6d7961fa4ffe8d9529d6720fbfdb2b3ec9a8
4
+ data.tar.gz: 907296ce5256555834c738f5fdf8d7277b57d6c09da3c733eb7cb0ba6526d0a2
5
5
  SHA512:
6
- metadata.gz: 44e65ba1446fc7c42e545b38223d4122e798f85d26aeb93103b85c45e7a4abc17e3b7da9f60853a7a38a24b5d4a258338111759d0b22bb4e5f99611e002eaa0f
7
- data.tar.gz: 25b84f09ac4fb6366a0d9782d0865a0a1ae214234fbdbde4ce57146d8bf740823b66f15d4871e4a45b0d3fc5f0557b9f91dd743501713ae6920a430e90bab62e
6
+ metadata.gz: 9d16c8cff475066981b0736ed07ebe0b463c8ee4e8c43049087f43ad8ee6789ff88a5c2e73e3fe6a31a52f7732cc6b8614de72d8c76df2dd3c54ee48a5977a41
7
+ data.tar.gz: d15deebe3406b501b95fa864306c03cfa7b106f810909dffeda8d7402d14de5aa1dad3d1129685b859ef9d537e656108a015b77f066ea5affd9e32785e018b0a
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -206,6 +206,20 @@ en:
206
206
  \_\_\_\_include Mongoid::Document\n
207
207
  \_\_\_\_scope :inactive, ->{ where(active: false) }\n
208
208
  \_\_end\n\n"
209
+ invalid_session_use:
210
+ message: "A session was attempted to be used with a model whose client cannot use
211
+ that session."
212
+ summary: "Sessions are started via driver clients (Model#mongo_client) and, in most cases, driver
213
+ clients are shared across models. When different models have their own clients, a session cannot
214
+ be obtained via one model and used for operations on another model."
215
+ resolution: "Only execute operations on the model class or instances of the model through which
216
+ the session was created. Otherwise, ensure that all models on which operations are executed
217
+ in the session block share the same driver client. For example, a model may have a different
218
+ client specified in its 'store_in' options.\n\n"
219
+ invalid_session_nesting:
220
+ message: "A session was started while another session was being used."
221
+ summary: "Sessions cannot be nested. Only one session can be used in a thread at once."
222
+ resolution: "Only use one session at a time; sessions cannot be nested."
209
223
  invalid_storage_options:
210
224
  message: "Invalid options passed to %{klass}.store_in: %{options}."
211
225
  summary: "The :store_in macro takes only a hash of parameters with
@@ -452,6 +466,13 @@ en:
452
466
  with the already defined method %{model_name}, or set the
453
467
  configuration option Mongoid.scope_overwrite_exception to false,
454
468
  which is its default. In this case a warning will be logged."
469
+ sessions_not_supported:
470
+ message: "Sessions are not supported by the connected server(s)."
471
+ summary: "A session was attempted to be used with a MongoDB server version
472
+ that doesn't support sessions. Sessions are supported in MongoDB
473
+ server versions 3.6 and higher."
474
+ resolution: "Verify that all servers in your deployment are at least
475
+ version 3.6 or don't attempt to use sessions with older server versions."
455
476
  taken:
456
477
  "is already taken"
457
478
  too_many_nested_attribute_records:
@@ -42,7 +42,7 @@ module Mongoid
42
42
  PLATFORM_DETAILS = "mongoid-#{VERSION}".freeze
43
43
 
44
44
  # The minimum MongoDB version supported.
45
- MONGODB_VERSION = "2.4.0"
45
+ MONGODB_VERSION = "2.6.0"
46
46
 
47
47
  # Sets the Mongoid configuration options. Best used by passing a block.
48
48
  #
@@ -3,12 +3,14 @@ require "mongoid/clients/factory"
3
3
  require "mongoid/clients/validators"
4
4
  require "mongoid/clients/storage_options"
5
5
  require "mongoid/clients/options"
6
+ require "mongoid/clients/sessions"
6
7
 
7
8
  module Mongoid
8
9
  module Clients
9
10
  extend ActiveSupport::Concern
10
11
  include StorageOptions
11
12
  include Options
13
+ include Sessions
12
14
 
13
15
  class << self
14
16
 
@@ -0,0 +1,113 @@
1
+ module Mongoid
2
+ module Clients
3
+
4
+ # Encapsulates behavior for getting a session from the client of a model class or instance,
5
+ # setting the session on the current thread, and yielding to a block.
6
+ # The session will be closed after the block completes or raises an error.
7
+ #
8
+ # @since 6.4.0
9
+ module Sessions
10
+
11
+ # Execute a block within the context of a session.
12
+ #
13
+ # @example Execute some operations in the context of a session.
14
+ # band.with_session(causal_consistency: true) do
15
+ # band.records << Record.create
16
+ # band.name = 'FKA Twigs'
17
+ # band.save
18
+ # band.reload
19
+ # end
20
+ #
21
+ # @param [ Hash ] options The session options. Please see the driver
22
+ # documentation for the available session options.
23
+ #
24
+ # @note You cannot do any operations in the block using models or objects
25
+ # that use a different client; the block will execute all operations
26
+ # in the context of the implicit session and operations on any models using
27
+ # another client will fail. For example, if you set a client using store_in on a
28
+ # particular model and execute an operation on it in the session context block,
29
+ # that operation can't use the block's session and an error will be raised.
30
+ # An error will also be raised if sessions are nested.
31
+ #
32
+ # @raise [ Errors::InvalidSessionUse ] If an operation is attempted on a model using another
33
+ # client from which the session was started or if sessions are nested.
34
+ #
35
+ # @return [ Object ] The result of calling the block.
36
+ #
37
+ # @yieldparam [ Mongo::Session ] The session being used for the block.
38
+ #
39
+ # @since 6.4.0
40
+ def with_session(options = {})
41
+ raise Mongoid::Errors::InvalidSessionUse.new(:invalid_session_nesting) if Threaded.get_session
42
+ session = persistence_context.client.start_session(options)
43
+ Threaded.set_session(session)
44
+ yield(session)
45
+ rescue Mongo::Error::InvalidSession => ex
46
+ if ex.message == Mongo::Session::SESSIONS_NOT_SUPPORTED
47
+ raise Mongoid::Errors::InvalidSessionUse.new(:sessions_not_supported)
48
+ end
49
+ raise Mongoid::Errors::InvalidSessionUse.new(:invalid_session_use)
50
+ ensure
51
+ Threaded.clear_session
52
+ end
53
+
54
+ private
55
+
56
+ def session
57
+ Threaded.get_session
58
+ end
59
+
60
+ module ClassMethods
61
+
62
+ # Execute a block within the context of a session.
63
+ #
64
+ # @example Execute some operations in the context of a session.
65
+ # Band.with_session(causal_consistency: true) do
66
+ # band = Band.create
67
+ # band.records << Record.new
68
+ # band.save
69
+ # band.reload.records
70
+ # end
71
+ #
72
+ # @param [ Hash ] options The session options. Please see the driver
73
+ # documentation for the available session options.
74
+ #
75
+ # @note You cannot do any operations in the block using models or objects
76
+ # that use a different client; the block will execute all operations
77
+ # in the context of the implicit session and operations on any models using
78
+ # another client will fail. For example, if you set a client using store_in on a
79
+ # particular model and execute an operation on it in the session context block,
80
+ # that operation can't use the block's session and an error will be raised.
81
+ # You also cannot nest sessions.
82
+ #
83
+ # @raise [ Errors::InvalidSessionUse ] If an operation is attempted on a model using another
84
+ # client from which the session was started or if sessions are nested.
85
+ #
86
+ # @return [ Object ] The result of calling the block.
87
+ #
88
+ # @yieldparam [ Mongo::Session ] The session being used for the block.
89
+ #
90
+ # @since 6.4.0
91
+ def with_session(options = {})
92
+ raise Mongoid::Errors::InvalidSessionUse.new(:invalid_session_nesting) if Threaded.get_session
93
+ session = persistence_context.client.start_session(options)
94
+ Threaded.set_session(session)
95
+ yield(session)
96
+ rescue Mongo::Error::InvalidSession => ex
97
+ if ex.message == Mongo::Session::SESSIONS_NOT_SUPPORTED
98
+ raise Mongoid::Errors::InvalidSessionUse.new(:sessions_not_supported)
99
+ end
100
+ raise Mongoid::Errors::InvalidSessionUse.new(:invalid_session_use)
101
+ ensure
102
+ Threaded.clear_session
103
+ end
104
+
105
+ private
106
+
107
+ def session
108
+ Threaded.get_session
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -61,6 +61,7 @@ module Mongoid
61
61
  # @since 4.0.0
62
62
  def reset_storage_options!
63
63
  self.storage_options = storage_options_defaults.dup
64
+ PersistenceContext.clear(self)
64
65
  end
65
66
 
66
67
  # Get the default storage options.
@@ -23,7 +23,7 @@ module Mongoid
23
23
  #
24
24
  # @since 3.0.0
25
25
  def aggregates(field)
26
- result = collection.find.aggregate(pipeline(field)).to_a
26
+ result = collection.find.aggregate(pipeline(field), session: session).to_a
27
27
  if result.empty?
28
28
  { "count" => 0, "sum" => nil, "avg" => nil, "min" => nil, "max" => nil }
29
29
  else
@@ -166,12 +166,12 @@ module Mongoid
166
166
  validate_out!
167
167
  cmd = command
168
168
  opts = { read: cmd.delete(:read).options } if cmd[:read]
169
- @map_reduce.database.command(cmd, opts || {}).first
169
+ @map_reduce.database.command(cmd, (opts || {}).merge(session: session)).first
170
170
  end
171
171
  alias :results :raw
172
172
 
173
173
  # Execute the map/reduce, returning the raw output.
174
- # Useful when you don't care about map/reduce's ouptut.
174
+ # Useful when you don't care about map/reduce's output.
175
175
  #
176
176
  # @example Run the map reduce
177
177
  # map_reduce.execute
@@ -249,6 +249,10 @@ module Mongoid
249
249
  def validate_out!
250
250
  raise Errors::NoMapReduceOutput.new({}) unless @map_reduce.out
251
251
  end
252
+
253
+ def session
254
+ criteria.send(:session)
255
+ end
252
256
  end
253
257
  end
254
258
  end
@@ -48,7 +48,8 @@ module Mongoid
48
48
  end
49
49
  unless removed.empty?
50
50
  collection.find(selector).update_one(
51
- positionally(selector, "$pullAll" => { path => removed })
51
+ positionally(selector, "$pullAll" => { path => removed }),
52
+ session: session
52
53
  )
53
54
  end
54
55
  deleted
@@ -303,7 +304,7 @@ module Mongoid
303
304
  updates["$set"].merge!(doc.atomic_updates["$set"] || {})
304
305
  doc.move_changes
305
306
  end
306
- collection.find(selector).update_one(updates) unless updates["$set"].empty?
307
+ collection.find(selector).update_one(updates, session: session) unless updates["$set"].empty?
307
308
  end
308
309
 
309
310
  # Get the limiting value.
@@ -444,6 +445,10 @@ module Mongoid
444
445
  doc._parent.remove_child(doc)
445
446
  doc.destroyed = true
446
447
  end
448
+
449
+ def session
450
+ @criteria.send(:session)
451
+ end
447
452
  end
448
453
  end
449
454
  end
@@ -95,7 +95,8 @@ module Mongoid
95
95
  def destroy
96
96
  each.inject(0) do |count, doc|
97
97
  doc.destroy
98
- count += 1
98
+ count += 1 if acknowledged_write?
99
+ count
99
100
  end
100
101
  end
101
102
  alias :destroy_all :destroy
@@ -340,7 +341,7 @@ module Mongoid
340
341
  @criteria, @klass, @cache = criteria, criteria.klass, criteria.options[:cache]
341
342
  @collection = @klass.collection
342
343
  criteria.send(:merge_type_selection)
343
- @view = collection.find(criteria.selector)
344
+ @view = collection.find(criteria.selector, session: session)
344
345
  apply_options
345
346
  end
346
347
 
@@ -704,6 +705,14 @@ module Mongoid
704
705
  yield(doc)
705
706
  documents.push(doc) if cacheable?
706
707
  end
708
+
709
+ def session
710
+ @criteria.send(:session)
711
+ end
712
+
713
+ def acknowledged_write?
714
+ collection.write_concern.nil? || collection.write_concern.acknowledged?
715
+ end
707
716
  end
708
717
  end
709
718
  end
@@ -28,6 +28,7 @@ module Mongoid
28
28
  include Scopable
29
29
  include Clients::Options
30
30
  include Options
31
+ include Clients::Sessions
31
32
 
32
33
  # Static array used to check with method missing - we only need to ever
33
34
  # instantiate once.
@@ -259,7 +259,9 @@ module Mongoid
259
259
  def prepare(field, operator, value)
260
260
  unless operator =~ /exists|type|size/
261
261
  value = value.__expand_complex__
262
- serializer = serializers[field]
262
+ field = field.to_s
263
+ name = aliases[field] || field
264
+ serializer = serializers[name]
263
265
  value = serializer ? serializer.evolve(value) : value
264
266
  end
265
267
  selection = { operator => value }
@@ -18,6 +18,7 @@ require "mongoid/errors/invalid_path"
18
18
  require "mongoid/errors/invalid_persistence_option"
19
19
  require "mongoid/errors/invalid_relation"
20
20
  require "mongoid/errors/invalid_scope"
21
+ require "mongoid/errors/invalid_session_use"
21
22
  require "mongoid/errors/invalid_set_polymorphic_relation"
22
23
  require "mongoid/errors/invalid_storage_options"
23
24
  require "mongoid/errors/invalid_storage_parent"
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Errors
4
+
5
+ # This error is raised when a session is attempted to be used with a model whose client cannot use it, if
6
+ # sessions are nested, or if the mongodb deployment doesn't support sessions.
7
+ #
8
+ # @since 6.4.0
9
+ class InvalidSessionUse < MongoidError
10
+
11
+ # Create the error.
12
+ #
13
+ # @example Create the error.
14
+ # InvalidSessionUse.new(:invalid_session_use)
15
+ #
16
+ # @param [ :invalid_sesion_use, :invalid_session_nesting ] error_type The type of session misuse.
17
+ #
18
+ # @since 6.4.0
19
+ def initialize(error_type)
20
+ super(compose_message(error_type.to_s))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -32,10 +32,10 @@ module Mongoid
32
32
  key, options = spec.key, spec.options
33
33
  if database = options[:database]
34
34
  with(database: database) do |klass|
35
- klass.collection.indexes.create_one(key, options.except(:database))
35
+ klass.collection.indexes(session: session).create_one(key, options.except(:database))
36
36
  end
37
37
  else
38
- collection.indexes.create_one(key, options)
38
+ collection.indexes(session: session).create_one(key, options)
39
39
  end
40
40
  end and true
41
41
  end
@@ -53,9 +53,9 @@ module Mongoid
53
53
  indexed_database_names.each do |database|
54
54
  with(database: database) do |klass|
55
55
  begin
56
- klass.collection.indexes.each do |spec|
56
+ klass.collection.indexes(session: session).each do |spec|
57
57
  unless spec["name"] == "_id_"
58
- klass.collection.indexes.drop_one(spec["key"])
58
+ klass.collection.indexes(session: session).drop_one(spec["key"])
59
59
  logger.info(
60
60
  "MONGOID: Removed index '#{spec["name"]}' on collection " +
61
61
  "'#{klass.collection.name}' in database '#{database}'."
@@ -204,7 +204,7 @@ module Mongoid
204
204
  def persist_atomic_operations(operations)
205
205
  if persisted? && operations
206
206
  selector = atomic_selector
207
- _root.collection.find(selector).update_one(positionally(selector, operations))
207
+ _root.collection.find(selector).update_one(positionally(selector, operations), session: session)
208
208
  end
209
209
  end
210
210
  end
@@ -61,7 +61,9 @@ module Mongoid
61
61
  _parent.insert
62
62
  else
63
63
  selector = _parent.atomic_selector
64
- _root.collection.find(selector).update_one(positionally(selector, atomic_inserts))
64
+ _root.collection.find(selector).update_one(
65
+ positionally(selector, atomic_inserts),
66
+ session: session)
65
67
  end
66
68
  end
67
69
 
@@ -76,7 +78,7 @@ module Mongoid
76
78
  #
77
79
  # @since 4.0.0
78
80
  def insert_as_root
79
- collection.insert_one(as_attributes)
81
+ collection.insert_one(as_attributes, session: session)
80
82
  end
81
83
 
82
84
  # Post process an insert, which sets the new record attribute to false
@@ -62,7 +62,9 @@ module Mongoid
62
62
  _parent.remove_child(self) if notifying_parent?(options)
63
63
  if _parent.persisted?
64
64
  selector = _parent.atomic_selector
65
- _root.collection.find(selector).update_one(positionally(selector, atomic_deletes))
65
+ _root.collection.find(selector).update_one(
66
+ positionally(selector, atomic_deletes),
67
+ session: session)
66
68
  end
67
69
  true
68
70
  end
@@ -78,7 +80,7 @@ module Mongoid
78
80
  #
79
81
  # @since 4.0.0
80
82
  def delete_as_root
81
- collection.find(atomic_selector).delete_one
83
+ collection.find(atomic_selector).delete_one(session: session)
82
84
  true
83
85
  end
84
86
 
@@ -48,11 +48,7 @@ module Mongoid
48
48
  #
49
49
  # @since 1.0.0
50
50
  def destroy_all(conditions = nil)
51
- selector = conditions || {}
52
- documents = where(selector)
53
- destroyed = documents.count
54
- documents.each { |doc| doc.destroy }
55
- destroyed
51
+ where(conditions || {}).destroy
56
52
  end
57
53
  end
58
54
  end
@@ -134,9 +134,9 @@ module Mongoid
134
134
  unless updates.empty?
135
135
  coll = collection(_root)
136
136
  selector = atomic_selector
137
- coll.find(selector).update_one(positionally(selector, updates))
137
+ coll.find(selector).update_one(positionally(selector, updates), session: session)
138
138
  conflicts.each_pair do |key, value|
139
- coll.find(selector).update_one(positionally(selector, { key => value }))
139
+ coll.find(selector).update_one(positionally(selector, { key => value }), session: session)
140
140
  end
141
141
  end
142
142
  end