diff --git a/lib/model.js b/lib/model.js index 60e6ee2e14..49bb15c382 100644 --- a/lib/model.js +++ b/lib/model.js @@ -148,6 +148,62 @@ Model.prototype.$isMongooseModelPrototype = true; Model.prototype.db; +/** + * Changes the Connection instance this model uses to make requests to MongoDB. + * This function is most useful for changing the Connection that a Model defined using `mongoose.model()` uses + * after initialization. + * + * #### Example: + * + * await mongoose.connect('mongodb://127.0.0.1:27017/db1'); + * const UserModel = mongoose.model('User', mongoose.Schema({ name: String })); + * UserModel.connection === mongoose.connection; // true + * + * const conn2 = await mongoose.createConnection('mongodb://127.0.0.1:27017/db2').asPromise(); + * UserModel.useConnection(conn2); // `UserModel` now stores documents in `db2`, not `db1` + * + * UserModel.connection === mongoose.connection; // false + * UserModel.connection === conn2; // true + * + * conn2.model('User') === UserModel; // true + * mongoose.model('User'); // Throws 'MissingSchemaError' + * + * Note: `useConnection()` does **not** apply any [connection-level plugins](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.plugin()) from the new connection. + * If you use `useConnection()` to switch a model's connection, the model will still have the old connection's plugins. + * + * @function useConnection + * @param [Connection] connection The new connection to use + * @return [Model] this + * @api public + */ + +Model.useConnection = function useConnection(connection) { + if (!connection) { + throw new Error('Please provide a connection.'); + } + if (this.db) { + delete this.db.models[this.modelName]; + delete this.prototype.db; + delete this.prototype[modelDbSymbol]; + delete this.prototype.collection; + delete this.prototype.$collection; + delete this.prototype[modelCollectionSymbol]; + } + + this.db = connection; + const collection = connection.collection(this.modelName, connection.options); + this.prototype.collection = collection; + this.prototype.$collection = collection; + this.prototype[modelCollectionSymbol] = collection; + this.prototype.db = connection; + this.prototype[modelDbSymbol] = connection; + this.collection = collection; + this.$__collection = collection; + connection.models[this.modelName] = this; + + return this; +}; + /** * The collection instance this model uses. * A Mongoose collection is a thin wrapper around a [MongoDB Node.js driver collection]([MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html)). @@ -2069,9 +2125,8 @@ Model.estimatedDocumentCount = function estimatedDocumentCount(options) { * * #### Example: * - * Adventure.countDocuments({ type: 'jungle' }, function (err, count) { - * console.log('there are %d jungle adventures', count); - * }); + * const count = await Adventure.countDocuments({ type: 'jungle' }); + * console.log('there are %d jungle adventures', count); * * If you want to count all documents in a large collection, * use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/model.html#Model.estimatedDocumentCount()) diff --git a/test/model.test.js b/test/model.test.js index fc8d9619f4..0fba26cb3e 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -7821,6 +7821,49 @@ describe('Model', function() { assert.strictEqual(doc.__v, 0); }); + describe('Model.useConnection() (gh-14802)', function() { + it('updates the model\'s db property to point to the provided connection instance and vice versa (gh-14802))', async function() { + const schema = new mongoose.Schema({ + name: String + }); + const Model = db.model('Test', schema); + assert.equal(db.model('Test'), Model); + const original = Model.find(); + assert.equal(original.model.collection.conn.name, 'mongoose_test'); + await Model.create({ name: 'gh-14802 test' }); + let docs = await original; + assert.equal(docs.length, 1); + assert.strictEqual(docs[0].name, 'gh-14802 test'); + + const connection = start({ uri: start.uri2 }); + await connection.asPromise(); + await Model.useConnection(connection); + assert.equal(db.models[Model.modelName], undefined); + assert(connection.models[Model.modelName]); + const query = Model.find(); + assert.equal(query.model.collection.conn.name, 'mongoose_test_2'); + + await Model.deleteMany({}); + await Model.create({ name: 'gh-14802 test 2' }); + docs = await query; + assert.equal(docs.length, 1); + assert.strictEqual(docs[0].name, 'gh-14802 test 2'); + + assert.equal(connection.model('Test'), Model); + assert.throws(() => db.model('Test'), /MissingSchemaError/); + }); + + it('should throw an error if no connection is passed', async function() { + const schema = new mongoose.Schema({ + name: String + }); + const Model = db.model('Test', schema); + assert.throws(() => { + Model.useConnection(); + }, { message: 'Please provide a connection.' }); + }); + }); + it('insertMany should throw an error if there were operations that failed validation, ' + 'but all operations that passed validation succeeded (gh-13256)', async function() { const userSchema = new Schema({ diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 2575ad3133..2c3b50a766 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -2,6 +2,7 @@ import mongoose, { Schema, Document, Model, + createConnection, connection, model, Types, @@ -977,3 +978,13 @@ function testWithLevel1NestedPaths() { 'foo.one': string | null | undefined }>({} as Test2); } + +async function gh14802() { + const schema = new mongoose.Schema({ + name: String + }); + const Model = model('Test', schema); + + const conn2 = mongoose.createConnection('mongodb://127.0.0.1:27017/mongoose_test'); + Model.useConnection(conn2); +} diff --git a/types/models.d.ts b/types/models.d.ts index 5cc38840d8..897573c245 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -607,6 +607,13 @@ declare module 'mongoose' { */ updateSearchIndex(name: string, definition: AnyObject): Promise; + /** + * Changes the Connection instance this model uses to make requests to MongoDB. + * This function is most useful for changing the Connection that a Model defined using `mongoose.model()` uses + * after initialization. + */ + useConnection(connection: Connection): this; + /** Casts and validates the given object against this model's schema, passing the given `context` to custom validators. */ validate(): Promise; validate(obj: any): Promise;