Mongoose Followers/Friends Relationship Query - javascript

I am trying to make a query that returns posts from both Friends and Non-Friends, but ensures Friends Posts are at top of list. What I have now only gets posts from Friends:
Schemas
var Post = mongoose.Schema({
name: String,
user: { type:ObjectId, ref:'User' },
createdAt: { type:Date, default:Date.now }
});
var User = mongoose.Schema({
username: String
});
var Relationship = mongoose.Schema({
from: { type:ObjectId, ref:'User' },
to: { type:ObjectId, ref:'User' }
});
Query looks like
Relationship.find({ from : thisUser },function(err,docs){
if (err) {console.log(err);}
var query = Post.find();
var plucked = _.pluck(docs,'to');
query.where('user').in( plucked );
query.sort('-createdAt');
query.limit(20);
query.skip( 20 * page);
query.exec(function(err,posts){
if (err) {console.log(err);}
res.send(posts);
});
});
The client will grab say 20 posts on each page. So any suggestions on how I can return all posts, but ensure posts by Friends appear first? For instance, if there are 100 posts that meet the query criteria and 30 of those are from friends, the first page and half of the second will all be friends posts (sorted by createdAt).
If I need to redo the schemas and relationships thats fine as well.

What I would have done is to skip the Relationship model all together (if it is not absolutely essential) and have a friends field in User like this instead:
var User = mongoose.Schema({
username: String,
friends: [User],
});
Then you're able to query the friends posts like this:
Post.find({user: {$in: thisUser.friends}}, function(err, posts) {
...
})
And append it with non-friends using $nin instead of $in.

Honestly, the best thing to do is to store the user names with the friendship object. This information is extremely unlikely to change. How many times have you changed your name on Facebook?

Related

Friend Request System - Express, MongoDB, EJS

I want to create a social network thus allowing users to send and interact with frind requests. As of now I have created the register, log-in and "search for other users function".
When I find and select another user, I display their user-info and have created a "Add friend" button.
Can anyone help me in a direction of the creation of the "Add friend" option? I have looked around for some time now, and not been able to find the correct solution. Below I have attached my UserSchema and route for finding users:
//User Schema
const UserSchema = new mongoose.Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
password: {
type: String,
required: true
},
},{ collection: 'Users' });
//Get single user based on ID
router.get('/user/get:id', ensureAuthenticated, function (req, res) {
MongoClient.connect(DBUri,{useUnifiedTopology: true }, function (err, db) {
let dbo = db.db(DBName);
const query = {_id: objectId(req.params.id)}
dbo.collection("Users").find(query).toArray(function(err, resultTasks) {
if (err) throw err;
res.render('../View/findFriend', {
resultTasks: resultTasks
});
db.close();
});
});
});
You can add something like this in your user schema:
friends: [{ type : ObjectId, ref: 'User' }],
OR
friends: userSchema
Take the one which suits you.
What that will do is add an array to the user, Then you can store IDs of friends.(Who are other users, hence the ref: 'User')
Then, When you have to fetch users you can do:
User.find(<ID or whatever you have to find Users>).populate('friends')
Also, To push a new friend simply use: user.friends.push(newFriend._id)

Mongoose auto fill data by searching in reference

const UserSchema = new Schema(
{
referrals: {
ref: 'User',
type: [mongoose.Schema.Types.ObjectId],
},
referredBy: {
ref: 'User',
type: mongoose.Schema.Types.ObjectId,
},
}
);
I want Mongoose to find users who have current user _id in referredBy reference.
In other words, eg: find all users who have '_IDOfSpecificUser' in their referredBy field and put all the found users in the array of referrals where user's _id is '_IDOfSpecificUser'.
How can I handle that in mongoose?
Simplest is using find
User.
find({ "referredBy" : "xxxxxxxxxxxx" }).
exec(function (err, users) {
if (err) return handleError(err);
console.log('The users are an array: ', users);
});
Refer to https://mongoosejs.com/docs/populate.html
If you want to convert bellow function to static method inside UserSchema, please refer to this https://mongoosejs.com/docs/api.html#schema_Schema-static and https://mongoosejs.com/docs/2.7.x/docs/methods-statics.html

Trying to understand the use of the populate method

