mongoid 4.0.0 → 4.0.1

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -1
  3. data/README.md +6 -2
  4. data/lib/config/locales/en.yml +2 -2
  5. data/lib/mongoid/atomic.rb +2 -2
  6. data/lib/mongoid/attributes.rb +2 -0
  7. data/lib/mongoid/contextual/aggregable/memory.rb +2 -2
  8. data/lib/mongoid/contextual/mongo.rb +3 -3
  9. data/lib/mongoid/criteria/#findable.rb# +141 -0
  10. data/lib/mongoid/document.rb +5 -5
  11. data/lib/mongoid/query_cache.rb +10 -2
  12. data/lib/mongoid/railtie.rb +2 -15
  13. data/lib/mongoid/railties/database.rake +1 -1
  14. data/lib/mongoid/relations/accessors.rb +2 -2
  15. data/lib/mongoid/relations/binding.rb +1 -1
  16. data/lib/mongoid/relations/bindings/referenced/many_to_many.rb +1 -1
  17. data/lib/mongoid/relations/builders/embedded/one.rb +1 -1
  18. data/lib/mongoid/relations/builders/nested_attributes/one.rb +1 -1
  19. data/lib/mongoid/relations/counter_cache.rb +2 -2
  20. data/lib/mongoid/relations/one.rb +1 -1
  21. data/lib/mongoid/relations/referenced/many.rb +4 -4
  22. data/lib/mongoid/relations/referenced/many_to_many.rb +5 -5
  23. data/lib/mongoid/relations/synchronization.rb +4 -4
  24. data/lib/mongoid/relations/targets/enumerable.rb +10 -10
  25. data/lib/mongoid/reloadable.rb +3 -3
  26. data/lib/mongoid/threaded.rb +26 -15
  27. data/lib/mongoid/traversable.rb +6 -2
  28. data/lib/mongoid/validatable/uniqueness.rb +3 -3
  29. data/lib/mongoid/version.rb +1 -1
  30. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +1 -1
  31. data/spec/app/models/id_key.rb +6 -0
  32. data/spec/mongoid/#atomic_spec.rb# +365 -0
  33. data/spec/mongoid/attributes_spec.rb +36 -0
  34. data/spec/mongoid/contextual/atomic_spec.rb +7 -13
  35. data/spec/mongoid/criteria_spec.rb +86 -75
  36. data/spec/mongoid/document_spec.rb +2 -2
  37. data/spec/mongoid/findable_spec.rb +2 -2
  38. data/spec/mongoid/positional_spec.rb +5 -10
  39. data/spec/mongoid/query_cache_spec.rb +32 -0
  40. data/spec/mongoid/relations/referenced/many_spec.rb +23 -0
  41. data/spec/mongoid/reloadable_spec.rb +23 -0
  42. data/spec/spec_helper.rb +2 -0
  43. metadata +7 -2
@@ -68,7 +68,7 @@ module Mongoid
68
68
  # @since 2.0.0
69
69
  def acceptable_id?
70
70
  id = convert_id(existing.class, attributes[:id])
71
- existing.id == id || id.nil? || (existing.id != id && update_only?)
71
+ existing._id == id || id.nil? || (existing._id != id && update_only?)
72
72
  end
73
73
 
74
74
  # Can the existing relation be deleted?
@@ -108,7 +108,7 @@ module Mongoid
108
108
  record[cache_column] = (record[cache_column] || 0) + 1
109
109
 
110
110
  if record.persisted?
111
- record.class.increment_counter(cache_column, record.id)
111
+ record.class.increment_counter(cache_column, record._id)
112
112
  record.remove_change(cache_column)
113
113
  end
114
114
  end
@@ -119,7 +119,7 @@ module Mongoid
119
119
  record[cache_column] = (record[cache_column] || 0) - 1
120
120
 
121
121
  if record.persisted?
122
- record.class.decrement_counter(cache_column, record.id)
122
+ record.class.decrement_counter(cache_column, record._id)
123
123
  record.remove_change(cache_column)
