Subdocument trong Mongoose

Phùng Hùng

Phùng Hùng

Teacher and Programmer

Subdocument là các document được nhúng trong các document khác. Trong Mongoose, bạn tạo subdocument bằng cách lống schema này trong schema khác. Mongoose có hai cách kí hiệu subdocument:

  • mảng các subdocument
  • một subdocument đơn.
const childSchema = new Schema({ name: String });
const parentSchema = new Schema({
// Array of subdocuments
children: [childSchema],
// Single nested subdocuments. Caveat: single nested subdocs only work
// in mongoose >= 4.2.0
child: childSchema,
});

Đối với mảng các subdocument có thể sử dụng cú pháp ngắn gọn hơn mà không phải tạo childSchemanhư sau:

const parentSchema = new Schema({
// Array of subdocuments
children: [{ name: String }],
});

Middleware

Subdocument có đầy đủ các tính năng như của một document thông thường như sử dụng được các middleware, validation, virtual...

Khi gọi save() trên document cha thì save() trên tất cả các document con được trigger trước, tương tự cho validate() middleware.

childSchema.pre('save', function (next) {
if ('invalid' == this.name) {
return next(new Error('#sadpanda'));
}
next();
});
const parent = new Parent({ children: [{ name: 'invalid' }] });
parent.save(function (err) {
console.log(err.message); // #sadpanda
});

Các middleware pre('save')pre('validate') của subdocument chạy trước middlware pre('save') của document cha, nhưng sau pre('validate').

// Below code will print out 1-4 in order
const childSchema = new mongoose.Schema({ name: 'string' });
childSchema.pre('validate', function (next) {
console.log('2');
next();
});
childSchema.pre('save', function (next) {
console.log('3');
next();
});
const parentSchema = new mongoose.Schema({
child: childSchema,
});
parentSchema.pre('validate', function (next) {
console.log('1');
next();
});
parentSchema.pre('save', function (next) {
console.log('4');
next();
});

Phân biệt subdocument với nested paths

// 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);

Khác biệt đầu tiên là bạn không thể truy cập trực tiếp đến các thuộc tính của subdocument như đối với nested path được:

const doc1 = new Subdoc({});
doc1.child.name = 'test'; // TypeError: cannot read property...
const doc2 = new Nested({});
doc2.child.name = 'test'; // Works

Giá trị mặc định của subdocument là undefined, nhưng của nested path là một đối tượng { undefined }:

const doc1 = new Subdoc({});
doc1.child === undefined; // true
const doc2 = new Nested({});
doc2.child === undefined; // false
console.log(doc2.child); // Prints 'MongooseDocument { undefined }'

Khi sử dụng phương thức .set() nó sẽ merge đối với nested path, nhưng overwrite đối với subdocument:

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 }

Thao tác với subdocument

Tìm kiếm một subdocument

Mỗi subdocument có một _id như document thông thường. Thường ta cần tìm một subdocument trong một mảng các subdocument. Mongoose cung cấp phương thức id để tìm một trong một mảng một subdocument nào đó có _id nào đó:

const doc = parent.children.id(_id);

Thêm một subdocument vào mảng subdocument

Có thể sử dụng các phương thức như push, unshift, addToSet để thêm một subdocument vào mảng:

const Parent = mongoose.model('Parent');
const parent = new Parent();
// create a comment
parent.children.push({ name: 'Liesl' });
const 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!');
});

Có thể tạo một single subdocument qua phương thức create:

const newdoc = parent.children.create({ name: 'Aaron' });

Xóa subdocument

  • Để xóa một subdocument đơn, có thể sử dụng phương thức remove() của nó. Hoặc đơn giản hơn gán nó thành null.

  • Nếu subdocument nằm trong mảng, sử dụng remove() hoặc phương thức .pull() của nó.

// 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');
});

Truy cập document cha

Để truy cập document cha, sử dụng phương thức .parent() của subdocument:

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

Nếu subdocument có nhiều cấp, bạn có thể truy cập document cấp cao nhất qua thuộc tính .ownerDocument().

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