Skip to content

Ref array .push crashes with "Model is not a constructor" if populated in .find().cursor() call #13575

@tomaskallup

Description

@tomaskallup

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Mongoose version

6.6.5, tested also with 7.3.1

Node.js version

16.19.1

MongoDB server version

5.0.17

Typescript version (if applicable)

No response

Description

When using .populate during a .find query with .cursor().eachAsync on a relation array (field: [{ type: Schema.Types.ObjectId, ref: 'MyModel' }]), all .push calls (and possibly other array methods) fail with Model is not a costructor seemingly because the populated field is not correctly marked as populated.

Steps to Reproduce

Create a new npm/yarn folder, install mongoose and run following script:

const mongoose = require("mongoose");

const { Schema } = mongoose;

const Company = mongoose.model(
  "Company",
  new Schema({ name: String }),
  "companies"
);
const User = mongoose.model(
  "User",
  new Schema({
    name: String,
    companies: [{ type: Schema.Types.ObjectId, ref: "Company" }],
  }),
  "users"
);

const main = async () => {
  await mongoose.connect(
    "mongodb://localhost:27017/populate-push-crash-testing"
  );

  await Company.insertMany([{ name: "Company 1" }, { name: "Company 2" }]);
  const company = await Company.findOne({ name: "Company 1" });
  const company2 = await Company.findOne({ name: "Company 2" });

  await User.insertMany([{ name: "User 1", companies: [company.id] }]);

  await User.find()
    .populate("companies")
    .cursor()
    .eachAsync(async (user) => {
      if (!user.populated("companies")) await user.populate("companies");

      user.companies.push(company2);

      await user.save();
    });
};

main()
  .catch(console.error)
  .finally(() => {
    process.exit(0);
  });

It will produce following error:

TypeError: Model is not a constructor
    at Proxy._cast (PROJECT-PATH/node_modules/mongoose/lib/types/array/methods/index.js:242:17)
    at Proxy._mapCast (PROJECT-PATH/node_modules/mongoose/lib/types/array/methods/index.js:260:17)
    at Arguments.map (<anonymous>)
    at Proxy.push (PROJECT-PATH/node_modules/mongoose/lib/types/array/methods/index.js:683:21)
    at PROJECT-PATH/index.js:36:22
    at handleNextResult (PROJECT-PATH/node_modules/mongoose/lib/helpers/cursor/eachAsync.js:173:22)
    at PROJECT-PATH/node_modules/mongoose/lib/helpers/cursor/eachAsync.js:164:11
    at PROJECT-PATH/node_modules/mongoose/lib/cursor/QueryCursor.js:552:7
    at PROJECT-PATH/node_modules/kareem/index.js:191:16
    at processTicksAndRejections (node:internal/process/task_queues:78:11)

However if you remove the .populate call in the .find query, everything seems to work fine. The example is simplified, in our real case we call a method on the model and want to be sure the field is populated, for now I've added a depopulate call and a populate call after that, which makes it work.

Another interesting thing is if I remove the cursor and simply do:

  for (const user of await User.find().populate("companies")) {
    if (!user.populated("companies")) await user.populate("companies");

    user.companies.push(company2);

    await user.save();
  }

It also seems to work, so this error might be related to cursor?

Expected Behavior

There should be no Error, mongoose should correctly mark the field as populated and the push should work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmed-bugWe've confirmed this is a bug in Mongoose and will fix it.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions