mongoid 7.0.0.beta → 7.0.0

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