mongoid 7.0.0.beta → 7.0.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 (86) 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/association/embedded/batchable.rb +39 -13
  7. data/lib/mongoid/association/many.rb +4 -0
  8. data/lib/mongoid/association/referenced/has_many.rb +2 -0
  9. data/lib/mongoid/association/referenced/has_many/enumerable.rb +38 -7
  10. data/lib/mongoid/association/referenced/has_many/proxy.rb +3 -2
  11. data/lib/mongoid/association/referenced/syncable.rb +1 -1
  12. data/lib/mongoid/association/touchable.rb +1 -1
  13. data/lib/mongoid/atomic.rb +3 -3
  14. data/lib/mongoid/atomic/modifiers.rb +12 -8
  15. data/lib/mongoid/attributes.rb +20 -12
  16. data/lib/mongoid/attributes/dynamic.rb +2 -2
  17. data/lib/mongoid/changeable.rb +1 -1
  18. data/lib/mongoid/clients.rb +2 -0
  19. data/lib/mongoid/clients/sessions.rb +113 -0
  20. data/lib/mongoid/clients/storage_options.rb +1 -0
  21. data/lib/mongoid/composable.rb +1 -0
  22. data/lib/mongoid/contextual/aggregable/mongo.rb +1 -1
  23. data/lib/mongoid/contextual/atomic.rb +6 -3
  24. data/lib/mongoid/contextual/geo_near.rb +1 -1
  25. data/lib/mongoid/contextual/map_reduce.rb +6 -2
  26. data/lib/mongoid/contextual/memory.rb +25 -2
  27. data/lib/mongoid/contextual/mongo.rb +33 -14
  28. data/lib/mongoid/copyable.rb +2 -2
  29. data/lib/mongoid/criteria.rb +1 -0
  30. data/lib/mongoid/criteria/queryable/extensions/string.rb +1 -1
  31. data/lib/mongoid/criteria/queryable/mergeable.rb +3 -1
  32. data/lib/mongoid/errors.rb +1 -0
  33. data/lib/mongoid/errors/invalid_session_use.rb +24 -0
  34. data/lib/mongoid/extensions.rb +0 -4
  35. data/lib/mongoid/extensions/hash.rb +5 -2
  36. data/lib/mongoid/factory.rb +14 -5
  37. data/lib/mongoid/fields.rb +2 -2
  38. data/lib/mongoid/indexable.rb +4 -4
  39. data/lib/mongoid/matchable/and.rb +1 -1
  40. data/lib/mongoid/matchable/elem_match.rb +9 -3
  41. data/lib/mongoid/persistable.rb +2 -2
  42. data/lib/mongoid/persistable/creatable.rb +4 -2
  43. data/lib/mongoid/persistable/deletable.rb +4 -2
  44. data/lib/mongoid/persistable/destroyable.rb +1 -5
  45. data/lib/mongoid/persistable/updatable.rb +2 -2
  46. data/lib/mongoid/persistable/upsertable.rb +2 -1
  47. data/lib/mongoid/persistence_context.rb +5 -4
  48. data/lib/mongoid/query_cache.rb +5 -3
  49. data/lib/mongoid/reloadable.rb +1 -1
  50. data/lib/mongoid/serializable.rb +1 -1
  51. data/lib/mongoid/shardable.rb +1 -1
  52. data/lib/mongoid/tasks/database.rb +3 -2
  53. data/lib/mongoid/threaded.rb +38 -0
  54. data/lib/mongoid/traversable.rb +1 -1
  55. data/lib/mongoid/version.rb +1 -1
  56. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +4 -0
  57. data/spec/app/models/band.rb +1 -0
  58. data/spec/app/models/shipment_address.rb +1 -0
  59. data/spec/mongoid/association/macros_spec.rb +20 -0
  60. data/spec/mongoid/association/referenced/has_many/eager_spec.rb +15 -0
  61. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +229 -0
  62. data/spec/mongoid/atomic/modifiers_spec.rb +17 -17
  63. data/spec/mongoid/atomic_spec.rb +17 -17
  64. data/spec/mongoid/attributes_spec.rb +38 -2
  65. data/spec/mongoid/clients/sessions_spec.rb +325 -0
  66. data/spec/mongoid/contextual/memory_spec.rb +19 -0
  67. data/spec/mongoid/contextual/mongo_spec.rb +133 -0
  68. data/spec/mongoid/copyable_spec.rb +34 -0
  69. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +43 -0
  70. data/spec/mongoid/criteria/queryable/selectable_spec.rb +32 -3
  71. data/spec/mongoid/extensions/hash_spec.rb +18 -1
  72. data/spec/mongoid/factory_spec.rb +11 -0
  73. data/spec/mongoid/interceptable_spec.rb +3 -1
  74. data/spec/mongoid/matchable/elem_match_spec.rb +20 -0
  75. data/spec/mongoid/persistable/deletable_spec.rb +19 -0
  76. data/spec/mongoid/persistable/destroyable_spec.rb +19 -0
  77. data/spec/mongoid/persistable/savable_spec.rb +2 -2
  78. data/spec/mongoid/persistable/updatable_spec.rb +2 -2
  79. data/spec/mongoid/persistable_spec.rb +31 -16
  80. data/spec/mongoid/persistence_context_spec.rb +14 -0
  81. data/spec/mongoid/positional_spec.rb +10 -10
  82. data/spec/mongoid/query_cache_spec.rb +2 -16
  83. data/spec/mongoid/shardable_spec.rb +32 -12
  84. data/spec/spec_helper.rb +74 -0
  85. metadata +456 -446
  86. metadata.gz.sig +0 -0
