mongoid 5.0.0 → 5.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 (85) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/CHANGELOG.md +54 -2
  5. data/lib/config/locales/en.yml +1 -1
  6. data/lib/mongoid/attributes.rb +1 -1
  7. data/lib/mongoid/clients.rb +7 -4
  8. data/lib/mongoid/clients/options.rb +2 -2
  9. data/lib/mongoid/contextual/aggregable/mongo.rb +2 -1
  10. data/lib/mongoid/contextual/geo_near.rb +1 -1
  11. data/lib/mongoid/contextual/memory.rb +4 -1
  12. data/lib/mongoid/contextual/mongo.rb +4 -5
  13. data/lib/mongoid/document.rb +1 -0
  14. data/lib/mongoid/indexable/specification.rb +3 -5
  15. data/lib/mongoid/indexable/validators/options.rb +7 -1
  16. data/lib/mongoid/matchable/exists.rb +1 -1
  17. data/lib/mongoid/persistable.rb +2 -1
  18. data/lib/mongoid/persistable/creatable.rb +1 -1
  19. data/lib/mongoid/persistable/deletable.rb +1 -1
  20. data/lib/mongoid/persistable/updatable.rb +2 -2
  21. data/lib/mongoid/positional.rb +75 -0
  22. data/lib/mongoid/relations/counter_cache.rb +19 -0
  23. data/lib/mongoid/relations/eager/base.rb +4 -2
  24. data/lib/mongoid/relations/embedded/batchable.rb +10 -3
  25. data/lib/mongoid/relations/proxy.rb +1 -1
  26. data/lib/mongoid/relations/touchable.rb +1 -1
  27. data/lib/mongoid/scopable.rb +6 -5
  28. data/lib/mongoid/selectable.rb +36 -1
  29. data/lib/mongoid/threaded.rb +34 -2
  30. data/lib/mongoid/timestamps/created.rb +1 -2
  31. data/lib/mongoid/timestamps/timeless.rb +19 -2
  32. data/lib/mongoid/timestamps/updated.rb +1 -1
  33. data/lib/mongoid/traversable.rb +1 -1
  34. data/lib/mongoid/version.rb +1 -1
  35. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +3 -1
  36. data/spec/app/models/account.rb +8 -0
  37. data/spec/app/models/answer.rb +2 -0
  38. data/spec/app/models/article.rb +2 -0
  39. data/spec/app/models/author.rb +2 -0
  40. data/spec/app/models/baby.rb +4 -0
  41. data/spec/app/models/book.rb +2 -0
  42. data/spec/app/models/consumption_period.rb +7 -0
  43. data/spec/app/models/exhibitor.rb +1 -0
  44. data/spec/app/models/kaleidoscope.rb +6 -0
  45. data/spec/app/models/kangaroo.rb +4 -0
  46. data/spec/app/models/note.rb +3 -0
  47. data/spec/app/models/page.rb +11 -0
  48. data/spec/app/models/simple.rb +5 -0
  49. data/spec/config/mongoid.yml +3 -1
  50. data/spec/mongoid/atomic/paths_spec.rb +17 -10
  51. data/spec/mongoid/attributes_spec.rb +2 -2
  52. data/spec/mongoid/clients/options_spec.rb +15 -0
  53. data/spec/mongoid/clients_spec.rb +6 -2
  54. data/spec/mongoid/config_spec.rb +3 -2
  55. data/spec/mongoid/contextual/aggregable/mongo_spec.rb +25 -2
  56. data/spec/mongoid/contextual/atomic_spec.rb +6 -6
  57. data/spec/mongoid/contextual/mongo_spec.rb +28 -75
  58. data/spec/mongoid/criteria_spec.rb +54 -0
  59. data/spec/mongoid/fields/standard_spec.rb +1 -1
  60. data/spec/mongoid/fields_spec.rb +1 -1
  61. data/spec/mongoid/indexable/specification_spec.rb +1 -1
  62. data/spec/mongoid/indexable_spec.rb +7 -7
  63. data/spec/mongoid/interceptable_spec.rb +55 -0
  64. data/spec/mongoid/persistable/creatable_spec.rb +19 -0
  65. data/spec/mongoid/persistable/destroyable_spec.rb +50 -0
  66. data/spec/mongoid/persistable/incrementable_spec.rb +56 -4
  67. data/spec/mongoid/persistable/pushable_spec.rb +11 -0
  68. data/spec/mongoid/persistable/savable_spec.rb +20 -2
  69. data/spec/mongoid/positional_spec.rb +221 -0
  70. data/spec/mongoid/query_cache_spec.rb +19 -0
  71. data/spec/mongoid/relations/auto_save_spec.rb +1 -1
  72. data/spec/mongoid/relations/bindings/referenced/many_to_many_spec.rb +1 -1
  73. data/spec/mongoid/relations/counter_cache_spec.rb +64 -11
  74. data/spec/mongoid/relations/eager/has_many_spec.rb +37 -0
  75. data/spec/mongoid/relations/eager_spec.rb +11 -0
  76. data/spec/mongoid/relations/embedded/many_spec.rb +38 -9
  77. data/spec/mongoid/relations/embedded/one_spec.rb +1 -1
  78. data/spec/mongoid/relations/proxy_spec.rb +22 -0
  79. data/spec/mongoid/relations/reflections_spec.rb +1 -1
  80. data/spec/mongoid/scopable_spec.rb +160 -19
  81. data/spec/mongoid/selectable_spec.rb +16 -6
  82. data/spec/mongoid/timestamps/timeless_spec.rb +17 -0
  83. data/spec/mongoid/validatable/uniqueness_spec.rb +17 -0
  84. metadata +40 -5
  85. metadata.gz.sig +3 -0
