Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment on lines +193 to +195
Copy link

@meshde-flux meshde-flux Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this makes it such that the name of the collection is always set to the modelName which would be different from the name of the collection assigned initially, which could either be a user-defined collection name or one that is automatically generated by mongoose using the utils.toCollectionName as mentioned here: https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model().

This leads to the case where a new collection is created and used once useConnection is invoked on the model. Is that intentional?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point @meshde-flux , I will open up new issue

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)).
Expand Down Expand Up @@ -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())
Expand Down
43 changes: 43 additions & 0 deletions test/model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
11 changes: 11 additions & 0 deletions test/types/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import mongoose, {
Schema,
Document,
Model,
createConnection,
connection,
model,
Types,
Expand Down Expand Up @@ -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);
}
7 changes: 7 additions & 0 deletions types/models.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,13 @@ declare module 'mongoose' {
*/
updateSearchIndex(name: string, definition: AnyObject): Promise<void>;

/**
* 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<void>;
validate(obj: any): Promise<void>;
Expand Down