Mongoose: timestamps: updatedAt after findByIdAndUpdate

Created on 3 Dec 2016  路  11Comments  路  Source: Automattic/mongoose

I wanted to replace the 3rd party mongoose-timestamp plugin through native mongoose functionality and specified the timestamps schema option.

In contrast to the mentioned plugin however, mongoose does not update when invoking findByIdAndUpdate. Is this by design? I've often stumbled over the differences between document and query middleware, so I'm quite used to this now, but I feel, that timestamps should logically update whenever a document is updated.

To make sure that I'm not doing anything wrong, here's a minimal example:

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/test');

var MyModel = mongoose.model('mySchema', new mongoose.Schema({
  name: String
}, {
  timestamps: true
}));

new MyModel({name: 'boob'}).save(function(err, doc) {
  console.log(doc.updatedAt);
  setTimeout(function() {
    MyModel.findByIdAndUpdate(doc._id, {name: 'bob'}, function(err, doc) {
      console.log(doc.updatedAt); // should by +5 seconds, shouldn't it?
    });
  }, 5000);
});

Would appreciate any feedback. If this behavior is really intentional, it should be stated in the docs, imho.

enhancement

Most helpful comment

Thanks for pointing this out. However this was not my issue, I just noticed that my example was too minimal in that regard. Apologies.

The "problem" seems to be, that mongoose does not update the updatedAt value when using findByIdAndUpdate in case it is already given in the data. The example which resembles my real-world scenario should more look like this:

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/test');

var MyModel = mongoose.model('mySchema', new mongoose.Schema({
  name: String
}, {
  timestamps: true
}));

new MyModel({name: 'boob'}).save(function(err, doc) {
  console.log(doc.updatedAt);
  setTimeout(function() {
    doc = doc.toObject();
    doc.name = 'bob';
    MyModel.findByIdAndUpdate(doc._id, doc, {new: true}, function(err, doc) {
      console.log(doc.updatedAt); // should by +5 seconds, shouldn't it?
    });
  }, 5000);
});

In contrast, saving the document via its save function seems to update the timestamp correctly, even if it is present in the document:

new MyModel({name: 'boob'}).save(function(err, doc) {
  console.log(doc.updatedAt);
  setTimeout(function() {
    doc.name = 'bob';
    doc.save(function() {
      MyModel.findById(doc._id, function(err, doc) {
        console.log(doc.updatedAt); // here the timestamp is +5 seconds, as expected
      });
    });
  }, 5000);
});

Is this the expected behavior?

All 11 comments

Set the new option to true and it'll work as expected. The timestamp gets set, you're just not using findByIdAndUpdate() properly.

Thanks for pointing this out. However this was not my issue, I just noticed that my example was too minimal in that regard. Apologies.

The "problem" seems to be, that mongoose does not update the updatedAt value when using findByIdAndUpdate in case it is already given in the data. The example which resembles my real-world scenario should more look like this:

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/test');

var MyModel = mongoose.model('mySchema', new mongoose.Schema({
  name: String
}, {
  timestamps: true
}));

new MyModel({name: 'boob'}).save(function(err, doc) {
  console.log(doc.updatedAt);
  setTimeout(function() {
    doc = doc.toObject();
    doc.name = 'bob';
    MyModel.findByIdAndUpdate(doc._id, doc, {new: true}, function(err, doc) {
      console.log(doc.updatedAt); // should by +5 seconds, shouldn't it?
    });
  }, 5000);
});

In contrast, saving the document via its save function seems to update the timestamp correctly, even if it is present in the document:

new MyModel({name: 'boob'}).save(function(err, doc) {
  console.log(doc.updatedAt);
  setTimeout(function() {
    doc.name = 'bob';
    doc.save(function() {
      MyModel.findById(doc._id, function(err, doc) {
        console.log(doc.updatedAt); // here the timestamp is +5 seconds, as expected
      });
    });
  }, 5000);
});

Is this the expected behavior?

Yeah this is expected, when you use update() or findOneAndUpdate() mongoose only sets timestamps if you don't set them yourself. Reasons are 2-fold: 1) prevent surprises, 2) it's easier to recommend doing delete doc.updatedAt before updating than to add an extra option to overwrite timestamps

Alright, thank you for clarifying!

@vkarpov15 is there a way to tell mongoose always overwrite timestamps?

@viktornord unfortunately not. We'll consider adding an option for this for a future release.

if findByIdAndUpdate doesn't trigger updateAt field update , i don't see any point to use the build in timestamp.

@huangxuewu it does trigger updatedAt update

@vkarpov15 somehow it is not updating in my code. I have to add
Announcement.pre("findOneAndUpdate", function (next) { this.updateAt = new Date(); next(); })
to make it work.

const Announcement = new Schema({
title: String,
brief: String,
content: String,
creator: String,
target: String,
targets: [],
noticed: [],
enable: Boolean
}, {
timestamps: true
});

function updateAnnouncement(data, done) { Model.announcement.findByIdAndUpdate(data._id, data, { upsert: true }).then(() => done()); }

is anything wrong with my code?

What does data look like in your code? It might be that there's a updatedAt property in there already.

Looking into this more closely, this is actually a bug rather than a feature we need to opt in to. That's because in OP's example, updatedAt does get bumped if you use $set:

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/test');

var MyModel = mongoose.model('mySchema', new mongoose.Schema({
  name: String
}, {
  timestamps: true
}));

mongoose.set('debug', true);

new MyModel({name: 'boob'}).save(function(err, doc) {
  console.log(doc.updatedAt);
  setTimeout(function() {
    doc = doc.toObject();
    doc.name = 'bob';
    // Add `$set`, now `updatedAt` gets bumped
    MyModel.findByIdAndUpdate(doc._id, { $set: doc }, {new: true}, function(err, doc) {
      console.log(doc.updatedAt); // should by +5 seconds, shouldn't it?
    });
  }, 1000);
});

The code is there that sets $set.updatedAt, the issue is that updatedAt without $set overwrites it. We'll fix this for 5.7 :+1:

Was this page helpful?
0 / 5 - 0 ratings