zoonman
zoonman

Reputation: 1163

How to implement partial document embedding in Mongoose?

I have a simple relation between topics and categories when topic belongs to a category.

So schema looks like this:

const CategorySchema = new mongoose.Schema({
 name:  String,
 slug: String,
 description: String
});

And topic

const TopicSchema = new mongoose.Schema({
  category: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Category'
  },
  title: String,
  slug: String,
  body:  String,
  created: {type: Date, default: Date.now}
});

I want to implement particular embedding of category into topic

{
    category: {
        _id: ObjectId('abc'),
        slug: 'catslug'
    },
    title: "Title",
    slug: "topictitle",
    ...
}

It will help me avoid unnecessary population and obtain performance bonuses. I don't want to embed whole document because I want to changes categories sometimes (it is a rare operation) and maintain references.

Upvotes: 1

Views: 627

Answers (1)

Igal Klebanov
Igal Klebanov

Reputation: 368

Hope this helps, done it in my own project to save some RTTs in common use cases. Make sure you're taking care of both copies on update.

parent.model.js:

const mongoose = require('mongoose');

const childEmbeddedSchema = new mongoose.Schema({
    _id: {type: mongoose.Schema.Types.ObjectId, ref: 'Child', auto: false, required: true, index: true},
    someFieldIWantEmbedded: {type: String}
});

const parentSchema = new mongoose.Schema({
    child: { type: childEmbeddedSchema },
    moreChildren: { type: [{type: childEmbeddedSchema }] }
});

module.exports = mongoose.model('Parent', parentSchema);

child.model.js:

const mongoose = require('mongoose');

const childSchema = new mongoose.Schema({
    someFieldIWantEmbedded: {type: String},
    someFieldIDontWantEmbedded: {type: Number},
    anotherFieldIDontWantEmbedded: {type: Date}
});

module.exports = mongoose.model('Child', childSchema);

parent.controller.js:

const mongoose = require('mongoose');
const Parent = require('path/to/parent.model');

exports.getAll = (req, res, next) => {
    const query = Parent.find();

    // only populate if requested! if true, will replace entire sub-document with fetched one.
    if (req.headers.populate === 'true') {
        query.populate({
            path: 'child._id',
            select: `someFieldIWantEmbedded ${req.headers.select}`
        });
        query.populate({
            path: 'moreChildren._id',
            select: `someFieldIWantEmbedded ${req.headers.select}`
        });
    }

    query.exec((err, results) => {
        if (err) {
            next(err);
        } else {
            res.status(200).json(results);
        }
    });
};

Upvotes: 1

Related Questions