简体   繁体   中英

Populate field in an array of objects MongoDB and Mongoose

I'm building a comment system for my website. I've created this schema for the article and it's comments:

let articleSchema = mongoose.Schema({

  language: {
    type: String,
    default: "english"
  },
  slug: {
    type: String,
    required: true
  },
  image: {
    type: String,
    required: true
  },
  title: {
    type: String,
    required: true
  },
  tags: {
    type: [String],
    required: true
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "User",
    required: true
  },
  date: {
    type: Date,
    default: Date.now()
  },
  description: {
    type: String,
    required: false
  },
  body: {
    type: String,
    required: true
  },

  translation: [
    {
      language: {
        type: String,
        required: true
      },
      title: {
        type: String,
        required: true
      },
      tags: {
        type: [String],
        required: true
      },
      description: {
        type: String,
        required: false
      },
      body: {
        type: String,
        required: true
      }
    }
  ],
  comments: [{
    author: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "User"
    },
    date: {
      type: Date,
      default: Date.now()
    },
    body: {
      type: String,
      required: true
    }
  }]
});

Now what I want to do is to populate every comment author field.

Let's say this is the article with a comment:

{
  "comments": [
   {
    "author": "5f71d3b575f9f800943903af",
    "date": "some date here",
    "body": "comment body"
   }
  ]
}

Now what I want to get after I populated all of this is this:

{
  "comments": [
   {
    "author": {
      "username": "Username",
      "avatar": "fox"
    },
    "date": "some date here",
    "body": "comment body"
   }
  ]
}

How can I do that?

Here's my current code:


router.get('/article/:id', async (req, res) => {
  const { id: articleId } = req.params;

  const lang = req.i18n.language;
  const langName = new Intl.DisplayNames(['en'], { type: "language" }).of(lang).toLowerCase();

  try {

    console.log(langName);

    if (req.i18n.language === "en") {
      var article = await Article.findById(articleId)
        .populate([
          {path:'comments.author', select:'+photo +username'},
          {path:'author', select:'+photo +username'}
        ])
        .select("title image tags author date description body comments");
    } else {

      var article = await Article
        .aggregate([
          {
            $match: {
              "_id": ObjectId(articleId)
            }
          }, {
            $unwind: "$translation"
          }, {
            $match: {
              "translation.language": langName
            }
          }, {
            $group: {
              _id: "$_id",
              image: { $first: "$image" },
              title: { $first: "$translation.title" },
              tags: { $first: "$translation.tags" },
              author: { $first: "$author" },
              date: { $first: "$date" },
              description: { $first: "$translation.description" },
              body: { $first: "$translation.body" },
              comments: { $first: "$comments" }
            }
          },
          {
            $lookup: {
              from: "users",
              localField: "author",
              foreignField: "_id",
              as: "author"
            }
          },
          {
            $unwind: {
              path: "$author"
            }
          },
          { ///////////////////// Here I need to populate the comment author. How can I do it?
            $lookup: {
              from: "users",
              localField: "comments.author",
              foreignField: "_id",
              as: "comments.author"
            }
          },
          {
            $unwind: {
              path: "$comments.author"
            }
          },
          {
            $project: {
              image: 1,
              title: 1,
              tags: 1,
              date: 1,
              description: 1,
              body: 1,
              // comments: 1,
              "comments.date": 1,
              "comments.body": 1,
              "comments.author.username": 1,
              "comments.author.avatar": 1,
              "author.username": 1,
              "author.avatar": 1
            }
          }
        ], function(err, result) {
          console.log(err)
          return result;
        });
      
      console.log(article[0].comments[0]);
      console.log(article);
    }

    if (!article) throw new Error();
  } catch {
    return res.status(404).render('errors/404');
  }


  res.render('articles/article', {
    article: article[0]
  });
});

So after some digging I came up with this solution:

router.get('/article/:id', async (req, res) => {
  const { id: articleId } = req.params;

  const lang = req.i18n.language;
  const langName = new Intl.DisplayNames(['en'], { type: "language" }).of(lang).toLowerCase();

  try {

    if (req.i18n.language === "en") {
      var article = await Article.findById(articleId)
        .populate([
          {path:'comments.author', select:'+photo +username'},
          {path:'author', select:'+photo +username'}
        ])
        .select("title image tags author date description body comments");
    } else {

      var article = await Article
        .aggregate([
          {
            $match: {
              "_id": ObjectId(articleId)
            }
          }, {
            $unwind: "$translation"
          }, {
            $match: {
              "translation.language": langName
            }
          },
          {
            $lookup: {
              from: "users",
              localField: "author",
              foreignField: "_id",
              as: "author"
            }
          },
          {
            $unwind: {
              path: "$author"
            }
          },
          {
            $unwind: {
              path: "$comments"
            }
          },
          {
            $lookup: {
              from: "users",
              localField: "comments.author",
              foreignField: "_id",
              as: "comments.author"
            }
          },
          {
            $group: {
              _id: "$_id",
              root: { $mergeObjects: '$$ROOT' },
              image: { $first: "$image" },
              title: { $first: "$translation.title" },
              tags: { $first: "$translation.tags" },
              author: { $first: "$author" },
              date: { $first: "$date" },
              description: { $first: "$translation.description" },
              body: { $first: "$translation.body" },
              comments: { $push: "$comments" }
            }
          },
          {
            $project: {
              image: 1,
              title: 1,
              tags: 1,
              date: 1,
              description: 1,
              body: 1,
              "comments.date": 1,
              "comments.body": 1,
              "comments.author.username": 1,
              "comments.author.avatar": 1,
              "author.username": 1,
              "author.avatar": 1
            }
          }
        ]);
      
    }

    if (!article) throw new Error();
  } catch {
    return res.status(404).render('errors/404');
  }


  res.render('articles/article', {
    article: article[0]
  });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM