Skip to content

Commit

Permalink
feat(document): add $timestamps() method to set timestamps for `sav…
Browse files Browse the repository at this point in the history
…e()`, `bulkSave()`, and `insertMany()`

Fix #12117
  • Loading branch information
vkarpov15 committed Oct 8, 2022
1 parent b8f4cb4 commit 2481079
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 1 deletion.
42 changes: 42 additions & 0 deletions lib/document.js
Expand Up @@ -948,6 +948,48 @@ Document.prototype.$session = function $session(session) {
return session;
};

/**
* Getter/setter around whether this document will apply timestamps by
* default when using `save()` and `bulkSave()`.
*
* #### Example:
*
* const TestModel = mongoose.model('Test', new Schema({ name: String }, { timestamps: true }));
* const doc = new TestModel({ name: 'John Smith' });
*
* doc.$timestamps(); // true
*
* doc.$timestamps(false);
* await doc.save(); // Does **not** apply timestamps
*
* @param {Boolean} [value] overwrite the current session
* @return {Document} this
* @method $timestamps
* @api public
* @memberOf Document
*/

Document.prototype.$timestamps = function $timestamps(value) {
if (arguments.length === 0) {
if (this.$__.timestamps != null) {
return this.$__.timestamps;
}

if (this.$__schema) {
return this.$__schema.options.timestamps;
}

return undefined;
}

const currentValue = this.$timestamps();
if (value !== currentValue) {
this.$__.timestamps = value;
}

return this;
};

/**
* Overwrite all values in this document with the values of `obj`, except
* for immutable properties. Behaves similarly to `set()`, except for it
Expand Down
13 changes: 12 additions & 1 deletion lib/model.js
Expand Up @@ -511,6 +511,9 @@ Model.prototype.save = function(options, fn) {
if (options.hasOwnProperty('session')) {
this.$session(options.session);
}
if (this.$__.timestamps != null) {
options.timestamps = this.$__.timestamps;
}
this.$__.$versionError = generateVersionError(this, this.modifiedPaths());

fn = this.constructor.$handleCallbackError(fn);
Expand Down Expand Up @@ -3455,7 +3458,8 @@ Model.$__insertMany = function(arr, options, callback) {
if (doc.$__schema.options.versionKey) {
doc[doc.$__schema.options.versionKey] = 0;
}
if ((!options || options.timestamps !== false) && doc.initializeTimestamps) {
const shouldSetTimestamps = (!options || options.timestamps !== false) && doc.initializeTimestamps && (!doc.$__ || doc.$__.timestamps !== false);
if (shouldSetTimestamps) {
return doc.initializeTimestamps().toObject(internalToObjectOptions);
}
return doc.toObject(internalToObjectOptions);
Expand Down Expand Up @@ -3696,6 +3700,13 @@ Model.bulkSave = async function(documents, options) {
document.$__.saveOptions = document.$__.saveOptions || {};
document.$__.saveOptions.timestamps = options.timestamps;
}
} else {
for (const document of documents) {
if (document.$__.timestamps != null) {
document.$__.saveOptions = document.$__.saveOptions || {};
document.$__.saveOptions.timestamps = document.$__.timestamps;
}
}
}

await Promise.all(documents.map(buildPreSavePromise));
Expand Down
1 change: 1 addition & 0 deletions lib/schema.js
Expand Up @@ -1789,6 +1789,7 @@ Schema.prototype.post = function(name) {
*
* @param {Function} plugin The Plugin's callback
* @param {Object} [opts] Options to pass to the plugin
* @param {Boolean} [opts.deduplicate=false] If true, ignore duplicate plugins (same `fn` argument using `===`)
* @see plugins /docs/plugins.html
* @api public
*/
Expand Down
39 changes: 39 additions & 0 deletions test/model.test.js
Expand Up @@ -4388,6 +4388,23 @@ describe('Model', function() {
});
});

it('timestamps respect $timestamps() (gh-12117)', async function() {
const schema = new Schema({ name: String }, { timestamps: true });
const Movie = db.model('Movie', schema);
const start = Date.now();

const arr = [
new Movie({ name: 'Star Wars' }),
new Movie({ name: 'The Empire Strikes Back' })
];
arr[1].$timestamps(false);

await Movie.insertMany(arr);
const docs = await Movie.find().sort({ name: 1 });
assert.ok(docs[0].createdAt.valueOf() >= start);
assert.ok(!docs[1].createdAt);
});

it('insertMany() with nested timestamps (gh-12060)', async function() {
const childSchema = new Schema({ name: { type: String } }, {
_id: false,
Expand Down Expand Up @@ -8441,6 +8458,7 @@ describe('Model', function() {
assert.ok(userToUpdate.createdAt);
assert.ok(userToUpdate.updatedAt);
});

it('`timestamps` has `undefined` as default value (gh-12059)', async() => {
// Arrange
const userSchema = new Schema({
Expand All @@ -8462,6 +8480,27 @@ describe('Model', function() {
assert.ok(userToUpdate.createdAt);
assert.ok(userToUpdate.updatedAt);
});

it('respects `$timestamps()` (gh-12117)', async function() {
// Arrange
const userSchema = new Schema({ name: String }, { timestamps: true });

const User = db.model('User', userSchema);

const newUser1 = new User({ name: 'John' });
const newUser2 = new User({ name: 'Bill' });

newUser2.$timestamps(false);

// Act
await User.bulkSave([newUser1, newUser2]);

// Assert
assert.ok(newUser1.createdAt);
assert.ok(newUser1.updatedAt);
assert.ok(!newUser2.createdAt);
assert.ok(!newUser2.updatedAt);
});
});

describe('Setting the explain flag', function() {
Expand Down
14 changes: 14 additions & 0 deletions test/timestamps.test.js
Expand Up @@ -626,6 +626,20 @@ describe('timestamps', function() {
assert.strictEqual(cat.updatedAt, old);
});

it('can skip with `$timestamps(false)` (gh-12117)', async function() {
const cat = await Cat.findOne();
const old = cat.updatedAt;

await delay(10);

cat.hobby = 'fishing';

cat.$timestamps(false);
await cat.save();

assert.strictEqual(cat.updatedAt, old);
});

it('should change updatedAt when findOneAndUpdate', function(done) {
Cat.create({ name: 'test123' }, function(err) {
assert.ifError(err);
Expand Down

0 comments on commit 2481079

Please sign in to comment.