mongoid 6.3.0 → 6.4.0

Sign up to get free protection for your applications and to get access to all the features.
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