I see that one way we use populate is to put one document from another collection into a "parent" collection. I was just going through this question and I was hoping someone could explain the answer to me better. And show me a practical use. Here is an example from the answer.
var PersonSchema = new mongoose.Schema({
t: String
}, {collection: 'persons'});
var User = mongoose.model('User', PersonSchema.extend({
_id: String,
name: String
}));
var ParentSchema = new mongoose.Schema({
s: String
}, {collection: 'parent'});
var Like = mongoose.model('Like', ParentSchema.extend({
_id: String,
user_id: {
type: String,
ref: 'User'
}
}));
Insert Data into DB,
var user = new User({
t: 't1',
_id: '1234567',
name: 'test'
});
var like = new Like({
s: 's1',
_id: '23456789',
});
user.save(function(err, u){
if(err)
console.log(err);
else {
like.user_id = u._id;
console.log(like);
like.save(function(err) {
if (err)
console.log(err);
else
console.log('save like and user....');
});
}
});
Query by
Like.findOne({}).populate('user_id').exec(function(err, doc) {
if (err)
console.log(err);
else
console.log(doc);
});
And the result is
{ _id: '23456789',
__t: 'Like',
user_id: { _id: '1234567', __t: 'User', t: 't1', name: 'test', __v: 0 },
s: 's1',
__v: 0 }
QUESTION
where does __t: 'User' come from?
I was thinking that using populate() or ref that would separate the collections but it looks like at the end the like collection has the users document in it. I think I wanted to use populate so I could make a document smaller.
3.Also if someone really wanted to help explain this to me I have an example that I have been trying to do and I don't know if I should use populate but if I should it would be great if you show me how. Here is the example.
You have
doctors
patients
information about the practice
There could be like a 1000 doctors and lots of patients for each doctor. and the information will be about their practice(like how many employees they have). so I feel that there should be a separation of concern.(one reason is to prevent a single document for a patient from getting to big). So If we're going with the populate method If you could explain how to set it up for this case. I guess I could have a doctor as a parent and a child refs for patients and another child refs for information about practice. so maybe there should be an array of objectId for the patients and an array for Other information
Q1: where does __t: 'User' come from?
Refer to this link.
mongoose now includes schema inheritance and discriminatorKey functionality that breaks mongoose-schema-extend. mongoose now sets the discriminatorKey schema option to __t by default
Q2: I was thinking that using populate() or ref that would separate the collections but it looks like at the end the like collection has the users document in it. I think I wanted to use populate so I could make a document smaller.
It seems you misunderstand the meaning of Population. There are no joins in MongoDB but sometimes we still want references to documents in other collections. This is where population comes in. Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). So populate is not used to make document smaller.
Q3: Doctor, Patient, Practice
Schema could be as following:
var DoctorSchema = new Schema ({
name: String,
// ... other field
});
var PatientSchema = new Schema ({
name: String,
doctor: {type: Schema.ObjectId,
ref: 'Doctor'}
});
var PracticeSchema = new Schema ({
ff: String,
patientId: {type: Schema.ObjectId,
ref: 'Patient'},
doctorId: {type: Schema.ObjectId,
ref: 'Doctor'}
});
As for schema, it is hard to determine which schema is better or not, (with populate or without it). The first thing we should consider is to meet our query requirement, to make the query easy. The design of mongoDB to make the query more efficiently. So our schema should meet it.

Simple Mongodb embedded database structure

i want to build a Mongodb database (Mongoose/Node.js) structure but i face a problem right now. i have two entities. Users and Books and i want to use embedded system(because of lack of joins in mongodb). And my problem is that which of this entities shpuld be an inner value to other.
For example i wiil face this two type of query in my app:
1- Books of an specific user
2- Users of an specific book
Now, Books should be a inner value for Users or contrariwise?
i can do this two:
Users schema:
var schema = new mongoose.Schema({
use_name: String,
user_family: String,
user_books: { type: Schema.Types.ObjectId, ref: 'books' }
});
Or this:
Books Schema:
var schema = new mongoose.Schema({
book_name: String,
book_lang: String,
book_user: { type: Schema.Types.ObjectId, ref: 'users' }
});
which is better? which is standard approach?
if i use both of them, when saving i have to do two save operation. if i has a large database with a lots of collections its gets worse that this...
after a lot of research i find out i have to use embedded system rather that using relation like collections to connect entities to each other, because Mongodb doesn't support joins and has poor support of things like this. embedded system is the correct way for a NoSql database like Mongodb?
Firstly, there's a minor correction that your user_books needs to be an array [].
Secondly, you should only reference one schema into another, otherwise you'll have to add unnecessary complexity in keeping them in sync.
So here's what your schemas should look like:
var UserSchema = new mongoose.Schema({
use_name: String,
user_family: String,
user_books: [{ type: Schema.Types.ObjectId, ref: 'books' }]
});
var BookSchema = new mongoose.Schema({
book_name: String,
book_lang: String,
});
Now "to fetch users that reads an specific book", you'll query like this:
UserSchema.find({ user_books: book._id })
which will give you all users that have BOOK_ID as (at least) one of their books.
If that's all, I guess you don't need population at all then.
Updated on the issue with $elemMatch query not working:
So as it turned out, we don't actually need $elemMatch with referenced docs array, since it's a simple array of _ids.
user // =>
{
_id: 56351c611ca0d2e81274100a
name: ...
books: [56351c611ca0d2e81274100b, 56351c611ca0d2e81274100c, ...]
}
$elemMatch works with array of objects, and would've been in the case of embedded doc:
var BookSchema = new mongoose.Schema({
book_name: String,
book_lang: String,
});
var UserSchema = new mongoose.Schema({
use_name: String,
user_family: String,
user_books: [BookSchema]
});
Since in this case the document would be like this:
user // =>
{
_id: 56351c611ca0d2e81274100a
name: ...
books: [
{ // each book here has an _id:
_id: 56351c611ca0d2e81274100b,
// this is what `$elemMatch: {_id:` would match for
name: ...
// you could do `$elemMatch: {name:`
lang: ...
// or `$elemMatch: {lang:`
}, {
_id: 56351c611ca0d2e81274100c,
name: ...
lang: ...
}, ...
]
}
This is where $elemMatch query would be needed.
UserSchema.find({ user_books: {$elemMatch: {_id: book._id } } })