124
124
  end
125
125
  end
@@ -53,7 +53,7 @@ module Mongoid
53
53
  #
54
54
  # @since 4.0.0
55
55
  def __evolve_object_id__
56
- target.id
56
+ target._id
57
57
  end
58
58
  end
59
59
  end
@@ -281,10 +281,10 @@ module Mongoid
281
281
  def substitute(replacement)
282
282
  if replacement
283
283
  new_docs, docs = replacement.compact, []
284
- new_ids = new_docs.map { |doc| doc.id }
284
+ new_ids = new_docs.map { |doc| doc._id }
285
285
  remove_not_in(new_ids)
286
286
  new_docs.each do |doc|
287
- docs.push(doc) if doc.send(foreign_key) != base.id
287
+ docs.push(doc) if doc.send(foreign_key) != base._id
288
288
  end
289
289
  concat(docs)
290
290
  else
@@ -304,7 +304,7 @@ module Mongoid
304
304
  # @since 2.4.0
305
305
  def unscoped
306
306
  klass.unscoped.where(
307
- foreign_key => Conversions.flag(base.id, __metadata)
307
+ foreign_key => Conversions.flag(base._id, __metadata)
308
308
  )
309
309
  end
310
310
 
@@ -492,7 +492,7 @@ module Mongoid
492
492
  removed.update_all(foreign_key => nil)
493
493
  end
494
494
  in_memory.each do |doc|
495
- if !ids.include?(doc.id)
495
+ if !ids.include?(doc._id)
496
496
  unbind_one(doc)
497
497
  target.delete(doc)
498
498
  if __metadata.destructive?
@@ -55,12 +55,12 @@ module Mongoid
55
55
  next unless doc
56
56
  append(doc)
57
57
  if persistable? || _creating?
58
- ids[doc.id] = true
58
+ ids[doc._id] = true
59
59
  save_or_delay(doc, docs, inserts)
60
60
  else
61
61
  existing = base.send(foreign_key)
62
- unless existing.include?(doc.id)
63
- existing.push(doc.id) and unsynced(base, foreign_key)
62
+ unless existing.include?(doc._id)
63
+ existing.push(doc._id) and unsynced(base, foreign_key)
64
64
  end
65
65
  end
66
66
  end
@@ -90,7 +90,7 @@ module Mongoid
90
90
  # @since 2.0.0.beta.1
91
91
  def build(attributes = {}, type = nil)
92
92
  doc = Factory.build(type || klass, attributes)
93
- base.send(foreign_key).push(doc.id)
93
+ base.send(foreign_key).push(doc._id)
94
94
  append(doc)
95
95
  doc.apply_post_processed_defaults
96
96
  unsynced(doc, inverse_foreign_key)
@@ -134,7 +134,7 @@ module Mongoid
134
134
  execute_callback :before_remove, doc
135
135
  end
136
136
  unless __metadata.forced_nil_inverse?
137
- criteria.pull(inverse_foreign_key => base.id)
137
+ criteria.pull(inverse_foreign_key => base._id)
138
138
  end
139
139
  if persistable?
140
140
  base.set(foreign_key => base.send(foreign_key).clear)
@@ -61,7 +61,7 @@ module Mongoid
61
61
  def remove_inverse_keys(meta)
62
62
  foreign_keys = send(meta.foreign_key)
63
63
  unless foreign_keys.nil? || foreign_keys.empty?
64
- meta.criteria(foreign_keys, self.class).pull(meta.inverse_foreign_key => id)
64
+ meta.criteria(foreign_keys, self.class).pull(meta.inverse_foreign_key => _id)
65
65
  end
66
66
  end
67
67
 
@@ -87,15 +87,15 @@ module Mongoid
87
87
  # had occurred.
88
88
  if meta.autosave?
89
89
  send(meta.name).in_memory.each do |doc|
90
- adds.delete_one(doc.id)
90
+ adds.delete_one(doc._id)
91
91
  end
