Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useDB doesn't allow to populate. #11003

Closed
manojsethi opened this issue Nov 23, 2021 · 4 comments
Closed

useDB doesn't allow to populate. #11003

manojsethi opened this issue Nov 23, 2021 · 4 comments
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Milestone

Comments

@manojsethi
Copy link

bug

What is the current behavior?
When we are using the useDB approach to achieve the multitenancy then the mongoose doesn't have information about the nested schema's

const db = Mongoose.connection.useDb(databaseName, {
      useCache: true,
      noListener: true,
    });

We are using typegoose wrapper along with mongoose

export class ClientIdAssociation {
  @prop({ ref: () => BaseUsers })
  user_id: Ref<BaseUsers>;

  @prop({ ref: () => Roles, required: true })
  roles!: Ref<Roles>[];
}
const CLIENT_ID_ASSOCIATION_MODEL = getModelForClass(ClientIdAssociation, {
  schemaOptions: {
    timestamps: true,
    collection: "user_association_with_clients",
  },
});
export default CLIENT_ID_ASSOCIATION_MODEL;

Now when we are trying to get the ref populated it says schema hasn't been registered for the "Roles", or any other ref property.
If we don't use the useDB(required for multitenancy) and just for testing we use the db name in connection string itself like mongodb://localhost:27017/theDB then it works fine.

If the current behavior is a bug, please provide the steps to reproduce.
My Model

import { getModelForClass, prop } from "@typegoose/typegoose";

export class BaseUsers {
  @prop()
  name: string;

  @prop({ unique: true })
  email: string;

  @prop()
  password: string;

  @prop()
  country: string;

  @prop()
  pin_code: string;

  @prop()
  mobile_number: string;

  @prop()
  can_be_deleted: boolean;

  @prop()
  avatar: string;
}

const BASE_USERS_MODEL = getModelForClass(BaseUsers, {
  schemaOptions: {
    collection: "users",
    timestamps: true,
  },
});
export default BASE_USERS_MODEL;

import { getModelForClass, prop } from "@typegoose/typegoose";

export class Roles {
  @prop()
  name: string;
}

const RolesModel = getModelForClass(Roles);
export default RolesModel;

Creating dynamic connection via DBConfigClass as below

import { AnyParamConstructor, ReturnModelType } from "@typegoose/typegoose/lib/types";
import Mongoose from "mongoose";

class DatabaseConfig {
  public database: Mongoose.Connection;
  public connectDatabase = async (): Promise<any> => {
    const uri = "mongodb://localhost:27017" ?? "";
    if (this.database) {
      return;
    }
    await Mongoose.connect(uri, {});
    this.database = Mongoose.connection;
    this.database.once("open", async () => {
      console.log("Connected to database");
    });
    this.database.on("error", () => {
      console.log("Error connecting to database");
    });
  };
  disconnectDatabase = async () => {
    if (!this.database) {
      return;
    }
    await Mongoose.disconnect();
  };

  getModelForDb<T extends AnyParamConstructor<any>>(databaseName: string, model: ReturnModelType<T>): ReturnModelType<T> & T {
    const db = Mongoose.connection.useDb(databaseName, {
      useCache: true,
      noListener: true,
    });

    const DbModel = db.model(model.modelName, model.schema) as ReturnModelType<T> & T;
    return DbModel;
  }
}

export default new DatabaseConfig();

This is how we are using this config for making dynamic connections

import { mongoose } from "@typegoose/typegoose";
import { Request } from "express";
import databaseConfig from "./config/database.config";
import CLIENT_ID_ASSOCIATION_MODEL, { ClientIdAssociation } from "./model/client_id_association";


