06/04/2021, 14:50

Populate trong Mongoose - NodeJS căn bản

Trong bài viết này chúng ta sẽ cùng nhau đi tìm hiểu về Populdate trong Mongoose. Trong phiên bản từ 3.2 trở đi Mongose có hỗ trợ một aggregation operator hỗ trợ việc join các document có mối liên hệ với nhau là lockup. Nhưng phổ biến và mạnh mẽ hơn đó chính là populate(), cho phép bạn join các ...

Trong bài viết này chúng ta sẽ cùng nhau đi tìm hiểu về Populdate trong Mongoose.

Trong phiên bản từ 3.2 trở đi Mongose có hỗ trợ một aggregation operator hỗ trợ việc join các document có mối liên hệ với nhau là lockup. Nhưng phổ biến và mạnh mẽ hơn đó chính là populate(), cho phép bạn join các document từ các collections khác.

Populate là quá quá trình tự động thay thế các paths trong documents gốc bằng cách documents trong các documents khác. Chúng ta có thể gộp một hay nhiều document, objects hay tất cả object từ một query. Bên dưới là ví dụ cụ thể:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const personSchema = Schema({
  _id: Schema.Types.ObjectId,
  name: String,
  age: Number,
  stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});

const storySchema = Schema({
  author: { type: Schema.Types.ObjectId, ref: 'Person' },
  title: String,
  fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});

const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);

Bên trên chúng ta có đã khởi tạo 2 Models. Person sẽ có một trường lưu trữ stories là một array ObjectID. Thuộc tính ref được sử dụng để cho Mongoose biết rằng trường này được join đến model nào, trong trường hợp này là model Story. Tất cả _ids mà chúng ta lưu trữ phải là _ids của một document trong Story model.

Chú ý: ObjectId, Number, String, và Buffer đều sử dụng được options ref. Tuy nhiên bạn nên sử dụng ObjectID ngoại trư trường hợp đặc biệt.

1. Lưu một refs

Bên trên chúng ta đã khởi tạo 2 Model, bây giờ mình sẽ thêm vào Person một document và đồng nghĩa với nó mình cũng thêm một document trong Story.

const author = new Person({
  _id: new mongoose.Types.ObjectId(),
  name: 'Nguyễ  Du',
  age: 0
});

author.save(function (err) {
  if (err) return handleError(err);

  const story1 = new Story({
    title: 'Truyện Kiều',
    author: author._id    // Lấy giá trị của ._id cho refs
  });

  story1.save(function (err) {
    if (err) return handleError(err);
    //Lưu thành công !!!
  });
});

2. Population

Tiếp theo, sau khi thêm một refs bạn có thể thực hiện gộp documents trong Story vào Person quá trình này gọi là populate, cũng gần tương tự như join trong MySQL. Bên dưới mình có ví dụ cụ thể :

Story.
  findOne({ title: 'Truyện Kiều' }).
  populate('author').
  exec(function (err, story) {
    if (err) return handleError(err);
    console.log('Tác giả %s', story.author.name);
    // prints "Tác giả Nguyễn Du"
  });

Tùy chọn các trường

Chúng ta cũng có thể tùy chỉnh các trường được trả về để tiết kiệm tài nguyên, ví dụ bên dưới mình sẽ chỉ trả về giá trị author.name mà thôi.

Story.
  findOne({ title: 'Truyện Kiều' }).
  populate('author', 'name'). // Chỉ trả về Person name
  exec(function (err, story) {
    if (err) return handleError(err);

    console.log('Tác giả : %s', story.author.name);
    // prints "Tác giả : Nguyễn Du"

    console.log('Tuổi %s', story.author.age);
    // prints "Tuổi : null'
  });

Populate với nhiều Paths

Ngoài ra, chúng ta còn có thể populate với nhiều path khác nhau bằng cú pháp sau:

Story.
  find(...).
  populate('fans').
  populate('author').
  exec();

Thêm điều kiện truy vấn

