micxav
micxav

Reputation: 41

Populate embedded ._id in an Object without creating a separate collection for it

How do I populate an embedded and nested object from another collection?

User Schema:

const walletSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: true
    },
    balance: {
      type: Number,
      required: true
    }
  }
);

const userSchema = new mongoose.Schema(
  {
    username: {
    type: String,
    required: true
    },
    wallets: {
      type: [walletSchema],
      default: []
    }
  }
);

const User = mongoose.model('User',userSchema);
export default User;

Expense schema:

const expenseSchema = new mongoose.Schema(
{
   userId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User',
      required: true
   },
   walletId: { // \<-- will store the respective wallet id of the User
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User.walets.walletId',     // \<-- THE PROBLEM
      required: true
   },
   amount: {
      type: Number,
      required: true
   }
);

const Expense = mongoose.model('Expense',expenseSchema);
export default Expense;

I embedded the wallets in the User object as I don't expect the user to have a thousand wallets and I will be accessing the wallets property frequently. Therefore, I didn't separate it into its own collection.

Is there a way to populate this field? Or do I have to do it manually? The user's expenses will be shown in a table displaying each expense's information

Expected result:

[{
  _id: new ObjectId('666418ed4e1b1ffa8ec19232'),  
  userId: {
    username: 'JohnDoe',
    wallets:[{
      _id: new ObjectId('666323b5fb1a3011bdc3ec29'),
      name: 'Savings',
      amount: 1234
    }]
  },  
  walletId: {
    _id: new ObjectId('666323b5fb1a3011bdc3ec29'),
    name: 'Savings',
    amount: 1234
  },  
  amount: 200,
}]

Upvotes: 0

Views: 88

Answers (1)

jQueeny
jQueeny

Reputation: 8027

You can use an aggregation without changing your schema like so:

const expense = await Expense.aggregate([
  {
    // Optional Stage: Match an expenses document
    $match: {
      "_id": req.params.id //< or from wherever you get an id
    }
  },
  {
    //Populate the expenses document with user details
    $lookup: {
      from: "users",
      localField: "userId",
      foreignField: "_id",
      as: "userId"
    }
  },
  {
    // Turn the new userId array field into just an object
    $unwind: "$userId"
  },
  {
    // Filter the wallets array to just get the one that matches walletId
    $set: {
      "walletId": {
        $filter: {
          input: "$userId.wallets",
          as: "wallet",
          cond: {
            $eq: [
              "$$wallet._id",
              "$walletId"
            ]
          }
        }
      }
    }
  },
  {
    // Turn the new array into just an object
    $unwind: "$walletId"
  }
])

See HERE for a working example.

Upvotes: 0

Related Questions