2

Let's say I have a collection of users and each user have an array with pages he likes (imagine facebook pages ids):

Current user:

{
  name: "me",
  likes: [123,234,777]
}

Target list:

{
  name: "Jane",
  likes: [111,222,333]
},
{
  name: "Mary",
  likes: [567,234,777,654]
},
{
  name: "John",
  likes: [123,234,567,890,777]
},
{
  name: "Steve",
  likes: [666,777,321,432,543]
},

Result:

{
  name: "John",
  likes: [123,234,777]
},
{
  name: "Mary",
  likes: [234,777]
},
{
  name: "Steve",
  likes: [777]
},
{
  name: "Jane",
  likes: []
},

I want to get an reactive publication of users ordered by users with whom I have the most common likes. I need'it reactive because each user have user presence - online/offline. The array with likes id's is static but user presence is changing over time.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
ciocan
  • 47
  • 4

1 Answers1

1

I'll leave the publish/subscribe and reactive part to you, but the general mechanics of doing a set-intersection without having to iterate the results in code can be peformed using the aggregation framework.

Notably, this is a server side function only, and you need to obtain a reference to the underlying "node native driver" in order to invoke the .aggregate() method at present:

var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;

In fact for MongoDB 2.6 and above there is a $setIntersection operator that is available to make this simple:

db.targets.aggregate([

    // You probably want a $match to find "friends" first
    { "$match": { "_id": { "$in": [ friends ] } } },

    // Project the set intersection
    { "$project": {
        "name": 1,
        "likes": {
            "$setIntersection": [ "$likes", [123,234,777] ]
        }
    }}
])

It is still basically possible with MongoDB versions prior to 2.6, you just need to manually do all the work that the $setIntersection operator allows:

db.targets.aggregate([

    // You probably want a $match to find "friends" first
    { "$match": { "_id": { "$in": [ friends ] } } },

    // Project an array with the matching values
    { "$project": {
        "name": 1,
        "likes": 1,
        "mylikes": { "$cond": [ 1, [123,234,777], 0 ] }
    }},

    // Unwind both of the arrays
    { "$unwind": "$likes" },
    { "$unwind": "$mylikes" },

    // Work out where those likes are the same. Intersection.
    { "$project": {
        "name": 1,
        "likes": 1,
        "match": { "$eq": [ "$likes", "$mylikes" ] }  
    }},

    // Filter the results that did not match
    { "$match": { "match": true } },

    // Group back the array of likes
    { "$group": {
        "_id": "$_id",
        "name": { "$first": "$name" },
        "likes": { "$push": "$likes" }
    }}
])

That last one removes the entry for "Jane" since there are no matching likes, but is more or less the desired result.

If that interests you, then you should take a look at this post which covers using aggregate with meteor in more detail. But the general method is here and that should provide a snappier and more elegant solution than processing the results in the client in order to find the intersection of the sets.

Community
  • 1
  • 1
Neil Lunn
  • 148,042
  • 36
  • 346
  • 317