mongoid 5.0.0 → 5.0.1

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