@@ -41,7 +41,7 @@ module Mongoid
41
41
  class_eval <<-READER, __FILE__, __LINE__ + 1
42
42
  def #{name}
43
43
  attribute_will_change!(#{name.inspect})
44
- read_attribute(#{name.inspect})
44
+ read_raw_attribute(#{name.inspect})
45
45
  end
46
46
  READER
47
47
  end
@@ -147,7 +147,7 @@ module Mongoid
147
147
  getter = attr.reader
148
148
  define_dynamic_reader(getter)
149
149
  attribute_will_change!(attr.reader)
150
- read_attribute(getter)
150
+ read_raw_attribute(getter)
151
151
  end
152
152
  end
153
153
  end
@@ -227,7 +227,7 @@ module Mongoid
227
227
  # @since 2.3.0
228
228
  def attribute_will_change!(attr)
229
229
  unless changed_attributes.key?(attr)
230
- changed_attributes[attr] = read_attribute(attr).__deep_copy__
230
+ changed_attributes[attr] = read_raw_attribute(attr).__deep_copy__
231
231
  end
232
232
  end
233
233
 
@@ -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.
@@ -84,6 +84,7 @@ module Mongoid
84
84
  Validatable,
85
85
  Equality,
86
86
  Association::Referenced::Syncable,
87
+ Association::Macros,
87
88
  ActiveModel::Model,
88
89
  ActiveModel::Validations
89
90
  ]
@@ -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
@@ -106,10 +106,10 @@ module Mongoid
106
106
  view.update_many("$push" => collect_operations(pushes))
107
107
  end
108
108
 
109
- # Perform an atomic $pushAll operation on the matching documents.
109
+ # Perform an atomic $push/$each operation on the matching documents.
110
110
  #
111
111
  # @example Push the values to the matching docs.
112
- # context.push(members: [ "Alan", "Fletch" ])
112
+ # context.push_all(members: [ "Alan", "Fletch" ])
113
113
  #
114
114
  # @param [ Hash ] pushes The operations.
115
115
  #
@@ -117,7 +117,10 @@ module Mongoid
117
117
  #
118
118
  # @since 3.0.0
119
119
  def push_all(pushes)
120
- view.update_many("$pushAll" => collect_operations(pushes))
120
+ push_each_updates = collect_operations(pushes).each.inject({}) do |ops, (field, elements)|
121
+ ops.merge!(field => { '$each' => elements })
122
+ end
123
+ view.update_many("$push" => push_each_updates)
121
124
  end
122
125
 
123
126
  # Perform an atomic $rename of fields on the matching documents.
@@ -242,7 +242,7 @@ module Mongoid
242
242
  # @since 3.0.0
243
243
  def documents
244
244
  results["results"].map do |attributes|
245
- doc = Factory.from_db(criteria.klass, attributes["obj"], criteria.options[:fields])
245
+ doc = Factory.from_db(criteria.klass, attributes["obj"], criteria)
246
246
  doc.attributes["geo_near_distance"] = attributes["dis"]
247
247
  doc
248
248
  end
@@ -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
@@ -154,6 +155,22 @@ module Mongoid
154
155
  apply_options
155
156
  end
156
157
 
158
+ # Increment a value on all documents.
159
+ #
160
+ # @example Perform the increment.
161
+ # context.inc(likes: 10)
162
+ #
163
+ # @param [ Hash ] incs The operations.
164
+ #
165
+ # @return [ Enumerator ] The enumerator.
166
+ #
167
+ # @since 7.0.0
168
+ def inc(*args)
169
+ each do |document|
170
+ document.inc *args
171
+ end
172
+ end
173
+
157
174
  # Get the last document in the database for the criteria's selector.
158
175
  #
159
176
  # @example Get the last document.
@@ -303,7 +320,7 @@ module Mongoid
303
320
  updates["$set"].merge!(doc.atomic_updates["$set"] || {})
304
321
  doc.move_changes
305
322
  end
306
- collection.find(selector).update_one(updates) unless updates["$set"].empty?
323
+ collection.find(selector).update_one(updates, session: session) unless updates["$set"].empty?
307
324
  end
308
325
 
309
326
  # Get the limiting value.
@@ -444,6 +461,12 @@ module Mongoid
444
461
  doc._parent.remove_child(doc)
445
462
  doc.destroyed = true
446
463
  end
464
+
465
+ private
466
+
467
+ def session
468
+ @criteria.send(:session)
469
+ end
447
470
  end
448
471
  end
449
472
  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
@@ -255,12 +256,12 @@ module Mongoid
255
256
  try_cache(:first) do
256
257
  if sort = view.sort || ({ _id: 1 } unless opts[:id_sort] == :none)
257
258
  if raw_doc = view.sort(sort).limit(-1).first
258
- doc = Factory.from_db(klass, raw_doc, criteria.options[:fields])
259
+ doc = Factory.from_db(klass, raw_doc, criteria)
259
260
  eager_load([doc]).first
260
261
  end
261
262
  else
262
263
  if raw_doc = view.limit(-1).first
263
- doc = Factory.from_db(klass, raw_doc, criteria.options[:fields])
264
+ doc = Factory.from_db(klass, raw_doc, criteria)
264
265
  eager_load([doc]).first
265
266
  end
266
267
  end
@@ -276,7 +277,7 @@ module Mongoid
276
277
  def find_first
277
278
  return documents.first if cached? && cache_loaded?
278
279
  if raw_doc = view.first
279
- doc = Factory.from_db(klass, raw_doc, criteria.options[:fields])
280
+ doc = Factory.from_db(klass, raw_doc, criteria)
280
281
  eager_load([doc]).first
281
282
  end
282
283
  end
@@ -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
 
@@ -367,7 +368,7 @@ module Mongoid
367
368
  try_cache(:last) do
368
369
  with_inverse_sorting(opts) do
369
370
  if raw_doc = view.limit(-1).first
370
- doc = Factory.from_db(klass, raw_doc, criteria.options[:fields])
371
+ doc = Factory.from_db(klass, raw_doc, criteria)
371
372
  eager_load([doc]).first
372
373
  end
373
374
  end
@@ -486,12 +487,16 @@ module Mongoid
486
487
  # context.update({ "$set" => { name: "Smiths" }})
487
488
  #
488
489
  # @param [ Hash ] attributes The new attributes for the document.
490
+ # @param [ Hash ] opts The update operation options.
491
+ #
492
+ # @option opts [ Array ] :array_filters A set of filters specifying to which array elements
493
+ # an update should apply.
489
494
  #
490
495
  # @return [ nil, false ] False if no attributes were provided.
491
496
  #
492
497
  # @since 3.0.0
493
- def update(attributes = nil)
494
- update_documents(attributes)
498
+ def update(attributes = nil, opts = {})
499
+ update_documents(attributes, :update_one, opts)
495
500
  end
496
501
 
497
502
  # Update all the matching documents atomically.
@@ -500,12 +505,16 @@ module Mongoid
500
505
  # context.update_all({ "$set" => { name: "Smiths" }})
501
506
  #
502
507
  # @param [ Hash ] attributes The new attributes for each document.
508
+ # @param [ Hash ] opts The update operation options.
509
+ #
510
+ # @option opts [ Array ] :array_filters A set of filters specifying to which array elements
511
+ # an update should apply.
503
512
  #
504
513
  # @return [ nil, false ] False if no attributes were provided.
505
514
  #
506
515
  # @since 3.0.0
507
- def update_all(attributes = nil)
508
- update_documents(attributes, :update_many)
516
+ def update_all(attributes = nil, opts = {})
517
+ update_documents(attributes, :update_many, opts)
509
518
  end
510
519
 
511
520
  private
@@ -541,10 +550,10 @@ module Mongoid
541
550
  # @return [ true, false ] If the update succeeded.
542
551
  #
543
552
  # @since 3.0.4
544
- def update_documents(attributes, method = :update_one)
553
+ def update_documents(attributes, method = :update_one, opts = {})
545
554
  return false unless attributes
546
555
  attributes = Hash[attributes.map { |k, v| [klass.database_field_name(k.to_s), v] }]
547
- view.send(method, attributes.__consolidate__(klass))
556
+ view.send(method, attributes.__consolidate__(klass), opts)
548
557
  end
549
558
 
550
559
  # Apply the field limitations.
@@ -674,7 +683,7 @@ module Mongoid
674
683
  def documents_for_iteration
675
684
  return documents if cached? && !documents.empty?
676
685
  return view unless eager_loadable?
677
- docs = view.map{ |doc| Factory.from_db(klass, doc, criteria.options[:fields]) }
686
+ docs = view.map{ |doc| Factory.from_db(klass, doc, criteria) }
678
687
  eager_load(docs)
679
688
  end
680
689
 
@@ -692,10 +701,20 @@ module Mongoid
692
701
  # @since 3.0.0
693
702
  def yield_document(document, &block)
694
703
  doc = document.respond_to?(:_id) ?
695
- document : Factory.from_db(klass, document, criteria.options[:fields])
704
+ document : Factory.from_db(klass, document, criteria)
696
705
  yield(doc)
697
706
  documents.push(doc) if cacheable?
698
707
  end
708
+
709
+ private
710
+
711
+ def session
712
+ @criteria.send(:session)
713
+ end
714
+
715
+ def acknowledged_write?
716
+ collection.write_concern.nil? || collection.write_concern.acknowledged?
717
+ end
699
718
  end
700
719
  end
701
720
  end
@@ -73,8 +73,8 @@ module Mongoid
73
73
  next unless attrs.present? && attrs[association.key].present?
74
74
 
75
75
  if association.is_a?(Association::Embedded::EmbedsMany)
76
- attrs[association.key].each do |attr|
77
- process_localized_attributes(association.klass, attr)
76
+ attrs[association.name.to_s].each_with_index do |attr, index|
77
+ process_localized_attributes(send(association.name)[index].class, attr)
78
78
  end
79
79
  else
80
80
  process_localized_attributes(association.klass, attrs[association.key])
@@ -27,6 +27,7 @@ module Mongoid
27
27
  include Modifiable
28
28
  include Scopable
29
29
  include Clients::Options
30
+ include Clients::Sessions
30
31
  include Options
31
32
 
32
33
  # Static array used to check with method missing - we only need to ever