class BaseService {
  get_user_details = async (req: Request): Promise<any> => {
    try {
      let client_id_association_model = databaseConfig.getModelForDb<typeof ClientIdAssociation>("ims_base_db", CLIENT_ID_ASSOCIATION_MODEL);

      let found_users = await client_id_association_model
        .find({
          user_id: new mongoose.Types.ObjectId("6103c782f67f5162439f7d8e"),
        })
        .populate(["roles"]);
      return {
        status_code: 200,
        data: {
          found_users,
        },
      };
    } catch (error) {
      console.log(error);
      return {
        status_code: 500,
        data: {
          message: "Internal server error",
          error: "On Fetch Error",
        },
      };
    }
  };
}
export default new BaseService();

My starting server file looks like this

import express, { Application, NextFunction, Request, Response } from "express";
import "reflect-metadata";
import base_user_service from "./base_service";
import databaseConfig from "./config/database.config";
const app: Application = express();

app.use(express.json({ limit: "50mb" }));

app.use(express.urlencoded({ extended: true, limit: "50mb" }));

databaseConfig.connectDatabase();
export const rootPath = __dirname;

app.get("/", async (req: Request, res: Response, next: NextFunction) => {
  let result = await base_user_service.get_user_details(req);

  if (result && result.status_code === 200)
    return res.status(result.status_code).json({
      status_code: result.status_code,
      success: true,
      data: result.data,
    });
  else
    return res.status(result.status_code).json({
      status_code: result.status_code,
      success: false,
      errors: [result.data],
    });
});

const PORT = 5002;

app.listen(PORT, () => console.log(`App listening in ${process.env.NODE_ENV} mode on port - ${PORT}`));

Error on calling the endpoint
MissingSchemaError: Schema hasn't been registered for model "Roles".
Use mongoose.model(name, schema)
    at NativeConnection.Connection.model (C:\Users\manoj\OneDrive\Desktop\base_service\node_modules\mongoose\lib\connection.js:1133:11)      
    at addModelNamesToMap (C:\Users\manoj\OneDrive\Desktop\base_service\node_modules\mongoose\lib\helpers\populate\getModelsMapForPopulate.js:470:28)
    at getModelsMapForPopulate (C:\Users\manoj\OneDrive\Desktop\base_service\node_modules\mongoose\lib\helpers\populate\getModelsMapForPopulate.js:174:7)
    at populate (C:\Users\manoj\OneDrive\Desktop\base_service\node_modules\mongoose\lib\model.js:4437:21)
    at _populate (C:\Users\manoj\OneDrive\Desktop\base_service\node_modules\mongoose\lib\model.js:4408:5)
    at C:\Users\manoj\OneDrive\Desktop\base_service\node_modules\mongoose\lib\model.js:4385:5
    at promiseOrCallback (C:\Users\manoj\OneDrive\Desktop\base_service\node_modules\mongoose\lib\helpers\promiseOrCallback.js:10:12)
    at Mongoose._promiseOrCallback (C:\Users\manoj\OneDrive\Desktop\base_service\node_modules\mongoose\lib\index.js:1151:10)
    at Function.Model.populate (C:\Users\manoj\OneDrive\Desktop\base_service\node_modules\mongoose\lib\model.js:4383:23)
    at C:\Users\manoj\OneDrive\Desktop\base_service\node_modules\mongoose\lib\query.js:2136:19

Typescript tsconfig.json


{
  "compilerOptions": {
    "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
    "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
    "lib": ["es6"] /* Specify library files to be included in the compilation. */,
    "allowJs": true /* Allow javascript files to be compiled. */,
    "outDir": "build" /* Redirect output structure to the directory. */,
    "downlevelIteration": true /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */,
    "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */,
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
    "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
    "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
    "resolveJsonModule": true /* Include modules imported with '.json' extension */,
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
    "strictPropertyInitialization": false
  }
}

What is the expected behavior?

The expected behaviour is to populate the child objects with the useDB option. Currently the schema doesn't get registered for the referenced schemas. When we are using the actual connection string with the db name it works fine. This approach was also tested with mongoose version ~5.10.x and was working well but since stubs are gone we wanted to upgrade to the latest version.

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.

NodeJS - 14.16.0
Typescript - 4.5.2
NPM - 8.1.4
"@typegoose/typegoose": "^9.1.0",
"express": "^4.17.1",
"mongoose": "^6.0.11"

