diff --git a/lib/model.js b/lib/model.js index e400536eb9..b45bcecb5c 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2774,6 +2774,49 @@ Model.create = async function create(doc, options) { return res; }; +/** + * Shortcut for saving one document to the database. + * `MyModel.insertOne(obj, options)` is almost equivalent to `new MyModel(obj).save(options)`. + * The difference is that `insertOne()` checks if `obj` is already a document, and checks for discriminators. + * + * This function triggers the following middleware. + * + * - `save()` + * + * #### Example: + * + * // Insert one new `Character` document + * const character = await Character.insertOne({ name: 'Jean-Luc Picard' }); + * character.name; // 'Jean-Luc Picard' + * + * // Create a new character within a transaction. + * await Character.insertOne({ name: 'Jean-Luc Picard' }, { session }); + * + * @param {Object|Document} doc Document to insert, as a POJO or Mongoose document + * @param {Object} [options] Options passed down to `save()`. + * @return {Promise} resolves to the saved document + * @api public + */ + +Model.insertOne = async function insertOne(doc, options) { + _checkContext(this, 'insertOne'); + + const discriminatorKey = this.schema.options.discriminatorKey; + const Model = this.discriminators && doc[discriminatorKey] != null ? + this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) : + this; + if (Model == null) { + throw new MongooseError( + `Discriminator "${doc[discriminatorKey]}" not found for model "${this.modelName}"` + ); + } + if (!(doc instanceof Model)) { + doc = new Model(doc); + } + + return await doc.$save(options); +}; + /** * _Requires a replica set running MongoDB >= 3.6.0._ Watches the * underlying collection for changes using diff --git a/test/model.test.js b/test/model.test.js index e7bd52447a..be87e5a438 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -8461,6 +8461,37 @@ describe('Model', function() { assert.deepStrictEqual(toDrop, []); }); }); + + describe('insertOne() (gh-14843)', function() { + it('should insert a new document', async function() { + const userSchema = new Schema({ + name: String + }); + const User = db.model('User', userSchema); + + const res = await User.insertOne({ name: 'John' }); + assert.ok(res instanceof User); + + const doc = await User.findOne({ _id: res._id }); + assert.equal(doc.name, 'John'); + }); + + it('should support validateBeforeSave: false option', async function() { + const userSchema = new Schema({ + name: { + type: String, + required: true + } + }); + const User = db.model('User', userSchema); + + const res = await User.insertOne({}, { validateBeforeSave: false }); + assert.ok(res instanceof User); + + const doc = await User.findOne({ _id: res._id }); + assert.equal(doc.name, undefined); + }); + }); }); diff --git a/test/types/models.test.ts b/test/types/models.test.ts index d448712a2d..1c7e1012a0 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -988,3 +988,13 @@ async function gh14802() { const conn2 = mongoose.createConnection('mongodb://127.0.0.1:27017/mongoose_test'); Model.useConnection(conn2); } + +async function gh14843() { + const schema = new mongoose.Schema({ + name: String + }); + const Model = model('Test', schema); + + const doc = await Model.insertOne({ name: 'taco' }); + expectType>(doc); +} diff --git a/types/models.d.ts b/types/models.d.ts index 3bf2c88dc9..835bb58562 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -576,6 +576,13 @@ declare module 'mongoose' { Array>> >; + /** + * Shortcut for saving one document to the database. + * `MyModel.insertOne(obj, options)` is almost equivalent to `new MyModel(obj).save(options)`. + * The difference is that `insertOne()` checks if `obj` is already a document, and checks for discriminators. + */ + insertOne>(doc: DocContents | TRawDocType, options?: SaveOptions): Promise; + /** * List all [Atlas search indexes](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) on this model's collection. * This function only works when connected to MongoDB Atlas.