forked from Automattic/mongoose
-
Notifications
You must be signed in to change notification settings - Fork 1
/
subdocs.pug
347 lines (296 loc) · 11.3 KB
/
subdocs.pug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
extends layout
append style
link(rel="stylesheet", href="/docs/css/inlinecpc.css")
script(type="text/javascript" src="/docs/js/native.js")
block content
<a class="edit-docs-link" href="#{editLink}" target="_blank">
<img src="/docs/images/pencil.svg" />
</a>
:markdown
## Subdocuments
<script>
_native.init("CK7DT53U",{
targetClass: 'native-inline'
});
</script>
<div class="native-inline">
<a href="#native_link#"><span class="sponsor">Sponsor</span> #native_company# — #native_desc#</a>
</div>
Subdocuments are documents embedded in other documents. In Mongoose, this
means you can nest schemas in other schemas. Mongoose has two
distinct notions of subdocuments: [arrays of subdocuments](https://masteringjs.io/tutorials/mongoose/array#document-arrays) and single nested
subdocuments.
```javascript
var childSchema = new Schema({ name: 'string' });
var parentSchema = new Schema({
// Array of subdocuments
children: [childSchema],
// Single nested subdocuments. Caveat: single nested subdocs only work
// in mongoose >= 4.2.0
child: childSchema
});
```
Aside from code reuse, one important reason to use subdocuments is to create
a path where there would otherwise not be one to allow for validation over
a group of fields (e.g. dateRange.fromDate <= dateRange.toDate).
:markdown
<ul class="toc">
<li><a href="#what-is-a-subdocument-">What is a Subdocument?</a></li>
<li><a href="#subdocuments-versus-nested-paths">Subdocuments versus Nested Paths</a></li>
<li><a href="#finding-a-subdocument">Finding a Subdocument</a></li>
<li><a href="#adding-subdocs-to-arrays">Adding Subdocs to Arrays</a></li>
<li><a href="#removing-subdocs">Removing Subdocs</a></li>
<li><a href="#subdoc-parents">Parents of Subdocs</a></li>
<li><a href="#altsyntaxarrays">Alternate declaration syntax for arrays</a></li>
<li><a href="#altsyntaxsingle">Alternate declaration syntax for single subdocuments</a></li>
</ul>
### What is a Subdocument?
Subdocuments are similar to normal documents. Nested schemas can have
[middleware](./middleware.html), [custom validation logic](./validation.html),
virtuals, and any other feature top-level schemas can use. The major
difference is that subdocuments are
not saved individually, they are saved whenever their top-level parent
document is saved.
```javascript
var Parent = mongoose.model('Parent', parentSchema);
var parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] })
parent.children[0].name = 'Matthew';
// `parent.children[0].save()` is a no-op, it triggers middleware but
// does **not** actually save the subdocument. You need to save the parent
// doc.
parent.save(callback);
```
:markdown
Subdocuments have `save` and `validate` [middleware](./middleware.html)
just like top-level documents. Calling `save()` on the parent document triggers
the `save()` middleware for all its subdocuments, and the same for `validate()`
middleware.
```javascript
childSchema.pre('save', function (next) {
if ('invalid' == this.name) {
return next(new Error('#sadpanda'));
}
next();
});
var parent = new Parent({ children: [{ name: 'invalid' }] });
parent.save(function (err) {
console.log(err.message) // #sadpanda
});
```
:markdown
Subdocuments' `pre('save')` and `pre('validate')` middleware execute
**before** the top-level document's `pre('save')` but **after** the
top-level document's `pre('validate')` middleware. This is because validating
before `save()` is actually a piece of built-in middleware.
```javascript
// Below code will print out 1-4 in order
var childSchema = new mongoose.Schema({ name: 'string' });
childSchema.pre('validate', function(next) {
console.log('2');
next();
});
childSchema.pre('save', function(next) {
console.log('3');
next();
});
var parentSchema = new mongoose.Schema({
child: childSchema
});
parentSchema.pre('validate', function(next) {
console.log('1');
next();
});
parentSchema.pre('save', function(next) {
console.log('4');
next();
});
```
### Subdocuments versus Nested Paths
In Mongoose, nested paths are subtly different from subdocuments.
For example, below are two schemas: one with `child` as a subdocument,
and one with `child` as a nested path.
```javascript
// Subdocument
const subdocumentSchema = new mongoose.Schema({
child: new mongoose.Schema({ name: String, age: Number })
});
const Subdoc = mongoose.model('Subdoc', subdocumentSchema);
// Nested path
const nestedSchema = new mongoose.Schema({
child: { name: String, age: Number }
});
const Nested = mongoose.model('Nested', nestedSchema);
```
These two schemas look similar, and the documents in MongoDB will
have the same structure with both schemas. But there are a few
Mongoose-specific differences:
First, instances of `Nested` never have `child === undefined`.
You can always set subproperties of `child`, even if you don't set
the `child` property. But instances of `Subdoc` can have `child === undefined`.
```javascript
const doc1 = new Subdoc({});
doc1.child === undefined; // true
doc1.child.name = 'test'; // Throws TypeError: cannot read property...
const doc2 = new Nested({});
doc2.child === undefined; // false
console.log(doc2.child); // Prints 'MongooseDocument { undefined }'
doc2.child.name = 'test'; // Works
```
Secondly, in Mongoose 5, [`Document#set()`](/docs/api/document.html#document_Document-set)
merges when you call it on a nested path, but overwrites when you call
it on a subdocument.
```javascript
const doc1 = new Subdoc({ child: { name: 'Luke', age: 19 } });
doc1.set({ child: { age: 21 } });
doc1.child; // { age: 21 }
const doc2 = new Nested({ child: { name: 'Luke', age: 19 } });
doc2.set({ child: { age: 21 } });
doc2.child; // { name: Luke, age: 21 }
```
h3#finding-a-subdocument Finding a Subdocument
:markdown
Each subdocument has an `_id` by default. Mongoose document arrays have a
special [id](./api.html#types_documentarray_MongooseDocumentArray-id) method
for searching a document array to find a document with a given `_id`.
```javascript
var doc = parent.children.id(_id);
```
h3#adding-subdocs-to-arrays Adding Subdocs to Arrays
:markdown
MongooseArray methods such as
[push](./api.html#mongoosearray_MongooseArray-push),
[unshift](./api.html#mongoosearray_MongooseArray-unshift),
[addToSet](./api.html#mongoosearray_MongooseArray-addToSet),
and others cast arguments to their proper types transparently:
```javascript
var Parent = mongoose.model('Parent');
var parent = new Parent;
// create a comment
parent.children.push({ name: 'Liesl' });
var subdoc = parent.children[0];
console.log(subdoc) // { _id: '501d86090d371bab2c0341c5', name: 'Liesl' }
subdoc.isNew; // true
parent.save(function (err) {
if (err) return handleError(err)
console.log('Success!');
});
```
:markdown
Subdocs may also be created without adding them to the array by using the
[create](./api.html#types_documentarray_MongooseDocumentArray.create)
method of MongooseArrays.
```javascript
var newdoc = parent.children.create({ name: 'Aaron' });
```
h3#removing-subdocs Removing Subdocs
:markdown
Each subdocument has it's own
[remove](./api.html#types_embedded_EmbeddedDocument-remove) method. For
an array subdocument, this is equivalent to calling `.pull()` on the
subdocument. For a single nested subdocument, `remove()` is equivalent
to setting the subdocument to `null`.
```javascript
// Equivalent to `parent.children.pull(_id)`
parent.children.id(_id).remove();
// Equivalent to `parent.child = null`
parent.child.remove();
parent.save(function (err) {
if (err) return handleError(err);
console.log('the subdocs were removed');
});
```
h3#subdoc-parents Parents of Subdocs
:markdown
Sometimes, you need to get the parent of a subdoc. You can access the
parent using the `parent()` function.
```javascript
const schema = new Schema({
docArr: [{ name: String }],
singleNested: new Schema({ name: String })
});
const Model = mongoose.model('Test', schema);
const doc = new Model({
docArr: [{ name: 'foo' }],
singleNested: { name: 'bar' }
});
doc.singleNested.parent() === doc; // true
doc.docArr[0].parent() === doc; // true
```
If you have a deeply nested subdoc, you can access the top-level document
using the `ownerDocument()` function.
```javascript
const schema = new Schema({
level1: new Schema({
level2: new Schema({
test: String
})
})
});
const Model = mongoose.model('Test', schema);
const doc = new Model({ level1: { level2: 'test' } });
doc.level1.level2.parent() === doc; // false
doc.level1.level2.parent() === doc.level1; // true
doc.level1.level2.ownerDocument() === doc; // true
```
h4#altsyntaxarrays Alternate declaration syntax for arrays
:markdown
If you create a schema with an array of objects, Mongoose will automatically
convert the object to a schema for you:
```javascript
var parentSchema = new Schema({
children: [{ name: 'string' }]
});
// Equivalent
var parentSchema = new Schema({
children: [new Schema({ name: 'string' })]
});
```
h4#altsyntaxsingle Alternate declaration syntax for single nested subdocuments
:markdown
Unlike document arrays, Mongoose 5 does not convert an objects in schemas
into nested schemas. In the below example, `nested` is a _nested path_
rather than a subdocument.
```javascript
const schema = new Schema({
nested: {
prop: String
}
});
```
This leads to some surprising behavior when you attempt to define a
nested path with validators or getters/setters.
```javascript
const schema = new Schema({
nested: {
// Do not do this! This makes `nested` a mixed path in Mongoose 5
type: { prop: String },
required: true
}
});
const schema = new Schema({
nested: {
// This works correctly
type: new Schema({ prop: String }),
required: true
}
});
```
Surprisingly, declaring `nested` with an object `type` makes `nested`
into a path of type [Mixed](/docs/schematypes.html#mixed). To instead
make Mongoose automatically convert `type: { prop: String }` into
`type: new Schema({ prop: String })`, set the `typePojoToMixed` option
to `false`.
```javascript
const schema = new Schema({
nested: {
// Because of `typePojoToMixed`, Mongoose knows to
// wrap `{ prop: String }` in a `new Schema()`.
type: { prop: String },
required: true
}
}, { typePojoToMixed: false });
```
h3#next Next Up
:markdown
Now that we've covered Subdocuments, let's take a look at
[querying](./queries.html).