5

Lemme take time to explain what is happening from start to finish.

Preamble:

A user a follows 10 other people. When user A logs in, an X number of posts from each of the 10 people are pulled into view.

I do not know if it is the right thing to do, and will appreciate a better way of doing it. However, I wanna give it a try, and it ain't working.

Follow Model:

let mongoose = require('mongoose');
let Schema = mongoose.Schema;

let FollowSchema = new Schema({
  user: {
    type: Schema.Types.ObjectId,
    ref: 'User'
  },
  followers: [{
    type: Schema.Types.ObjectId,
    ref: 'Card'
  }],
  following: [{
    type: Schema.Types.ObjectId,
    ref: 'Card'
  }]
});

module.exports = mongoose.model('Follow', FollowSchema);

Card Model

let mongoose = require('mongoose');
let Schema = mongoose.Schema;

let CardSchema = new Schema({
  title: String,
  content: String,
  createdById: {
    type: Schema.Types.ObjectId,
    ref: 'User'
  },
  createdBy: {
    type: String
  }
});

module.exports = mongoose.model('Card', CardSchema);

Follow logic

When user A follows user B, do two things:

  • Push the user_id of B to user A document on field 'following' (A is following B)
  • Push user_id of A to user B document on field 'followers' (B is followed by A)

    router.post('/follow', utils.loginRequired, function(req, res) {
    const user_id = req.user._id;
    const follow = req.body.follow_id;
    
    let bulk = Follow.collection.initializeUnorderedBulkOp();
    
    bulk.find({ 'user': Types.ObjectId(user_id) }).upsert().updateOne({
        $addToSet: {
            following: Types.ObjectId(follow)
        }
    });
    
    bulk.find({ 'user': Types.ObjectId(follow) }).upsert().updateOne({
        $addToSet: {
            followers: Types.ObjectId(user_id)
        }
    })
    
    bulk.execute(function(err, doc) {
        if (err) {
            return res.json({
                'state': false,
                'msg': err
            })
        }
        res.json({
            'state': true,
            'msg': 'Followed'
        })
    })
    

    })

Actual DB values

> db.follows.find().pretty()
{
    "_id" : ObjectId("59e3e27dace1f14e0a70862d"),
    "user" : ObjectId("59e2194177cae833894c9956"),
    "following" : [
        ObjectId("59e3e618ace1f14e0a708713")
    ]
}
{
    "_id" : ObjectId("59e3e27dace1f14e0a70862e"),
    "user" : ObjectId("59e13b2dca5652efc4ca2cf5"),
    "followers" : [
        ObjectId("59e2194177cae833894c9956"),
        ObjectId("59e13b2d27cfed535928c0e7"),
        ObjectId("59e3e617149f0a3f1281e849")
    ]
}
{
    "_id" : ObjectId("59e3e71face1f14e0a708770"),
    "user" : ObjectId("59e13b2d27cfed535928c0e7"),
    "following" : [
        ObjectId("59e3e618ace1f14e0a708713"),
        ObjectId("59e13b2dca5652efc4ca2cf5"),
        ObjectId("59e21942ca5652efc4ca30ab")
    ]
}
{
    "_id" : ObjectId("59e3e71face1f14e0a708771"),
    "user" : ObjectId("59e3e618ace1f14e0a708713"),
    "followers" : [
        ObjectId("59e13b2d27cfed535928c0e7"),
        ObjectId("59e2194177cae833894c9956")
    ]
}
{
    "_id" : ObjectId("59e3e72bace1f14e0a708779"),
    "user" : ObjectId("59e21942ca5652efc4ca30ab"),
    "followers" : [
        ObjectId("59e13b2d27cfed535928c0e7"),
        ObjectId("59e2194177cae833894c9956"),
        ObjectId("59e3e617149f0a3f1281e849")
    ]
}
{
    "_id" : ObjectId("59f0eef155ee5a5897e1a66d"),
    "user" : ObjectId("59e3e617149f0a3f1281e849"),
    "following" : [
        ObjectId("59e21942ca5652efc4ca30ab"),
        ObjectId("59e13b2dca5652efc4ca2cf5")
    ]
}
> 

With the above database results, this is my query:

Query

router.get('/follow/list', utils.loginRequired, function(req, res) {
    const user_id = req.user._id;

    Follow.findOne({ 'user': Types.ObjectId(user_id) })
      .populate('following')
      .exec(function(err, doc) {
          if (err) {
              return res.json({
                  'state': false,
                  'msg': err
              })
          };

          console.log(doc.username);

          res.json({
              'state': true,
              'msg': 'Follow list',
              'doc': doc
          })
      })
});

With the above query, from my little understanding of Mongoose populate, I expect to get cards from each of the Users in the following array.

My understanding and expectations might be wrong, however with such an endgoal, is this populate approach okay? Or am I trying to solve an aggregation task with population?

UPDATE:

Thanks for the answer. Getting quite close, but still, the followingCards array contains no result. Here's the contents of my current Follow model:

> db.follows.find().pretty()
{
    "_id" : ObjectId("59f24c0555ee5a5897e1b23d"),
    "user" : ObjectId("59f24bda1d048d1edad4bda8"),
    "following" : [
        ObjectId("59f24b3a55ee5a5897e1b1ec"),
        ObjectId("59f24bda55ee5a5897e1b22c")
    ]
}
{
    "_id" : ObjectId("59f24c0555ee5a5897e1b23e"),
    "user" : ObjectId("59f24b3a55ee5a5897e1b1ec"),
    "followers" : [
        ObjectId("59f24bda1d048d1edad4bda8")
    ]
}
{
    "_id" : ObjectId("59f24c8855ee5a5897e1b292"),
    "user" : ObjectId("59f24bda55ee5a5897e1b22c"),
    "followers" : [
        ObjectId("59f24bda1d048d1edad4bda8")
    ]
}
>