Mongoose - Upserting Documents with Nested Models

I have a basic document with a 'checked_in' flag in my express app:
module.exports = Book= mongoose.model('Book', new Schema({
name : String,
checked_in : Boolean
},{ collection : 'Book' }));
I wanted to keep a log of when books are checked in and out so I came up with another schema:
var action = new Schema({
checked_in: Boolean,
});
module.exports = Activity = mongoose.model('Activity', new Schema({
book_id: String,
actions: [action]
},{ collection : 'Activity' }));
The 'book_id' should be the document id of a book and when I update a book I need to either create or update the activity log for that book with a new item inside of actions:
exports.update = function(req, res){
return Book.findById(req.params.id, function(err, book) {
var activity = new Activity({book_id: book.id});
activity.actions.push({
checked_in: req.body.checked_in,
});
Activity.update({ book_id: book.id}, activity.toObject(), { upsert: true }));
book.checked_in = req.body.checked_in;
return device.save(function(err) {
return res.send(book);
});
});
};
The problem I am having is that nothing gets inserted into the Activity collection. If I use .save() then i just get lots of duplicates in the collection.
UPDATE
I've started re-working things with the advice given below but am still not having any luck with this. Here's what I have now:
module.exports = Activity = mongoose.model('Activity', new Schema({
book_id: Schema.ObjectId,
actions: [new Schema({
checked_in: Boolean,
last_user: String
})]
},{ collection : 'Activity' }));
Here's the update code now:
exports.update = function(req, res){
// TODO: Check for undefined.
return book.findById(req.params.id, function(err, book) {
if(!err) {
// Update the book.
book.checked_in = req.body.checked_in;
book.last_user = req.body.last_user;
book.save();
// If there's no associated activity for the book, create one.
// Otherwise update and push new activity to the actions array.
Activity.findById(book._id, function (err, activity) {
activity.actions.push({
checked_in: req.body.checked_in,
last_user: req.body.last_user
})
activity.save();
});
}
});
};
What I want to end up with is a document for each book with an array of check outs/ins that gets updated each time someone checks a book in or out. i.e:
{
book_id: "5058c5ddeeb0a3aa253cf9d4",
actions: [
{ checked_in: true, last_user: 'ralph' },
{ checked_in: true, last_user: 'gonzo' },
{ checked_in: true, last_user: 'animal' }
]
}
Eventually I will have a time stamp within each entry.
There are a couple problems:
You're trying to find the book's activity doc using findById using the book's id instead of the activity's id.
You're not handling the case where the book's activity doc doesn't exist yet.
Try this instead:
Activity.findOne({book_id: book._id}, function (err, activity) {
if (!activity) {
// No Activity doc for the book yet, create one.
activity = new Activity({book_id: book._id});
}
activity.actions.push({
checked_in: req.body.checked_in,
last_user: req.body.last_user
});
activity.save();
});
I see a few things that can be improved...
The book_id field in the Activity model should be Schema.ObjectId instead of a String. You will then be able to use populate if you wish.
You aren't doing any error checking in exports.update. If the user passes in an invalid id, you will want to check if book is undefined or not, as well as the common if (err) return next(err) (this requires your function params to be res, res, next).
When you create the activity in exports.update, you want to use book._id instead of book.id
All the return statements are not needed
The device variable is not declared anywhere, I'm not sure what you are trying to save... I think you meant book there.
You can then just .save() the activity instead of doing the Activity.update.

Categories