@@ -170,6 +170,61 @@ describe Mongoid::Interceptable do
170
170
  it "runs after document instantiation" do
171
171
  expect(game.name).to eq("Testing")
172
172
  end
173
+
174
+ context 'when the document is embedded' do
175
+
176
+ after do
177
+ Book.destroy_all
178
+ end
179
+
180
+ let(:book) do
181
+ book = Book.new({
182
+ :pages => [
183
+ {
184
+ content: "Page 1",
185
+ notes: [
186
+ { message: "Page 1 / Note A" },
187
+ { message: "Page 1 / Note B" }
188
+ ]
189
+ },
190
+ {
191
+ content: "Page 2",
192
+ notes: [
193
+ { message: "Page 2 / Note A" },
194
+ { message: "Page 2 / Note B" }
195
+ ]
196
+ }
197
+ ]
198
+ })
199
+ book.id = '123'
200
+ book.save
201
+ book
202
+ end
203
+
204
+ let(:new_message) do
205
+ 'Note C'
206
+ end
207
+
208
+ before do
209
+ book.pages.each do | page |
210
+ page.notes.destroy_all
211
+ page.notes.new(message: new_message)
212
+ page.save
213
+ end
214
+ end
215
+
216
+ let(:expected_messages) do
217
+ book.reload.pages.reduce([]) do |messages, p|
218
+ messages += p.notes.reduce([]) do |msgs, n|
219
+ msgs << n.message
220
+ end
221
+ end
222
+ end
223
+
224
+ it 'runs the callback on the embedded documents and saves the parent document' do
225
+ expect(expected_messages.all? { |m| m == new_message }).to be(true)
226
+ end
227
+ end
173
228
  end
174
229
 
175
230
  describe ".after_build" do
@@ -393,6 +393,25 @@ describe Mongoid::Persistable::Creatable do
393
393
  expect(container.vehicles.size).to eq(2)
394
394
  end
395
395
  end
396
+
397
+ context 'when searching by a Time value' do
398
+
399
+ let!(:account) do
400
+ Account.create!(name: 'test', period_started_at: Time.now.utc)
401
+ end
402
+
403
+ let!(:queried_consumption) do
404
+ account.consumption_periods.find_or_create_by(started_at: account.period_started_at)
405
+ end
406
+
407
+ before do
408
+ account.reload
409
+ end
410
+
411
+ it 'does not change the Time value' do
412
+ expect(queried_consumption).to eq(account.current_consumption)
413
+ end
414
+ end
396
415
  end
397
416
 
398
417
  context "#find_or_create_by!" do
@@ -82,6 +82,35 @@ describe Mongoid::Persistable::Destroyable do
82
82
  expect(from_db.addresses).to be_empty
83
83
  end
84
84
  end
