Returns the user if the user follows each other by aggregating
(First of all, explaining complicated things in English is not my forte. I tried to be as exhaustive as possible so that you might understand my question).
I've recently started working on Mongoose for a node.js based web application I'm developing. Typically, include user systems where each user can follow other users. If userA follows userB, it will add a new document to the Connections collection with userA's "_id" as "idA" and userB's "_id" as "idB".
User collection
{
"_id" : ObjectId("HarrysID"),
"name" : "Harry"
"password": "..."
}
{
"_id" : ObjectId("TomsID"),
"name" : "Tom"
"password": "..."
}
connection collection
{
"_id" : ObjectId("5ac130e9d6239151a86035c7"),
"idA" : ObjectId("TomsID"),
"idB" : ObjectId("HarrysID"),
}
{
"_id" : ObjectId("5ac13102d6239151a86035c8"),
"idA" : ObjectId("HarrysID"),
"idB" : ObjectId("TomsID"),
}
what i am trying to achieve
Without going too deep, you probably know about Twitter's system where you can write a message to a user only if that user follows you. Apparently, it can be done easily with just two requests:
Does Harry follow Tom? If true, does user Tom follow Harry? => Harry can contact Tom!
Now I want to do it on a bigger scale. I need a function that returns a list of users that can be contacted.
I've used a recursive function to do this that first finds every document in Connections with "idA" as the current user. It then takes the returned list of connections and checks to see if a document exists at "idB" followed by "idA" - if not, it removes the document from the final result array. I wouldn't consider myself a database guru, but the system does appear to be inefficient in a number of ways, attracting almost all types of bugs.
Is there a better way? I'm sure Aggregate(), project() and/or group() would help, but how?
What should it look like?
Since Harry follows Tom and Tom follows Harry, Harry's contacts should contain Tom (of followers).
listContacts(ObjectId("HarrysID"), function(err, result) {
console.log("Harry can message these users = ", result);
/* Harry can message these users =
[{
"_id" : ObjectId("5ac13102d6239151a86035c8"),
"idA" : ObjectId("HarrysID"),
"idB" : ObjectId("TomsID"),
}]
*/
});
Thanks for taking your time!
One way to fix this is to create a new collection with the information you need to answer the question, can user B contact user B ? I don't know of a good name, but one possibility is contactable
.
If each document in it represents a user, and the document contains a list of other user IDs belonging to users who can be contacted, this can help you answer the question with just one request, for example:
Contactable Collection
{
"_id" : ObjectId("HarrysID"),
"can_contact" : [ObjectId("TomsID"), ObjectId("AlicesID"), ...]
}
{
"_id" : ObjectId("TomsID"),
"can_contact": [ObjectID("HarrysID")]
}
The challenging part of using this method is keeping this collection up-to-date, which means that whenever one user follows the other, there is a chance that two documents will need to be modified (if that follow-up creates an interaction where two users follow each other). connect). Likewise, an unfollow action (eg, User B unfollows User A) will require you to remove User A's ID from the list within User B's can_contact
list , and vice versa.
Now I want to do it on a bigger scale. I need a function that returns a list of users that can be contacted.
To return a full list of users , not just user IDs, can be extended to maintain a list of objects representing users. However, this denormalization presents a similar maintainability challenge, because every time a user updates any profile information that you want to keep in this list, you have to run a query to determine which documents in the collection need to be updated. Actually, to make the query easier, it's better to keep the original list of user IDs as string values too, because then your query can be as simple as in this example .
In a JavaScript environment with mongoose, you can have a function that takes a userId and a user object as parameters, and performs the desired update, like this:
contactableSchema = {
id : ObjectId,
can_contact : [Object],
can_contact_user_ids : [String]
}
var Contactable = mongoose.model('Contactable', contactableSchema);
function updateContactableCollection(userId, updatedUser) {
Contactable.find({can_contact_user_ids: userId}, (err, contactables) => {
if (contactables) {
contactables.forEach(contactable => {
// now must update the right element within this contactable's "can_contact" list
})
}
})
}