javascriptnode.jstypescriptmongodbmongoose

populate a field where references can be from two different collections


I have a array of messages messages = await ChatMessage.find() , each message has replyTo field which is defined as mongoose.Schema.Types.ObjectId referencing ref:chattingMessage document.

In db , replyTo stores references of two types of objects chattingMessage and groupMessage. I want to populate replyTo given it stores references to any type objects.

I implemented a function which populates all the messages which are referenced to chattingMessage correctly but fails for groupMessage. Here is the implementation.

// chattingMessage -> ChatMessage , groupMessage -> Message
async function populateReplyTo(messages) {
  const populatedMessages = await Promise.all(messages.map(async (message) => {
    if (message.replyTo) {
      let replyMessage = await ChatMessage.findById(message.replyTo);
      if (!replyMessage) replyMessage = await Message.findById(message.replyTo);

      if (replyMessage) {
        console.log('Found replyMessage:', replyMessage); // even though it console logs correctly
        message.replyTo = replyMessage; // populated message is not assigned ?? 
      } else {
        console.log('No replyMessage found, assigning null'); 
        message.replyTo = null;
      }
    }

    if (message.replyTo && message.replyTo.sender) {
      const replySender = await User.findById(message.replyTo.sender).select('_id username name');
      if (replySender) {
        message.replyTo.sender = replySender; 
      }
    }
    
    return message;
  }));
  console.log(populatedMessages)
  return populatedMessages;
}

Here is logging of terminal

Found replyMessage: {
  _id: new ObjectId("6714e824c212c22e48b96c89"),
  sender: new ObjectId("6714e1acc212c22e48b92089"),
  content: 'hii',
  chat: new ObjectId("66604d0079ebab20eec4da73"),
  replyTo: null,
  status: 'Read',
  messageType: 'ChatMessage',
  deletedBy: [],
  reports: [],
  isDeleted: false,
  attachments: [],
  createdAt: 2024-10-20T11:23:16.998Z,
  updatedAt: 2024-10-23T13:45:07.054Z,
  __v: 0
} but populated one is  not assigned , instead i only find reference in replyTo

[

 {
    _id: new ObjectId("671681fada25dcde60b63526"),
    sender: new ObjectId("66dafffec77d3018b2b0ddb3"),
    receiver: new ObjectId("6714e1acc212c22e48b92089"),
    messageType: 'ChatMessage',
    subscriptionRequestMessageAction: 'none',
    message: 'where to start',
    replyTo: new ObjectId("9023e824c212c22e48b96c89"), // why reference , even though I got the object logged above
    status: 'Read',
    deletedBy: [],
    reports: [],
    createdAt: 2024-10-21T16:31:54.514Z,
    updatedAt: 2024-10-23T15:47:24.551Z,
    __v: 0
  }

]

clearly there is a problem in assignment. or Am I missing something. Any help is appreciated..


Solution

  • When you 'find' records from mongoose, they are not technically 'objects' in the JavaScript sense, but are 'Document' objects which are a mongoose specific class object. Directly changing these document objects can result in unexpected behavior.

    You have to convert these documents into simple JS objects with one of these methods:

    // if your `message` parameter is a 'Document', turn to objects.
    message.toObject();
    
    ...
    
    // probably should make the messages objects too
    
    ..... await ChatMessage.findById(message.replyTo).toObject();
    //                                                ^^^^^^^^^^ call this
    

    Also, .toObject() will make your queries faster too