Skip to content

Commit

Permalink
fix(model+timestamps): set timestamps on subdocuments in insertMany()
Browse files Browse the repository at this point in the history
Fix #12060
  • Loading branch information
vkarpov15 committed Jul 15, 2022
1 parent 115f922 commit 4160245
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 41 deletions.
26 changes: 26 additions & 0 deletions lib/helpers/timestamps/setDocumentTimestamps.js
@@ -0,0 +1,26 @@
'use strict';

module.exports = function setDocumentTimestamps(doc, timestampOption, currentTime, createdAt, updatedAt) {
const skipUpdatedAt = timestampOption != null && timestampOption.updatedAt === false;
const skipCreatedAt = timestampOption != null && timestampOption.createdAt === false;

const defaultTimestamp = currentTime != null ?
currentTime() :
doc.ownerDocument().constructor.base.now();

if (!skipCreatedAt &&
(doc.isNew || doc.$isSubdocument) &&
createdAt &&
!doc.$__getValue(createdAt) &&
doc.$__isSelected(createdAt)) {
doc.$set(createdAt, defaultTimestamp, undefined, { overwriteImmutable: true });
}

if (!skipUpdatedAt && updatedAt && (doc.isNew || doc.$isModified())) {
let ts = defaultTimestamp;
if (doc.isNew && createdAt != null) {
ts = doc.$__getValue(createdAt);
}
doc.$set(updatedAt, ts);
}
};
32 changes: 14 additions & 18 deletions lib/helpers/timestamps/setupTimestamps.js
Expand Up @@ -4,6 +4,7 @@ const applyTimestampsToChildren = require('../update/applyTimestampsToChildren')
const applyTimestampsToUpdate = require('../update/applyTimestampsToUpdate');
const get = require('../get');
const handleTimestampOption = require('../schema/handleTimestampOption');
const setDocumentTimestamps = require('./setDocumentTimestamps');
const symbols = require('../../schema/symbols');

module.exports = function setupTimestamps(schema, timestamps) {
Expand Down Expand Up @@ -44,24 +45,7 @@ module.exports = function setupTimestamps(schema, timestamps) {
return next();
}

const skipUpdatedAt = timestampOption != null && timestampOption.updatedAt === false;
const skipCreatedAt = timestampOption != null && timestampOption.createdAt === false;

const defaultTimestamp = currentTime != null ?
currentTime() :
this.ownerDocument().constructor.base.now();

if (!skipCreatedAt && (this.isNew || this.$isSubdocument) && createdAt && !this.$__getValue(createdAt) && this.$__isSelected(createdAt)) {
this.$set(createdAt, defaultTimestamp, undefined, { overwriteImmutable: true });
}

if (!skipUpdatedAt && updatedAt && (this.isNew || this.$isModified())) {
let ts = defaultTimestamp;
if (this.isNew && createdAt != null) {
ts = this.$__getValue(createdAt);
}
this.$set(updatedAt, ts);
}
setDocumentTimestamps(this, timestampOption, currentTime, createdAt, updatedAt);

next();
});
Expand All @@ -76,6 +60,18 @@ module.exports = function setupTimestamps(schema, timestamps) {
if (updatedAt && !this.get(updatedAt)) {
this.$set(updatedAt, ts);
}

if (this.$isSubdocument) {
return this;
}

const subdocs = this.$getAllSubdocs();
for (const subdoc of subdocs) {
if (subdoc.initializeTimestamps) {
subdoc.initializeTimestamps();
}
}

return this;
};

Expand Down
2 changes: 1 addition & 1 deletion lib/model.js
Expand Up @@ -3396,7 +3396,7 @@ Model.$__insertMany = function(arr, options, callback) {
if (doc.$__schema.options.versionKey) {
doc[doc.$__schema.options.versionKey] = 0;
}
if (doc.initializeTimestamps) {
if ((!options || options.timestamps !== false) && doc.initializeTimestamps) {
return doc.initializeTimestamps().toObject(internalToObjectOptions);
}
return doc.toObject(internalToObjectOptions);
Expand Down
74 changes: 52 additions & 22 deletions test/model.test.js
Expand Up @@ -4346,6 +4346,58 @@ describe('Model', function() {
await db.close();
});

describe('insertMany()', function() {
it('with timestamps (gh-723)', function() {
const schema = new Schema({ name: String }, { timestamps: true });
const Movie = db.model('Movie', schema);
const start = Date.now();

const arr = [{ name: 'Star Wars' }, { name: 'The Empire Strikes Back' }];
return Movie.insertMany(arr).
then(docs => {
assert.equal(docs.length, 2);
assert.ok(!docs[0].isNew);
assert.ok(!docs[1].isNew);
assert.ok(docs[0].createdAt.valueOf() >= start);
assert.ok(docs[1].createdAt.valueOf() >= start);
}).
then(() => Movie.find()).
then(docs => {
assert.equal(docs.length, 2);
assert.ok(docs[0].createdAt.valueOf() >= start);
assert.ok(docs[1].createdAt.valueOf() >= start);
});
});

it('insertMany() with nested timestamps (gh-12060)', async function() {
const childSchema = new Schema({ name: { type: String } }, {
_id: false,
timestamps: true
});

const parentSchema = new Schema({ child: childSchema }, {
timestamps: true
});

const Test = db.model('Test', parentSchema);

await Test.insertMany([{ child: { name: 'test' } }]);
let docs = await Test.find();

assert.equal(docs.length, 1);
assert.equal(docs[0].child.name, 'test');
assert.ok(docs[0].child.createdAt);
assert.ok(docs[0].child.updatedAt);

await Test.insertMany([{ child: { name: 'test2' } }], { timestamps: false });
docs = await Test.find({ 'child.name': 'test2' });
assert.equal(docs.length, 1);
assert.equal(docs[0].child.name, 'test2');
assert.ok(!docs[0].child.createdAt);
assert.ok(!docs[0].child.updatedAt);
});
});

describe('bug fixes', function() {
it('doesnt crash (gh-1920)', function(done) {
const parentSchema = new Schema({
Expand Down Expand Up @@ -4638,28 +4690,6 @@ describe('Model', function() {
});
});

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

const arr = [{ name: 'Star Wars' }, { name: 'The Empire Strikes Back' }];
return Movie.insertMany(arr).
then(docs => {
assert.equal(docs.length, 2);
assert.ok(!docs[0].isNew);
assert.ok(!docs[1].isNew);
assert.ok(docs[0].createdAt.valueOf() >= start);
assert.ok(docs[1].createdAt.valueOf() >= start);
}).
then(() => Movie.find()).
then(docs => {
assert.equal(docs.length, 2);
assert.ok(docs[0].createdAt.valueOf() >= start);
assert.ok(docs[1].createdAt.valueOf() >= start);
});
});

it('returns empty array if no documents (gh-8130)', function() {
const Movie = db.model('Movie', Schema({ name: String }));
return Movie.insertMany([]).then(docs => assert.deepEqual(docs, []));
Expand Down

0 comments on commit 4160245

Please sign in to comment.