92
92
  end
93
93
 
94
94
  unless adds.empty?
95
- meta.criteria(adds, self.class).without_options.add_to_set(meta.inverse_foreign_key => id)
95
+ meta.criteria(adds, self.class).without_options.add_to_set(meta.inverse_foreign_key => _id)
96
96
  end
97
97
  unless subs.empty?
98
- meta.criteria(subs, self.class).without_options.pull(meta.inverse_foreign_key => id)
98
+ meta.criteria(subs, self.class).without_options.pull(meta.inverse_foreign_key => _id)
99
99
  end
100
100
  end
101
101
  end
@@ -59,7 +59,7 @@ module Mongoid
59
59
  #
60
60
  # @since 2.1.0
61
61
  def <<(document)
62
- _added[document.id] = document
62
+ _added[document._id] = document
63
63
  self
64
64
  end
65
65
  alias :push :<<
@@ -110,9 +110,9 @@ module Mongoid
110
110
  #
111
111
  # @since 2.1.0
112
112
  def delete(document)
113
- doc = (_loaded.delete(document.id) || _added.delete(document.id))
113
+ doc = (_loaded.delete(document._id) || _added.delete(document._id))
114
114
  unless doc
115
- if _unloaded && _unloaded.where(_id: document.id).exists?
115
+ if _unloaded && _unloaded.where(_id: document._id).exists?
116
116
  yield(document) if block_given?
117
117
  return document
118
118
  end
@@ -128,7 +128,7 @@ module Mongoid
128
128
  #
129
129
  # @example Delete all matching documents.
130
130
  # enumerable.delete_if do |doc|
131
- # dod.id == id
131
+ # dod._id == _id
132
132
  # end
133
133
  #
134
134
  # @return [ Array<Document> ] The remaining docs.
@@ -138,8 +138,8 @@ module Mongoid
138
138
  load_all!
139
139
  deleted = in_memory.select(&block)
140
140
  deleted.each do |doc|
141
- _loaded.delete(doc.id)
142
- _added.delete(doc.id)
141
+ _loaded.delete(doc._id)
142
+ _added.delete(doc._id)
143
143
  end
144
144
  self
145
145
  end
@@ -179,8 +179,8 @@ module Mongoid
179
179
  end
180
180
  else
181
181
  unloaded_documents.each do |doc|
182
- document = _added.delete(doc.id) || _loaded.delete(doc.id) || doc
183
- _loaded[document.id] = document
182
+ document = _added.delete(doc._id) || _loaded.delete(doc._id) || doc
183
+ _loaded[document._id] = document
184
184
  yield(document)
185
185
  end
186
186
  end
@@ -237,7 +237,7 @@ module Mongoid
237
237
  else
238
238
  @_added, @executed = {}, true
239
239
  @_loaded = target.inject({}) do |_target, doc|
240
- _target[doc.id] = doc
240
+ _target[doc._id] = doc
241
241
  _target
242
242
  end
243
243
  end
@@ -255,7 +255,7 @@ module Mongoid
255
255
  # @since 3.0.0
256
256
  def include?(doc)
257
257
  return super unless _unloaded
258
- _unloaded.where(_id: doc.id).exists? || _added.has_key?(doc.id)
258
+ _unloaded.where(_id: doc._id).exists? || _added.has_key?(doc._id)
259
259
  end
260
260
 
261
261
  # Inspection will just inspect the entries for nice array-style
@@ -21,7 +21,7 @@ module Mongoid
21
21
  def reload
22
22
  reloaded = _reload
23
23
  if Mongoid.raise_not_found_error && reloaded.empty?
24
- raise Errors::DocumentNotFound.new(self.class, id, id)
24
+ raise Errors::DocumentNotFound.new(self.class, _id, _id)
25
25
  end
26
26
  @attributes = reloaded
27
27
  @attributes_before_type_cast = {}
@@ -57,7 +57,7 @@ module Mongoid
57
57
  #