85
+
86
+ context 'when removing from a list of embedded documents' do
87
+
88
+ context 'when the embedded documents list is reversed in memory' do
89
+
90
+ let(:word) do
91
+ Word.create!(name: 'driver')
92
+ end
93
+
94
+ let(:from_db) do
95
+ Word.find(word.id)
96
+ end
97
+
98
+ before do
99
+ word.definitions.find_or_create_by(description: 'database connector')
100
+ word.definitions.find_or_create_by(description: 'chauffeur')
101
+ word.definitions = word.definitions.reverse
102
+ word.definitions.last.destroy
103
+ end
104
+
105
+ it 'removes the embedded document in memory' do
106
+ expect(word.definitions.size).to eq(1)
107
+ end
108
+
109
+ it 'removes the embedded document in the database' do
110
+ expect(from_db.definitions.size).to eq(1)
111
+ end
112
+ end
113
+ end
85
114
  end
86
115
 
87
116
  context "when removing deeply embedded documents" do
@@ -188,5 +217,26 @@ describe Mongoid::Persistable::Destroyable do
188
217
  end
189
218
  end
190
219
  end
220
+
221
+ context 'when removing a list of embedded documents' do
222
+
223
+ context 'when the embedded documents list is reversed in memory' do
224
+
225
+ let(:word) do
226
+ Word.create!(name: 'driver')
227
+ end
228
+
229
+ before do
230
+ word.definitions.find_or_create_by(description: 'database connector')
231
+ word.definitions.find_or_create_by(description: 'chauffeur')
232
+ word.definitions = word.definitions.reverse
233
+ word.definitions.destroy_all
234
+ end
235
+
236
+ it 'removes all embedded documents' do
237
+ expect(word.definitions.size).to eq(0)
238
+ end
239
+ end
240
+ end
191
241
  end
192
242
  end
@@ -16,7 +16,7 @@ describe Mongoid::Persistable::Incrementable do
16
16
  expect(person.score).to eq(90)
17
17
  end
18
18
 
19
- it "sets a nonexistant value" do
19
+ it "sets a nonexistent value" do
20
20
  expect(person.inte).to eq(30)
21
21
  end
22
22
 
@@ -32,7 +32,7 @@ describe Mongoid::Persistable::Incrementable do
32
32
  expect(person.reload.score).to eq(90)
33
33
  end
34
34
 
35
- it "persists a nonexistant inc" do
35
+ it "persists a nonexistent inc" do
36
36
  expect(person.reload.inte).to eq(30)
37
37
  end
38
38
 
@@ -97,7 +97,7 @@ describe Mongoid::Persistable::Incrementable do
97
97
  expect(address.no).to eq(90)
98
98
  end
99
99
 
100
- it "sets a nonexistant value" do
100
+ it "sets a nonexistent value" do
101
101
  expect(address.house).to eq(30)
102
102
  end
103
103
 
@@ -113,7 +113,7 @@ describe Mongoid::Persistable::Incrementable do
113
113
  expect(address.reload.no).to eq(90)
114
114
  end
115
115
 
116
- it "persists a nonexistant inc" do
116
+ it "persists a nonexistent inc" do
117
117
  expect(address.reload.house).to eq(30)
118
118
  end
119
119
 
@@ -169,5 +169,57 @@ describe Mongoid::Persistable::Incrementable do
169
169
  it_behaves_like "an incrementable embedded document"
170
170
  end
171
171
  end
172
+
173
+ context "when the document is embedded in another embedded document" do
174
+ shared_examples_for "an incrementable embedded document in another embedded document" do
175
+
176
+ it "increments a positive value" do
177
+ expect(second_answer.position).to eq(2)
178
+ end
179
+
180
+ it "persists a positive inc" do
181
+ expect(second_answer.reload.position).to eq(2)
182
+ end
183
+
184
+ it "clears out dirty changes" do
185
+ expect(second_answer).to_not be_changed
186
+ end
187
+ end
188
+
189
+ let(:survey) do
190
+ Survey.create
191
+ end
192
+
193
+ let(:question) do
194
+ survey.questions.create(content: 'foo')
195
+ end
196
+
197
+ let!(:first_answer) do
198
+ question.answers.create(position: 99)
199
+ end
200
+
201
+ let!(:second_answer) do
202
+ question.answers.create(position: 1)
203
+ end
204
+
205
+ context "when providing string fields" do
206
+
207
+ let!(:inc) do
208
+ second_answer.inc("position" => 1)
209
+ end
210
+
211
+ it_behaves_like "an incrementable embedded document in another embedded document"
212
+
213
+ end
214
+
215
+ context "when providing symbol fields" do
216
+
217
+ let!(:inc) do
218
+ second_answer.inc(position: 1)
219
+ end
220
+
221
+ it_behaves_like "an incrementable embedded document in another embedded document"
222
+ end
223
+ end
172
224
  end