Here are all the current content I have from Card Model:

> db.cards.find().pretty()
{
    "_id" : ObjectId("59f24bc01d048d1edad4bda6"),
    "title" : "A day or two with Hubtel's HTTP API",
    "content" : "a day or two",
    "external" : "",
    "slug" : "a-day-or-two-with-hubtels-http-api-df77056d",
    "createdBy" : "seanmavley",
    "createdById" : ObjectId("59f24b391d048d1edad4bda5"),
    "createdAt" : ISODate("2017-10-26T20:55:28.293Z"),
    "__v" : 0
}
{
    "_id" : ObjectId("59f24c5f1d048d1edad4bda9"),
    "title" : "US couple stole goods worth $1.2m from Amazon",
    "content" : "for what",
    "external" : "https://bbc.com",
    "slug" : "us-couple-stole-goods-worth-dollar12m-from-amazon-49b0a524",
    "createdBy" : "nkansahrexford",
    "createdById" : ObjectId("59f24bda1d048d1edad4bda8"),
    "createdAt" : ISODate("2017-10-26T20:58:07.793Z"),
    "__v" : 0
}

With the Populate Virtual example from yours (@Veeram), here's the response I get:

{"state":true,"msg":"Follow list","doc":{"_id":"59f24c0555ee5a5897e1b23d","user":"59f24bda1d048d1edad4bda8","following":["59f24b3a55ee5a5897e1b1ec","59f24bda55ee5a5897e1b22c"],"followers":[],"id":"59f24c0555ee5a5897e1b23d","followingCards":[]}}

The followingCards array is empty.

Using the $lookup query on the other hand simply returns []

I'm likely missing something?

KhoPhi
  • 9,660
  • 17
  • 77
  • 128

3 Answers3

2

You can use either virtual populate or $lookup operator in aggregation pipeline.

Using Virtual Populate

FollowSchema.virtual('followingCards', {
    ref: 'Card',
    localField: 'following',
    foreignField: 'createdById'
});

Follow.findOne({ 
   'user': Types.ObjectId(user_id) })
   .populate('followingCards')
   .exec(function(err, doc) {      
      console.log(JSON.stringify(doc));
});

Using $lookup aggregation

Follow.aggregate([
  {
    "$match": {
      "user": Types.ObjectId(user_id) 
    }
  },
  {
    "$lookup": {
      "from": "cards",
      "localField": "following",
      "foreignField": "createdById",
      "as": "followingCards"
    }
  }
]).exec(function (err, doc) {
     console.log(JSON.stringify(doc));
})
s7vr
  • 73,656
  • 11
  • 106
  • 127
  • Thanks for the response. Tried your approaches. The `followingCards` array is then part of the response object, however the array contains an empty list. Please see updated question for actual values in DB currently. – KhoPhi Oct 26 '17 at 21:09
  • Np. Can you add the `mongoose.set('debug', true);` at the top of the file to see what queries get sent to mongodb ? Btw I have fixed the lookup to include the correct collection name `cards`. Can you also try lookup aggregation without the match stage to check if it works and then you can put it back ? – s7vr Oct 26 '17 at 23:49
  • The `following` array field values in the `follows` collections doesn't have a matching `createdById` in the `cards` colleciton. Try using the `followers` field instead for the data that you have provided and you can adjust the answer appropriately. – s7vr Oct 26 '17 at 23:53
0
var mongoose = require('mongoose'), Schema = mongoose.Schema

var eventSchema = Schema({
title     : String,
location  : String,
startDate : Date,
endDate   : Date
});

var personSchema = Schema({
firstname: String,
lastname: String,
email: String,
dob: Date,
city: String,
eventsAttended: [{ type: Schema.Types.ObjectId, ref: 'Event' }]
});

var Event  = mongoose.model('Event', eventSchema);
var Person = mongoose.model('Person', personSchema);

To show how populate is used, first create a person object,

   aaron = new Person({firstname: 'Aaron'}) and an event object, 
   event1 = new Event({title: 'Hackathon', location: 'foo'}):

   aaron.eventsAttended.push(event1);
   aaron.save(callback); 

Then, when you make your query, you can populate references like this:

 Person
.findOne({ firstname: 'Aaron' })
.populate('eventsAttended') .exec(function(err, person) {
if (err) return handleError(err);
console.log(person);
 });

// only works if we pushed refs to person.eventsAttended

vijay kumar
  • 1,025
  • 13
  • 11
0

note: change Activity.find to Card.find

const { ObjectID } = require("mongodb");
// import Follow and Activity(Card) schema

const userId = req.tokenData.userId; // edit this too...
Follow.aggregate([
  {
    $match: {
      user: ObjectID(userId)
    }
  }
])
.then(data => {
  // console.log(data)
  var dataUsers = data[0].following.map(function(item) {
    return item._id;
  });
  // console.log(dataUsers)
  Activity.find(
    { createdById: { $in: dataUsers } },
    {
      _id: 1,
      title: 1,
      content: 1,
      createdBy: 1,
      creatorAvatar: 1,
      activityType: 1,
      createdAt: 1
    }
  )
    // .sort({createdAt:-1)
    .then(posts => res.send({ posts }));
});