58
58
  # @since 2.3.2
59
59
  def reload_root_document
60
- {}.merge(with(read: :primary).collection.find(_id: id).one || {})
60
+ {}.merge(with(read: :primary).collection.find(_id: _id).one || {})
61
61
  end
62
62
 
63
63
  # Reload the embedded document.
@@ -70,7 +70,7 @@ module Mongoid
70
70
  # @since 2.3.2
71
71
  def reload_embedded_document
72
72
  extract_embedded_attributes({}.merge(
73
- _root.with(read: :primary).collection.find(_id: _root.id).one
73
+ _root.with(read: :primary).collection.find(_id: _root._id).one
74
74
  ))
75
75
  end
76
76
 
@@ -6,6 +6,17 @@ module Mongoid
6
6
  # This module contains logic for easy access to objects that have a lifecycle
7
7
  # on the current thread.
8
8
  module Threaded
9
+ DATABASE_OVERRIDE_KEY = "[mongoid]:db-override"
10
+ SESSIONS_KEY = "[mongoid]:sessions"
11
+ SESSION_OVERRIDE_KEY = "[mongoid]:session-override"
12
+ SCOPE_STACK_KEY = "[mongoid]:scope-stack"
13
+ AUTOSAVES_KEY = "[mongoid]:autosaves"
14
+ VALIDATIONS_KEY = "[mongoid]:validations"
15
+
16
+ STACK_KEYS = Hash.new do |hash, key|
17
+ hash[key] = "[mongoid]:#{key}-stack"
18
+ end
19
+
9
20
  extend self
10
21
 
11
22
  # Begin entry into a named thread local stack.
@@ -31,7 +42,7 @@ module Mongoid
31
42
  #
32
43
  # @since 3.0.0
33
44
  def database_override
34
- Thread.current["[mongoid]:db-override"]
45
+ Thread.current[DATABASE_OVERRIDE_KEY]
35
46
  end
36
47
 
37
48
  # Set the global database override.
@@ -45,7 +56,7 @@ module Mongoid
45
56
  #
46
57
  # @since 3.0.0
47
58
  def database_override=(name)
48
- Thread.current["[mongoid]:db-override"] = name
59
+ Thread.current[DATABASE_OVERRIDE_KEY] = name
49
60
  end
50
61
 
51
62
  # Get the database sessions from the current thread.
@@ -57,7 +68,7 @@ module Mongoid
57
68
  #
58
69
  # @since 3.0.0
59
70
  def sessions
60
- Thread.current["[mongoid]:sessions"] ||= {}
71
+ Thread.current[SESSIONS_KEY] ||= {}
61
72
  end
62
73
 
63
74
  # Are in the middle of executing the named stack
@@ -99,7 +110,7 @@ module Mongoid
99
110
  #
100
111
  # @since 2.4.0
101
112
  def stack(name)
102
- Thread.current["[mongoid]:#{name}-stack"] ||= []
113
+ Thread.current[STACK_KEYS[name]] ||= []
103
114
  end
104
115
 
105
116
  # Begin autosaving a document on the current thread.
@@ -111,7 +122,7 @@ module Mongoid
111
122
  #
112
123
  # @since 3.0.0
113
124
  def begin_autosave(document)
114
- autosaves_for(document.class).push(document.id)
125
+ autosaves_for(document.class).push(document._id)
115
126
  end
116
127
 
117
128
  # Begin validating a document on the current thread.
@@ -123,7 +134,7 @@ module Mongoid
123
134
  #
124
135
  # @since 2.1.9
125
136
  def begin_validate(document)
126
- validations_for(document.class).push(document.id)
137
+ validations_for(document.class).push(document._id)
127
138
  end
128
139
 
129
140
  # Exit autosaving a document on the current thread.
@@ -135,7 +146,7 @@ module Mongoid
135
146
  #
136
147
  # @since 3.0.0
137
148
  def exit_autosave(document)