173
225
  end
@@ -122,6 +122,17 @@ describe Mongoid::Persistable::Pushable do
122
122
 
123
123
  it_behaves_like "a unique pushable embedded document"
124
124
  end
125
+
126
+ context "when provided an array of objects" do
127
+
128
+ before do
129
+ person.add_to_set(array: [{ a: 1}, { b: 2}])
130
+ end
131
+
132
+ it 'persists the array of objects' do
133
+ expect(person.reload.array).to eq([{ 'a' => 1}, { 'b' => 2}])
134
+ end
135
+ end
125
136
  end
126
137
  end
127
138
 
@@ -83,6 +83,24 @@ describe Mongoid::Persistable::Savable do
83
83
  end
84
84
  end
85
85
  end
86
+
87
+ context 'when the embedded document is unchanged' do
88
+
89
+ let(:kangaroo) do
90
+ Kangaroo.new
91
+ end
92
+
93
+ after do
94
+ Kangaroo.destroy_all
95
+ end
96
+
97
+ it 'only makes one call to the database' do
98
+ allow(Kangaroo.collection).to receive(:insert).once
99
+ expect_any_instance_of(Mongo::Collection::View).to receive(:update_one).never
100
+ kangaroo.build_baby
101
+ kangaroo.save
102
+ end
103
+ end
86
104
  end
87
105
  end
88
106
 
@@ -395,7 +413,7 @@ describe Mongoid::Persistable::Savable do
395
413
  end
396
414
  end
397
415
 
398
- it "reads for persistance as a UTC Time" do
416
+ it "reads for persistence as a UTC Time" do
399
417
  expect(user.changes["last_login"].last.class).to eq(Time)
400
418
  end
401
419
 
@@ -414,7 +432,7 @@ describe Mongoid::Persistable::Savable do
414
432
  end
415
433
  end
416
434
 
417
- it "reads for persistance as a UTC Time" do
435
+ it "reads for persistence as a UTC Time" do
418
436
  expect(user.changes["account_expires"].last.class).to eq(Time)
419
437
  end
420
438
 
