From 4a79a887c5434ccaee9d890c60d9cdd0ddb43320 Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:51:55 -0400 Subject: [PATCH 1/3] feat: `useConnection(connection)` function --- lib/model.js | 15 +++++++++++++++ test/model.test.js | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/model.js b/lib/model.js index 7b9223d2408..38e9f0f458d 100644 --- a/lib/model.js +++ b/lib/model.js @@ -146,6 +146,21 @@ Model.prototype.$isMongooseModelPrototype = true; Model.prototype.db; +/** + * @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]; + } + this.db = connection; + connection.models[this.modelName] = 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)). diff --git a/test/model.test.js b/test/model.test.js index b73757e4721..71317696517 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -7575,6 +7575,16 @@ describe('Model', function() { assert.strictEqual(doc.__v, 0); }); + it('updates the model\'s db property to point to the provided connection instance and vice versa', async function() { + const schema = new mongoose.Schema({ + name: String + }); + const Model = db.model('Test', schema); + const connection = start(); + Model.useConnection(connection); + assert.equal(db.models[Model.modelName], undefined); + assert(connection.models[Model.modelName]); + }); }); From 3a9afe678a773d52c197fb036cbc2197a04cd894 Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:55:54 -0400 Subject: [PATCH 2/3] more assertions --- lib/model.js | 16 +++++++++++++++- test/model.test.js | 19 ++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/model.js b/lib/model.js index 38e9f0f458d..bab6e06a7ed 100644 --- a/lib/model.js +++ b/lib/model.js @@ -150,14 +150,28 @@ Model.prototype.db; * @api public */ -Model.useConnection = function useConnection(connection) { +Model.useConnection = async 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; }; diff --git a/test/model.test.js b/test/model.test.js index 71317696517..f5e784cccea 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -7575,16 +7575,29 @@ describe('Model', function() { assert.strictEqual(doc.__v, 0); }); - it('updates the model\'s db property to point to the provided connection instance and vice versa', async function() { + it('updates the model\'s db property to point to the provided connection instance and vice versa asdf', async function() { const schema = new mongoose.Schema({ name: String }); const Model = db.model('Test', schema); - const connection = start(); - Model.useConnection(connection); + const connection = start({ uri: start.uri2 }); + const original = Model.find(); + assert.equal(original.model.collection.conn.name, 'mongoose_test'); + await Model.useConnection(connection); assert.equal(db.models[Model.modelName], undefined); assert(connection.models[Model.modelName]); + const res = Model.find(); + assert.equal(res.model.collection.conn.name, 'mongoose_test_2'); }); + 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.' }) + }) }); From e87d506342e46a03cd7126930a69b5d9ca5c9d69 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sat, 26 Oct 2024 13:57:34 -0400 Subject: [PATCH 3/3] feat(model): complete useConnection() --- lib/model.js | 38 ++++++++++++++++++++---- test/model.test.js | 62 ++++++++++++++++++++++++++------------- test/types/models.test.ts | 11 +++++++ types/models.d.ts | 7 +++++ 4 files changed, 91 insertions(+), 27 deletions(-) diff --git a/lib/model.js b/lib/model.js index bab6e06a7ed..512d65dfffa 100644 --- a/lib/model.js +++ b/lib/model.js @@ -147,22 +147,47 @@ 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 = async function useConnection(connection) { +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[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; @@ -173,6 +198,8 @@ Model.useConnection = async function useConnection(connection) { this.collection = collection; this.$__collection = collection; connection.models[this.modelName] = this; + + return this; }; /** @@ -2096,9 +2123,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 f5e784cccea..f1d31c8c00e 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -7575,29 +7575,49 @@ describe('Model', function() { assert.strictEqual(doc.__v, 0); }); - it('updates the model\'s db property to point to the provided connection instance and vice versa asdf', async function() { - const schema = new mongoose.Schema({ - name: String + + 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.' }); }); - const Model = db.model('Test', schema); - const connection = start({ uri: start.uri2 }); - const original = Model.find(); - assert.equal(original.model.collection.conn.name, 'mongoose_test'); - await Model.useConnection(connection); - assert.equal(db.models[Model.modelName], undefined); - assert(connection.models[Model.modelName]); - const res = Model.find(); - assert.equal(res.model.collection.conn.name, 'mongoose_test_2'); }); - 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.' }) - }) }); diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 218c4c90569..0e4636da5d6 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 c042305a828..60ecfd01d4c 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -600,6 +600,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;