138
- autosaves_for(document.class).delete_one(document.id)
149
+ autosaves_for(document.class).delete_one(document._id)
139
150
  end
140
151
 
141
152
  # Exit validating a document on the current thread.
@@ -147,7 +158,7 @@ module Mongoid
147
158
  #
148
159
  # @since 2.1.9
149
160
  def exit_validate(document)
150
- validations_for(document.class).delete_one(document.id)
161
+ validations_for(document.class).delete_one(document._id)
151
162
  end
152
163
 
153
164
  # Get the global session override.
@@ -159,7 +170,7 @@ module Mongoid
159
170
  #
160
171
  # @since 3.0.0
161
172
  def session_override
162
- Thread.current["[mongoid]:session-override"]
173
+ Thread.current[SESSION_OVERRIDE_KEY]
163
174
  end
164
175
 
165
176
  # Set the global session override.
@@ -173,7 +184,7 @@ module Mongoid
173
184
  #
174
185
  # @since 3.0.0
175
186
  def session_override=(name)
176
- Thread.current["[mongoid]:session-override"] = name
187
+ Thread.current[SESSION_OVERRIDE_KEY] = name
177
188
  end
178
189
 
179
190
  # Get the mongoid scope stack for chained criteria.
@@ -185,7 +196,7 @@ module Mongoid
185
196
  #
186
197
  # @since 2.1.0
187
198
  def scope_stack
188
- Thread.current["[mongoid]:scope-stack"] ||= {}
199
+ Thread.current[SCOPE_STACK_KEY] ||= {}
189
200
  end
190
201
 
191
202
  # Is the document autosaved on the current thread?
@@ -199,7 +210,7 @@ module Mongoid
199
210
  #
200
211
  # @since 2.1.9
201
212
  def autosaved?(document)
202
- autosaves_for(document.class).include?(document.id)
213
+ autosaves_for(document.class).include?(document._id)
203
214
  end
204
215
 
205
216
  # Is the document validated on the current thread?
@@ -213,7 +224,7 @@ module Mongoid
213
224
  #
214
225
  # @since 2.1.9
215
226
  def validated?(document)
216
- validations_for(document.class).include?(document.id)
227
+ validations_for(document.class).include?(document._id)
217
228
  end
218
229
 
219
230
  # Get all autosaves on the current thread.
@@ -225,7 +236,7 @@ module Mongoid
225
236
  #
226
237
  # @since 3.0.0
227
238
  def autosaves
228
- Thread.current["[mongoid]:autosaves"] ||= {}
239
+ Thread.current[AUTOSAVES_KEY] ||= {}
229
240
  end
230
241
 
231
242
  # Get all validations on the current thread.
@@ -237,7 +248,7 @@ module Mongoid
237
248
  #
238
249
  # @since 2.1.9
239
250
  def validations
240
- Thread.current["[mongoid]:validations"] ||= {}
251
+ Thread.current[VALIDATIONS_KEY] ||= {}
241
252
  end
242
253
 
243
254
  # Get all autosaves on the current thread for the class.
@@ -7,8 +7,12 @@ module Mongoid
7
7
  module Traversable
8
8
  extend ActiveSupport::Concern
9
9
 
10
- included do
11
- attr_accessor :_parent
10
+ def _parent
11
+ @__parent
12
+ end
13
+
14
+ def _parent=(p)
15
+ @__parent = p
12
16
  end
13
17
 
14
18
  # Get all child +Documents+ to this +Document+, going n levels deep if
@@ -130,7 +130,7 @@ module Mongoid
130
130
  end
131
131
 
132
132
  if document.persisted? && !document.embedded?
133
- selector.merge!(_id: { "$ne" => document.id })
133
+ selector.merge!(_id: { "$ne" => document._id })
134
134
  end
135
135
  selector
136
136
  end
@@ -164,7 +164,7 @@ module Mongoid
164
164
  # @return [ Criteria ] The scoped criteria.
165
165
  #
166
166
  # @since 2.3.0
