1

Problem:

Trying to populate the User object products: with all matching Products Object, but returns empty array

Before posting this question I did the following:

I have read the mongoose Docs, tried implementing as stated - to no avail

  1. mongodb populate method not working
  2. MongoDB Combining Collections - Not Populating
  3. Mongoose populate() returns empty array with no errors
  4. Mongoose populate returns empty array or list of ObjectIds
  5. Mongoose populate not populating an array and always returns an empty array
  6. Mongoose returning empty array after populate() method and all 7 links he tried aswell

The following in all possible combinations : -async -exec() -then() -populate() with/without specifying path, model, select

here is my repo incase more details are needed: https://github.com/FlyingVespa/Capstone-BE/tree/main/src

User Schema:

const userSchema = new Schema(
      {
        role: notReqString,
        email: {
          type: String,
          lowercase: true,
          required: [true, "An email is required."],
          unique: [true, "An email is already registered."],
          match: [/.+\@.+\..+/, "Not a valid email"],
          // validate: [isEmail, "Please enter valid email"],
        },
        password: reqString,
        url: notReqString,
        businessname: reqString,
        category: notReqString,
        username: notReqString,
        address: {
          lat: notReqString,
          lng: notReqString,
          street_number: notReqString,
          street_name: notReqString,
          city: notReqString,
          state: notReqString,
          country: notReqString,
        },
        companydetails: {
          bio: notReqString,
          mobile: notReqString,
          public_email: notReqString,
          store_services: { type: Array },
          shipping: notReqString,
        },
        tradingtimes: [
          {
            day: notReqString,
            trading: reqBoolean,
            open: notReqString,
            closed: notReqString,
          },
          {
            day: notReqString,
            trading: reqBoolean,
            open: notReqString,
            closed: notReqString,
          },
          {
            day: notReqString,
            trading: reqBoolean,
            open: notReqString,
            closed: notReqString,
          },
          {
            day: notReqString,
            trading: reqBoolean,
            open: notReqString,
            closed: notReqString,
          },
          {
            day: notReqString,
            trading: reqBoolean,
            open: notReqString,
            closed: notReqString,
          },
          {
            day: notReqString,
            trading: reqBoolean,
            open: notReqString,
            closed: notReqString,
          },
          {
            day: notReqString,
            trading: reqBoolean,
            open: notReqString,
            closed: notReqString,
          },
        ],
    
        img_logo: {
          ...notReqString,
          default: () => {
            return `https://eu.ui-avatars.com/api/?name=Test`;
          },
        },
    
        products: [{ type: Schema.Types.ObjectId, ref: "Product" }], /*REFRENCING HERE*/
      },
      { timestamps: true }
    );

userSchema.pre("save", async function (next) {
  const newUser = this;
  const plainPW = newUser.password;
  if (newUser.isModified("password")) {
    newUser.password = await bcrypt.hash(plainPW, 10);
  }
  next();
});

userSchema.methods.toJSON = function () {
  const userDocument = this;
  const userObject = userDocument.toObject();
  delete userObject.password;
  delete userObject.__v;
  delete userObject.refreshToken;
  return userObject;
};

export default model("User", userSchema);

Product Schema: import mongoose from "mongoose";

const { Schema, model } = mongoose;
const reqString = { type: String, required: false };
const notReqString = { type: String, required: false };
const reqNumber = { type: Number, required: true };
const productSchema = new Schema(
  {
    businessId: {
      type: Schema.Types.ObjectId,
      ref: "User",
      required: true,
    },
    name: notReqString,
    price: reqNumber,
    units: notReqString,
    status: notReqString,
    sku: notReqString,
    brand: notReqString,
    description: notReqString,

    image: {
      ...notReqString,
      default: () => {
        return `https://eu.ui-avatars.com/api/?name=product`;
      },
    },
  },
  { timestamps: true }
);

productSchema.methods.toJSON = function () {
  const productDocument = this;
  const productObject = productDocument.toObject();
  delete productObject.__v;
  return productObject;
};

export default model("Product", productSchema);

Add new product:

export const addNewProduct = async (req, res, next) => {
  try {
    const userId = req.params.userId;
    console.log(req.params);
    const user = await User.findById(userId);
    if (!user) {
      return next(createError(404, `User with id ${userId} not found`));
    }
    const newProductData = { ...req.body, businessId: userId };
    const newProduct = new Product(newProductData);
    const createdProduct = await newProduct.save();

    res.status(201).send(createdProduct);
  } catch (error) {
    if (error.name === "ValidationError") {
      next(createError(400, error));
    } else {
      next(createError(500, error));
    }
  }
};

