mongoid 4.0.0 → 4.0.1

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