@@ -0,0 +1,221 @@
1
+ require "spec_helper"
2
+
3
+ describe Mongoid::Positional do
4
+
5
+ describe "#positionally" do
6
+
7
+ let(:positionable) do
8
+ Class.new do
9
+ include Mongoid::Positional
10
+ end.new
11
+ end
12
+
13
+ let(:updates) do
14
+ {
15
+ "$set" => {
16
+ "field" => "value",
17
+ "children.0.field" => "value",
18
+ "children.0.children.1.children.3.field" => "value"
19
+ },
20
+ "$pushAll" => {
21
+ "children.0.children.1.children.3.fields" => [ "value", "value" ]
22
+ }
23
+ }
24
+ end
25
+
26
+ context "when a child has an embeds many under an embeds one" do
27
+
28
+ context "when selector does not include the embeds one" do
29
+
30
+ let(:selector) do
31
+ { "_id" => 1, "child._id" => 2 }
32
+ end
33
+
34
+ let(:ops) do
35
+ {
36
+ "$set" => {
37
+ "field" => "value",
38
+ "child.children.1.children.3.field" => "value",
39
+ }
40
+ }
41
+ end
42
+
43
+ let(:processed) do
44
+ positionable.positionally(selector, ops)
45
+ end
46
+
47
+ it "does not do any replacement" do
48
+ expect(processed).to eq(ops)
49
+ end
50
+ end
51
+
52
+ context "when selector includes the embeds one" do
53
+
54
+ let(:selector) do
55
+ { "_id" => 1, "child._id" => 2, "child.children._id" => 3 }
56
+ end
57
+
58
+ let(:ops) do
59
+ {
60
+ "$set" => {
61
+ "field" => "value",
62
+ "child.children.1.children.3.field" => "value",
63
+ }
64
+ }
65
+ end
66
+
67
+ let(:expected) do
68
+ {
69
+ "$set" => {
70
+ "field" => "value",
71
+ "child.children.1.children.3.field" => "value",
72
+ }
73
+ }
74
+ end
75
+
76
+ let(:processed) do
77
+ positionable.positionally(selector, ops)
78
+ end
79
+
80
+ it "does not do any replacement" do
81
+ expect(processed).to eq(expected)
82
+ end
83
+ end
84
+ end
85
+
86
+ context "when the selector has only 1 pair" do
87
+
88
+ let(:selector) do
89
+ { "_id" => 1 }
90
+ end
91
+
92
+ let(:processed) do
93
+ positionable.positionally(selector, updates)
94
+ end
95
+
96
+ it "does not do any replacement" do
97
+ expect(processed).to eq(updates)
98
+ end
99
+ end
100
+
101
+ context "when the selector has 2 pairs" do
102
+
103
+ context "when the second pair has an id" do
104
+
105
+ let(:selector) do
106
+ { "_id" => 1, "children._id" => 2 }
107
+ end
108
+
109
+ let(:expected) do
110
+ {
111
+ "$set" => {
112
+ "field" => "value",
113
+ "children.$.field" => "value",
114
+ "children.0.children.1.children.3.field" => "value"
115
+ },
116
+ "$pushAll" => {
117
+ "children.0.children.1.children.3.fields" => [ "value", "value" ]
118
+ }
119
+ }
120
+ end
121
+
122
+ let(:processed) do
123
+ positionable.positionally(selector, updates)
124
+ end
125
+
126
+ it "replaces the first index with the positional operator" do
127
+ expect(processed).to eq(expected)
128
+ end
129
+ end
130
+
131
+ context "when the second pair has no id" do
132
+
133
+ let(:selector) do
134
+ { "_id" => 1, "children._id" => nil }
135
+ end
136
+
137
+ let(:expected) do
138
+ {
139
+ "$set" => {
140
+ "field" => "value",
141
+ "children.0.field" => "value",
142
+ "children.0.children.1.children.3.field" => "value"
143
+ },
144
+ "$pushAll" => {
145
+ "children.0.children.1.children.3.fields" => [ "value", "value" ]
146
+ }
147
+ }
148
+ end
149
+
150
+ let(:processed) do
151
+ positionable.positionally(selector, updates)
152
+ end
153
+
154
+ it "does not replace the index with the positional operator" do
155
+ expect(processed).to eq(expected)
156
+ end
157
+ end
158
+ end
159
+
160
+ context "when the selector has 3 pairs" do
161
+
162
+ let(:selector) do
163
+ { "_id" => 1, "children._id" => 2, "children.0.children._id" => 3 }
164
+ end
165
+
166
+ let(:expected) do
167
+ {
168
+ "$set" => {
169
+ "field" => "value",
170
+ "children.$.field" => "value",
171
+ "children.0.children.1.children.3.field" => "value"
172
+ },
173
+ "$pushAll" => {
174
+ "children.0.children.1.children.3.fields" => [ "value", "value" ]
175
+ }
176
+ }
177
+ end
178
+
179
+ let(:processed) do
180
+ positionable.positionally(selector, updates)
181
+ end
182
+
183
+ it "replaces the first index with the positional operator" do
184
+ expect(processed).to eq(expected)
185
+ end
186
+ end
187
+
188
+ context "when the selector has 4 pairs" do
189
+
190
+ let(:selector) do
191
+ {
192
+ "_id" => 1,
193
+ "children._id" => 2,
194
+ "children.0.children._id" => 3,
195
+ "children.0.children.1.children._id" => 4
196
+ }
197
+ end
198
+
199
+ let(:expected) do
200
+ {
201
+ "$set" => {
202
+ "field" => "value",
203
+ "children.$.field" => "value",
204
+ "children.0.children.1.children.3.field" => "value"
205
+ },
206
+ "$pushAll" => {
207
+ "children.0.children.1.children.3.fields" => [ "value", "value" ]
208
+ }
209
+ }
210
+ end
211
+
212
+ let(:processed) do
213
+ positionable.positionally(selector, updates)
214
+ end
215
+
216
+ it "replaces the first index with the positional operator" do
217
+ expect(processed).to eq(expected)
218
+ end
219
+ end
220
+ end
221
+ end