Fetch Single User:

  export const getSingleUser = async (req, res, next) => {
      try {
        const userId = req.params.userId;
        User.findById(userId)
          .populate({ path: "products", model: "Product" })
          .exec((err, result) => {
            if (err) {
              console.log("err", err);
              return res.send({ error: err });
            }
            console.log("result", result);
            res.send({ result: result });
          });
      } catch (error) {
        next(error);
      }
    };

current log output:

{
    "result": {
        "address": {
            "lat": "38.2305534",
            "lng": "15.5532993",
            "street_number": "1256",
            "street_name": "Autumn Leaf",
            "city": "Messina",
            "state": "Sicilia",
            "country": "Italy"
        },
        "companydetails": {
            "bio": "i vel nisl. Duis ac nibh. Fusce lacus purus, aliquet at",
            "mobile": "139-432-2309",
            "public_email": "rgunton0@soup.io",
            "store_services": [
                "Overhead Doors",
                "HVAC",
                "Temp Fencing, Decorative Fencing and Gates"
            ],
            "shipping": "false"
        },
        "_id": "62569c28dff1ca6175d48299",
        "role": "user",
        "email": "malgeo0@boston.com",
        "url": "Orca",
        "businessname": "Kaymbo",
        "category": "Capital Goods",
        "username": "llefort0",
        "tradingtimes": [
            {
                "day": "0",
                "trading": true,
                "_id": "62569c28dff1ca6175d4829a"
            },
            {
                "day": "1",
                "trading": false,
                "_id": "62569c28dff1ca6175d4829b"
            },
            {
                "day": "2",
                "trading": true,
                "_id": "62569c28dff1ca6175d4829c"
            },
            {
                "day": "3",
                "trading": true,
                "_id": "62569c28dff1ca6175d4829d"
            },
            {
                "day": "4",
                "trading": false,
                "_id": "62569c28dff1ca6175d4829e"
            },
            {
                "day": "5",
                "trading": false,
                "_id": "62569c28dff1ca6175d4829f"
            },
            {
                "day": "6",
                "trading": false,
                "_id": "62569c28dff1ca6175d482a0"
            }
        ],
        "products": [],
        "img_logo": "https://eu.ui-avatars.com/api/?name=Test",
        "createdAt": "2022-04-13T09:47:20.541Z",
        "updatedAt": "2022-04-13T09:47:20.541Z"
    }
}

modules versions:

  "node": v14.17.5 
  "express":"^4.17.1", 
  "mongodb": "^4.1.3",
  "mongoose": "^6.2.10"
Shivam
  • 3,514
  • 2
  • 13
  • 27
  • Where are you saving the newly created product to the user? Right now you are just saving new product using `await newProduct.save();` but you are not adding it to user. – Shivam Apr 13 '22 at 13:21
  • const newProductData = { ...req.body, businessId: userId }; <--- I am getting the userId from the params – FlyingVespa Apr 13 '22 at 13:23
  • When you save the product, you have to add the product's _id to user's products array, if you don't do that you will always get empty array. – Shivam Apr 13 '22 at 13:29
  • is that where I implement "push"? as per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push – FlyingVespa Apr 13 '22 at 13:34

1 Answers1

0

When you save the new product, you have to add the product's _id (which is returned) to the user's product's array.

Something like this

const createdProduct = await newProduct.save();

// Since you have access to user you can do
user.products.push(createdProduct._id);
await user.save();

Check Saving refs in mongoose

Shivam
  • 3,514
  • 2
  • 13
  • 27
  • thanks! this did the job! i tried user.products.push(createdProduct._id); on a previous occasion but did not do user.save(). So I presume If I import a json file directly into mongodb, it wont populate unless each product is added manually due to the push. is there perhaps a way to populate by checking the database and then update the user if products found ? Otherwise I'll spend ages trying to add data to database... – FlyingVespa Apr 13 '22 at 14:22
  • Since you need to have `ObjectID` in user's product's array, you need to save product in order to get the ID (it's auto generated by mongo). You can open another question, since you are importing a file to mongo (this was not mentioned in this question) – Shivam Apr 13 '22 at 14:31