Subdocuments trong Mongooose

Trong bài viết này chúng ta sẽ đi tìm hiểu về Subdocuments trong Mongoose. Subdocuments là một document nằm bên trong một document nào đó. Trong Mongoose, nghĩa là bạn có thể thêm 1 document vào trong 1 document.

Mongoose chia Subdocument thành 2 loại chính gồm:

  • Một mảng subdocument
  • Một document riêng biệt.

Ở đây mình có một ví dụ:

var childSchema = new Schema({ name: 'string' });

var parentSchema = new Schema({
  // Một mảng subdocumnet
  children: [childSchema],
  //Một subdocument độc lập
  child: childSchema
});

1. Subdocument là gì ?

Subdocument giống như các documents thông thường, các schemas con đều có thể sử dụng middleware, validation, virtuals hay bất cứ tính năng nào mà schemas cha hỗ trợ. Cái khác nhau của một subdocuments là nó sẽ không tư động lưu khi có sự thay đổi mà phải đợi documents cha lưu nó mới thèm lưu.

//Tạo một schemas
var parentSchema = new Schema({
  children: [
    {
      name: String
    }
  ]
});
var Prarent = mongoose.model("Parent", parentSchema);

var parent = new Prarent({
  children: [{ name: "Freetuts.net" }, { name: "Hoc lap trinh" }]
});
console.log(parent.children[0].name);
parent.children[1].name = "Node.js";
parent.save();

Subdocument cũng có các middleware như document cha như save() hay vadlidate(). Nếu bạn gọi save() ở document cha thì đồng thời cũng gọi save() ở trong tất cả các subdocumentsvalidate() cũng tương tự như vậy.

//Đây là middleware, các bạn sẽ được biết ở bài sau nhé!
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
});

Midleware save()validate() sẽ thực thi trước so với document cha. Đây là thứ tự thực hiện của các middleware khi chúng ta tiến hành lưu một model.


  var childSchema = new mongoose.Schema({ name: "string" });

  childSchema.pre("validate", function(next) {
    console.log("childSchema validate.");
    next();
  });

  childSchema.pre("save", function(next) {
    console.log("childSchema save.");
    next();
  });

  var parentSchema = new mongoose.Schema({
    child: [childSchema]
  });

  parentSchema.pre("validate", function(next) {
    console.log("parentSchema validate");
    next();
  });

  parentSchema.pre("save", function(next) {
    console.log("parentSchema save");
    next();
  });
  var Parent = mongoose.model('Parent', parentSchema)
  var parent = new Parent({child: [{name: 'Freetuts.net'}, {name: 'Lap trinh NodeJS'}]})
  parent.save()

validate mongoose png

2. Tìm kiếm subdocument

Mỗi document trong subdocument đều có một _id duy nhất. Một mảng mongoose document sẽ có một phương thức id cho phép bạn tìm kiếm document đó bằng id :

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

3. Thêm một subdocs vào mảng

MongooseArray còn hỗ trợ các phương thức như push, unshift, addToSet và các arguements khác cho phép làm việc với nó. Ví dụ bên dưới mình sẽ đi thêm một subdoc vào một docs như sau:

var Parent = mongoose.model('Parent');
var parent = new Parent;

// Thêm một subdoc
parent.children.push({ name: 'Freetuts.net' });
var subdoc = parent.children[0];
//In ra subdoc vừa thêm
console.log(subdoc) // { _id: '501d86090d371bab2c0341c5', name: 'Freetuts.net' }

//Kiểm tra xem docs có sự thay đổi hay không ? 
subdoc.isNew; // true

//Tiến hành lưu
parent.save(function (err) {
  if (err) return handleError(err)
  console.log('Success!');
});

Subdocs có thể được tạo mở MongooseArray hoặc được thêm vào bằng phương thức create() :

var newdoc = parent.children.create({ name: 'Freetuts.net' });

4. Xóa một subdocs trong mảng

Mỗi subdocument đều có thể được xóa đi. Trong một mảng các subdocument, để thực hiện chúng ta gọi làm .pull() trong subdocument, trong một subdocument thông thường chúng ta sử dụng remove(), chúng ta có ví dụ bên dưới :

// Tương đương với `parent.children.pull(_id)`
parent.children.id(_id).remove();
// Tương đương với `parent.child = null`
parent.child.remove();
parent.save(function (err) {
  if (err) return handleError(err);
  console.log('subdocs đã được xóa');
});

5. Quan hệ trong một subdocument

Trong một vài trường hợp bạn cần xem document cha, Mongoose cung cấp cho chúng ta một phương thức cho tình huống này đó là .parent()

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 bạn có nhiều subdocument mà bạn muốn tìm cụ, kị của chúng thì có thể dùng ownerDocument(). Chúng ta có ví dụ bên dưới :

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

6. Các cú pháp khai báo Subdocument

Ở đây mình liệt kê ra các cú pháp của subdocument, những cú pháp này đều tương đương nhau và có thể được sử dụng thay cho nhau.

Nếu bạn tạo một Schema trong một array obejct thì mongoose sẽ tự động chuyển nó sang object schema cho bạn. Ví dụ:

var parentSchema = new Schema({
  children: [{ name: 'string' }]
});
// Giống nhau
var parentSchema = new Schema({
  children: [new Schema({ name: 'string' })]
});

Trong các trường hợp bạn có thể viết tắt các schema như ví dụ bên dưới :

var parentSchema = new Schema({
  child: { type: { name: 'string' } }
}, { typePojoToMixed: false });
// Giống nhau
var parentSchema = new Schema({
  child: new Schema({ name: 'string' })
});

// Khác nhau, cẩn thận khi viết như vầy
var parentSchema = new Schema({
  child: { type: { name: 'string' } }
});

Trên đây là những kiến thức cơ bản về Subdocuments trong Mongoose. Mong bài viết này có thể giúp ích cho bạn cho việc lập trình với NodeJS, cảm ơn bạn đã quan tâm bài viết này.

Ngẫu nhiên

Freetuts sử dụng theme GoodNews, code trên nền tảng Codeigniter, VPS mua tại Tinohost.
Sử dụng mã TINO30_2020 để được giảm 30% khi mua Hosting / VPS tại Tinohost.