167
- def scope(criteria, document, attribute)
167
+ def scope(criteria, document, _attribute)
168
168
  Array.wrap(options[:scope]).each do |item|
169
169
  name = document.database_field_name(item)
170
170
  criteria = criteria.where(item => document.attributes[name])
@@ -227,7 +227,7 @@ module Mongoid
227
227
  def to_validate(document, attribute, value)
228
228
  metadata = document.relations[attribute.to_s]
229
229
  if metadata && metadata.stores_foreign_key?
230
- [ metadata.foreign_key, value && value.id ]
230
+ [ metadata.foreign_key, value && value._id ]
231
231
  else
232
232
  [ attribute, value ]
233
233
  end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid
3
- VERSION = "4.0.0"
3
+ VERSION = "4.0.1"
4
4
  end
@@ -32,7 +32,7 @@ development:
32
32
  # Includes the root model name in json serialization. (default: false)
33
33
  # include_root_in_json: false
34
34
 
35
- # Include the _type field in serializaion. (default: false)
35
+ # Include the _type field in serialization. (default: false)
36
36
  # include_type_for_serialization: false
37
37
 
38
38
  # Preload all models in development, needed when models use
@@ -0,0 +1,6 @@
1
+ class IdKey
2
+ include Mongoid::Document
3
+ field :key
4
+ alias_method :id, :key
5
+ alias_method :id=, :key=
6
+ end
@@ -0,0 +1,365 @@
1
+ require "spec_helper"
2
+
3
+ describe Mongoid::Atomic do
4
+
5
+ describe "#add_atomic_pull" do
6
+
7
+ let!(:person) do
8
+ Person.create
9
+ end
10
+
11
+ let(:address) do
12
+ person.addresses.create
13
+ end
14
+
15
+ let(:location) do
16
+ address.locations.create
17
+ end
18
+
19
+ before do
20
+ person.add_atomic_pull(address)
21
+ end
22
+
23
+ it "adds the document to the delayed atomic pulls" do
24
+ expect(person.delayed_atomic_pulls["addresses"]).to eq([ address ])
25
+ end
26
+
27
+ it "flags the document for destruction" do
28
+ expect(address).to be_flagged_for_destroy
29
+ end
30
+ end
31
+
32
+ describe "#add_atomic_unset" do
33
+
34
+ let!(:person) do
35
+ Person.new
36
+ end
37
+
38
+ let(:name) do
39
+ person.build_name
40
+ end
41
+
42
+ before do
43
+ person.add_atomic_unset(name)
44
+ end
45
+
46
+ it "adds the document to the delayed atomic unsets" do
47
+ expect(person.delayed_atomic_unsets["name"]).to eq([ name ])
48
+ end
49
+
50
+ it "flags the document for destruction" do
51
+ expect(name).to be_flagged_for_destroy
52
+ end
53
+ end
54
+
55
+ describe "#atomic_updates" do
56
+
57
+ context "when the document is persisted" do
58
+
59
+ let(:person) do
60
+ Person.create
61
+ end
62
+
63
+ context "when the document is modified" do
64
+
65
+ before do
66
+ person.title = "Sir"
67
+ end
68
+
69
+ it "returns the atomic updates" do
70
+ expect(person.atomic_updates).to eq({ "$set" => { "title" => "Sir" }})
71
+ end
72
+
73
+ context "when an embeds many child is added" do
74
+
75
+ let!(:address) do
76
+ person.addresses.build(street: "Oxford St")
77
+ end
78
+
79
+ it "returns a $set and $pushAll for modifications" do
80
+ expect(person.atomic_updates).to eq(
81
+ {
82
+ "$set" => { "title" => "Sir" },
83
+ "$pushAll" => { "addresses" => [
84
+ { "_id" => "oxford-st", "street" => "Oxford St" }
85
+ ]}
86
+ }
87
+ )
88
+ end
89
+ end
90
+
91
+ context "when an embeds one child is added" do
92
+
93
+ let!(:name) do
94
+ person.build_name(first_name: "Lionel")
95
+ end
96
+
97
+ it "returns a $set for modifications" do
98
+ expect(person.atomic_updates).to eq(
99
+ {
100
+ "$set" => {
101
+ "title" => "Sir",
102
+ "name" => { "_id" => "Lionel-", "first_name" => "Lionel" }
103
+ }
104
+ }
105
+ )
106
+ end
107
+ end
108
+
109
+ context "when an existing embeds many gets modified" do
110
+
111
+ let!(:address) do
112
+ person.addresses.create(street: "Oxford St")
113
+ end
114
+
115
+ before do
116
+ address.street = "Bond St"
117
+ end
118
+
119
+ context "when asking for the updates from the root" do
120
+
121
+ it "returns the $set with correct position and modifications" do
122
+ expect(person.atomic_updates).to eq(
123
+ { "$set" => { "title" => "Sir", "addresses.0.street" => "Bond St" }}
124
+ )
125
+ end
126
+ end
127
+
128
+ context "when asking for the updates from the child" do
129
+
130
+ it "returns the $set with correct position and modifications" do
131
+ expect(address.atomic_updates).to eq(
132
+ { "$set" => { "addresses.0.street" => "Bond St" }}
133
+ )
134
+ end
135
+ end
136
+
137
+ context "when an existing 2nd level embedded child gets modified" do
138
+
139
+ let!(:location) do
140
+ address.locations.create(name: "Home")
141
+ end
142
+
143
+ before do
144
+ location.name = "Work"
145
+ end
146
+
147
+ context "when asking for the updates from the root" do
148
+
149
+ it "returns the $set with correct positions and modifications" do
150
+ expect(person.atomic_updates).to eq(
151
+ { "$set" => {
152
+ "title" => "Sir",
153
+ "addresses.0.street" => "Bond St",
154
+ "addresses.0.locations.0.name" => "Work" }
155
+ }
156
+ )
157
+ end
158
+ end
159
+
160
+ context "when asking for the updates from the 1st level child" do
161
+
162
+ it "returns the $set with correct positions and modifications" do
163
+ expect(address.atomic_updates).to eq(
164
+ { "$set" => {
165
+ "addresses.0.street" => "Bond St",
166
+ "addresses.0.locations.0.name" => "Work" }
167
+ }
168
+ )
169
+ end
170
+ end
171
+
172
+ context "when asking for the updates from the 2nd level child" do
173
+
174
+ it "returns the $set with correct positions and modifications" do
175
+ expect(location.atomic_updates).to eq(
176
+ { "$set" => {
177
+ "addresses.0.locations.0.name" => "Work" }
178
+ }
179
+ )
180
+ end
181
+ end
182
+ end
183
+
184
+ context "when a 2nd level embedded child gets added" do
185
+
186
+ let!(:location) do
187
+ address.locations.build(name: "Home")
188
+ end
189
+
190
+ context "when asking for the updates from the root" do
191
+
192
+ it "returns the $set with correct positions and modifications" do
193
+ expect(person.atomic_updates).to eq(
194
+ {
195
+ "$set" => {
196
+ "title" => "Sir",
197
+ "addresses.0.street" => "Bond St"
198
+ },
199
+ conflicts: {
200
+ "$pushAll" => {
201
+ "addresses.0.locations" => [{ "_id" => location.id, "name" => "Home" }]
202
+ }
203
+ }
204
+ }
205
+ )
206
+ end
207
+ end
208
+
209
+ context "when asking for the updates from the 1st level child" do
210
+
211
+ it "returns the $set with correct positions and modifications" do
212
+ expect(address.atomic_updates).to eq(
213
+ {
214
+ "$set" => {
215
+ "addresses.0.street" => "Bond St"
216
+ },
217
+ conflicts: {
218
+ "$pushAll" => {
219
+ "addresses.0.locations" => [{ "_id" => location.id, "name" => "Home" }]
220
+ }
221
+ }
222
+ }
223
+ )
224
+ end
225
+ end
226
+ end
227
+
228
+ context "when an embedded child gets unset" do
229
+
230
+ before do
231
+ person.attributes = { addresses: nil }
232
+ end
233
+
234
+ let(:updates) do
235
+ person.atomic_updates
236
+ end
237
+
238
+ it "returns the $set for the first level and $unset for other." do
239
+ expect(updates).to eq({
240
+ "$unset" => { "addresses" => true },
241
+ "$set" => { "title" => "Sir" }
242
+ })
243
+ end
244
+ end
245
+
246
+ context "when adding a new second level child" do
247
+
248
+ let!(:new_address) do
249
+ person.addresses.build(street: "Another")
250
+ end
251
+
252
+ let!(:location) do
253
+ new_address.locations.build(name: "Home")
254
+ end
255
+
256
+ context "when asking for the updates from the root document" do
257
+
258
+ it "returns the $set for 1st level and other for the 2nd level" do
259
+ expect(person.atomic_updates).to eq(
260
+ {
261
+ "$set" => {
262
+ "title" => "Sir",
263
+ "addresses.0.street" => "Bond St"
264
+ },
265
+ conflicts: {
266
+ "$pushAll" => {
267
+ "addresses" => [{
268
+ "_id" => new_address.id,
269
+ "street" => "Another",
270
+ "locations" => [
271
+ "_id" => location.id,
272
+ "name" => "Home"
273
+ ]
274
+ }]
275
+ }
276
+ }
277
+ }
278
+ )
279
+ end
280
+ end
281
+
282
+ context "when asking for the updates from the 1st level document" do
283
+
284
+ it "returns the $set for 1st level and other for the 2nd level" do
285
+ expect(address.atomic_updates).to eq(
286
+ { "$set" => { "addresses.0.street" => "Bond St" }}
287
+ )
288
+ end
289
+ end
290
+ end
291
+
292
+ context "when adding a new child beetween two existing and updating one of them" do
293
+
294
+ let!(:new_address) do
295
+ person.addresses.build(street: "Ipanema")
296
+ end
297
+
298
+ let!(:location) do
299
+ new_address.locations.build(name: "Home")
300
+ end
301
+
302
+ before do
303
+ person.addresses[0] = new_address
304
+ person.addresses[1] = address
305
+ end
306
+
307
+ it "returns the $set for 1st and 2nd level and other for the 3nd level" do
308
+ expect(person.atomic_updates).to eq(
309
+ {
310
+ "$set" => {
311
+ "title" => "Sir"
312
+ },
313
+ "$pushAll" => {
314
+ "addresses" => [{
315
+ "_id" => new_address.id,
316
+ "street" => "Ipanema",
317
+ "locations" => [
318
+ "_id" => location.id,
319
+ "name" => "Home"
320
+ ]
321
+ }]
322
+ },
323
+ conflicts: {
324
+ "$set" => { "addresses.0.street"=>"Bond St" }
325
+ }
326
+ }
327
+ )
328
+ end
329
+ end
330
+ end
331
+
332
+ context "when adding new embedded docs at multiple levels" do
333
+
334
+ let!(:address) do
335
+ person.addresses.build(street: "Another")
336
+ end
337
+
338
+ let!(:location) do
339
+ address.locations.build(name: "Home")
340
+ end
341
+
342
+ it "returns the proper $sets and $pushAlls for all levels" do
343
+ expect(person.atomic_updates).to eq(
344
+ {
345
+ "$set" => {
346
+ "title" => "Sir",
347
+ },
348
+ "$pushAll" => {
349
+ "addresses" => [{
350
+ "_id" => address.id,
351
+ "street" => "Another",
352
+ "locations" => [
353
+ "_id" => location.id,
354
+ "name" => "Home"
355
+ ]
356
+ }]
357
+ }
358
+ }
359
+ )
360
+ end
361
+ end
362
+ end
363
+ end
364
+ end
365
+ end