Bạn có thể thêm điều kiện truy vấn, trong ví dụ trên mình sẽ truy vấn fans dựa vào tuổi lớn hơn 50:

Story.
  find(...).
  populate({
    path: 'fans',
    match: { age: { $gte: 50 } },
    select: 'name -_id'
  }).
  exec();

Ngoài ra populate còn hỗ trợ giới hạn các document, thêm object options như ví dụ:

const stories = Story.find().sort({ name: 1 }).populate({
  path: 'fans',
  options: { limit: 2 } //giới hạn các document
});

stories[0].fans.length; // 2
stories[1].fans.length; // 0

Nếu bạn muốn giới hạn trong mỗi document thì bạn có thể sử dụng perDocumentLimit :

const stories = await Story.find().sort({ name: 1 }).populate({
  path: 'fans',

  perDocumentLimit: 2
});

stories[0].fans.length; // 2
stories[1].fans.length; // 2

Populate với nhiều cấp

Ở đây mình có một Model như sau:

var userSchema = new Schema({
  name: String,
  friends: [{ type: ObjectId, ref: 'User' }]
});

Giả sử bạn muốn lấy tất cả bạn bè của một người nào đó, rồi lại lấy bạn bè của những người vừa lấy được =))) Chúng ta sẽ sử dụng cú pháp sau:

User.
  findOne({ name: 'Val' }).
  populate({
    path: 'friends',
    // Get friends of friends - populate the 'friends' array for every friend
    populate: { path: 'friends' }
  });

refPath

Mongoose cũng có thể populate từ nhiều collections khác nhau dựa trên giá trị của thuộc tính trong document. Giả sử bạn đang xây dựng một Schema để lưu trữ bình luận. Người dùng có thể comment về bài đăng trên Blog hoặc Sản phẩm

const commentSchema = new Schema({
  body: { type: String, required: true },
  on: {
    type: Schema.Types.ObjectId,
    required: true,
    refPath: 'onModel'
  },
  onModel: {
    type: String,
    required: true,
    enum: ['BlogPost', 'Product']
  }
});

const Product = mongoose.model('Product', new Schema({ name: String }));
const BlogPost = mongoose.model('BlogPost', new Schema({ title: String }));
const Comment = mongoose.model('Comment', commentSchema);

Lúc này khi lưu một comment bạn chỉ cần chọn Model mà bạn muốn dùng để populate :

const book = await Product.create({ name: 'Zaidap.com });
const post = await BlogPost.create({ title: 'Lập trình NodeJS' });

const commentOnBook = await Comment.create({
  body: 'Sách hay',
  on: book._id,
  onModel: 'Product'
});

const commentOnPost = await Comment.create({
  body: 'Thông tin rất hữu ích',
  on: post._id,
  onModel: 'BlogPost'
});

const comments = await Comment.find().populate('on').sort({ body: 1 });
comments[0].on.name; // "Zaidap.com"
comments[1].on.title; // "Lập trình NodeJS"

Populate trong Middleware

Tham khảo thêm bài viết về Middleware. Bạn có thể thực hiện thao tác populate trong pre và posts hooks. Chúng ta có một vài ví dụ :

// Thường dùng `populate()` khi `find()` được gọi
MySchema.pre('find', function() {
  this.populate('user');
});
// Thường gọi `populate()` sau khi `find()` được gọi. 
// Rât hữu ích trong trường hợp bạn muốn populate sau khi tìm được documents
MySchema.post('find', async function(docs) {
  for (let doc of docs) {
    if (doc.isPublic) {
      await doc.populate('user').execPopulate();
    }
  }
});
// `populate()` sau khi save. 
// Hưu ích cho việc gửi populate về API
MySchema.post('save', function(doc, next) {
  doc.populate('user').execPopulate().then(function() {
    next();
  });
});

Trên đây là những kiến thức cơ bản về Populate 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.

Hoàng Hải Đăng

24 chủ đề

7226 bài viết

Cùng chủ đề
0