8

I have a user model schema in mongoose which contains a list of friends and groups and stats info like so...

var user = new Schema({
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true, select: false },
  roles: [{ type: String, required: true }],
  friends: [{ type: Schema.Types.ObjectId, ref: 'User' }],
  groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }], 
  stats : {
    nbrFriends: { type: Number, required: false },
    nbrGroups: { type: Number, required: false }
  }
}, {
  timestamps: true
}); 

I need to update the users stats whenever a change is made to the friends or groups fields to contain the new number of friends or groups etc. For example, when the following function is called on a user:

var addGroup = function(user, group, cb) {

  user.groups.push(group);
  User.findOneAndUpdate({ _id: user._id }, { $set: { groups: user.groups }}, { new: true }, function(err, savedResult) {
    if(err) {
      return cb(err);
    }
    console.log('updated user: ' + JSON.stringify(savedResult));
    return cb(null, savedResult);
  });
};

How could I make sure the stats is automatically updated to contain the new number of groups the user has? It seems like a middleware function would be the best approach here. I tried the following but this never seems to get called...

user.pre('save', function(next) {
  var newStats = {
    nbrGroups: this.groups.length,
    nbrPatients: this.friends.length
  };
  this.stats = newStats;
  this.save(function(err, result) {
    if(err) {
      console.log('error saving: ' + err);
    } else {
      console.log('saved');
    }
    next();
  });  
});
CSharp
  • 1,396
  • 1
  • 18
  • 41

3 Answers3

6

You need to use the middleware a.k.a. hooks:

Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions.

See the docs:

rsp
  • 107,747
  • 29
  • 201
  • 177
  • There is something I dont understand about mongoose post hooks. For instance I need to run 3 min task after new document is inserted (User signs up) so will user have to wait for hook task to complete or response will be returned right after .save() has been successfully executed. Does post hooks run in background? – Nux Jun 10 '19 at 23:54
  • if you supply a post hook to be called on 'save' like so...schema.post('save', function(doc) { console.log('%s has been saved', doc._id); }); ..then the second parameter is a function that will be called after the new document is inserted, I believe. So if you want your task to run after the User signs up then you would add the task code inside that function I presume. – CSharp Jun 12 '19 at 12:46
4

From version 3.6, you can use change streams.

Like:

const Users = require('./models/users.js')

 var filter = [{
        $match: {
            $and: [{
                 $or:[
                { "updateDescription.updatedFields.friends": { $exists: true } },
                { "updateDescription.updatedFields.groups": { $exists: true } },
              ]
                { operationType: "update" }]
            }
        }];

 var options = { fullDocument: 'updateLookup' };


let userStream = Users.watch(filter,options)

userStream.on('change',next=>{

//Something useful!

})

Pranesh A S
  • 331
  • 1
  • 7
-4

You should update with vanilla JS and then save the document updated to trigger the pre-save hooks.

See Mongoose docs

If you have many keys to update you could loop through the keys in the body and update one by one.

const user = await User.findById(id);

Object.keys(req.body).forEach(key => {
  user[key] = req.body[key];
}

const saved = await user.save();
Aecio Levy
  • 149
  • 1
  • 8