@manojsethi
Copy link
Author

"@typegoose/typegoose": "8.3.0",
"express": "^4.17.1",
"mongoose": "5.13.13"

With the latest version of mongoose 5, the above functionality works as expected.

@hasezoey
Copy link
Collaborator

reproduction code (pure mongoose, minified from example): https://github.com/typegoose/typegoose-testing/tree/verify633

checkout commit 072941c42d9c0eb8ed588b7e757e4a8435c46c46 for mongoose 6.x behavior
checkout commit ff56b173622d23aff894932e2ad6c66b5c78f1d4 for mongoose 5.x behavior

output on 6.x:

baseInChanged {
  something: 'helloInChanged2',
  reference: new ObjectId("619cdac1ce8470ff0831ca1c"),
  _id: new ObjectId("619e2ccfdc2dee0ab26e0eb6"),
  __v: 0
}
MissingSchemaError: Schema hasn't been registered for model "Nested".
Use mongoose.model(name, schema)
    at NativeConnection.Connection.model (/mnt/projects/nodejs/typegoose-testing/node_modules/mongoose/lib/connection.js:1133:11)
    at addModelNamesToMap (/mnt/projects/nodejs/typegoose-testing/node_modules/mongoose/lib/helpers/populate/getModelsMapForPopulate.js:470:28)
    at getModelsMapForPopulate (/mnt/projects/nodejs/typegoose-testing/node_modules/mongoose/lib/helpers/populate/getModelsMapForPopulate.js:174:7)
    at populate (/mnt/projects/nodejs/typegoose-testing/node_modules/mongoose/lib/model.js:4445:21)
    at _populate (/mnt/projects/nodejs/typegoose-testing/node_modules/mongoose/lib/model.js:4416:5)
    at /mnt/projects/nodejs/typegoose-testing/node_modules/mongoose/lib/model.js:4393:5
    at /mnt/projects/nodejs/typegoose-testing/node_modules/mongoose/lib/helpers/promiseOrCallback.js:32:5
    at new Promise (<anonymous>)
    at promiseOrCallback (/mnt/projects/nodejs/typegoose-testing/node_modules/mongoose/lib/helpers/promiseOrCallback.js:31:10)
    at Mongoose._promiseOrCallback (/mnt/projects/nodejs/typegoose-testing/node_modules/mongoose/lib/index.js:1151:10)

output on 5.x:

baseInChanged {
  _id: 619e2cf912726254c5a44501,
  something: 'helloInChanged2',
  reference: 619cdac1ce8470ff0831ca1c,
  __v: 0
}
baseInChanged populated {
  _id: 619e2cf912726254c5a44501,
  something: 'helloInChanged2',
  reference: 619cdac1ce8470ff0831ca1c,
  __v: 0
}

Note: no error, it just didnt populate

note: because this was originally a typegoose issue, the code still contains typegoose things, but commented out

i think this may be related to https://mongoosejs.com/docs/migrating_to_6.html#strictpopulate

@vkarpov15 vkarpov15 added this to the 6.0.15 milestone Nov 27, 2021
@vkarpov15 vkarpov15 added the has repro script There is a repro script, the Mongoose devs need to confirm that it reproduces the issue label Nov 27, 2021
@IslandRhythms
Copy link
Collaborator

Can repro the 6.x behavior. I can't run the 5.x code cause npm throws an error saying there is conflicting dependencies

@IslandRhythms
Copy link
Collaborator

I did a --force and was able to run it

@IslandRhythms IslandRhythms added confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. and removed has repro script There is a repro script, the Mongoose devs need to confirm that it reproduces the issue labels Nov 29, 2021
@vkarpov15 vkarpov15 modified the milestones: 6.0.15, 6.0.16, 6.0.17 Dec 6, 2021
vkarpov15 added a commit that referenced this issue Dec 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Projects
None yet
Development

No branches or pull requests

4 participants