How i can to create new model inside another model? - node.js

I have TweetModel and i need to create RatingModel inside, and RatingModel must have a same TweetModel id, because i want to link them for further deletion.
Maybe there are other ways to make rating for posts in mongoose.
I will be very grateful for your help
const tweet = await TweetModel.create({
text: req.body.text,
images: req.body.images,
user: user._id,
rating: ..., // => {retweets[], likes[]}
})
tweet.save()
TweetSchema
RatingSchema

Assuming you already know how to create Schema, you can create 2 different schemas for your tweet and rating. This will create 2 separate collections will inturn be more manageable.
const { text, images } = req.body
const objectId = new mongoose.Types.ObjectId() //create a objectId first.
await TweetModel.updateOne({ _id }, { text, images, user: user._id }, { upsert: true })
await RatingModel.updateOne(....)
We are using updateOne here with the option of {upsert: true} where we will create a new record if record _id don't exist.
You will be able to get the ratings easily with mongoose.populate()
More on { upsert: true } - https://docs.mongodb.com/manual/reference/method/db.collection.update/
Bonus
If you want to create the record in half the time.
const [ tweet, rating ] = await Promise.all([
TweetModel.updateOne({ _id }, { text, images, user: user._id }, { upsert: true })
RatingModel.updateOne(....)
])
Using Promise.all you can execute both update request at the same time.
Bonus 2
If you want to populate ratings inside tweet, add this in your Schema..
TweetModel.virtual('rating', {
ref: 'Rating',
localField: '_id',
foreignField: '_id',
});
Scroll to Populate - https://mongoosejs.com/docs/tutorials/virtuals.html

Related

how to loop over an array and get data for each element? - with mongoose & node.js

I am trying to build a social network. I have a collection of "connections"/"friend requests".
the schema is as follows:
{
senderId: { type: String },
receiverId: { type: String },
approved: { type: Boolean },
},
when the person didn't approve the connection yet, the connection is marked with "pending".
when the user enter to the website, i get all the pending connections people sent him, with this command:
const requestsPending = await Connection.find({
receiverId: userId,
approved: false,
});
the thing is, in the backend, I want to get the details of the people who send the friend requests (so I can show their name+picture).
I want to loop over the pending requests array, and get the extra data of the user who sent the request.
await User.findById(requestsPending[0][senderId]);
any idea how to do it for each element? what's the best approach?
or any idea how to do it more efficiently?
thank you.
this seems to work:
var requestsPendingWithUsersData = await Promise.all(
requestsPending.map(async (item) => {
const userData = await User.findById(item.senderId);
return {
item,
senderData: { picture: userData.picture, name: userData.username },
};
})
);
await User.find({
'senderId': { $in: [
101,102,103,...
]}
})
You can try something like the above, where you pass an array of senderID to $in. The $in clause is similar to like operator in SQL.
If the senderId is an ObjectId, pass them as ObjectID types
await User.find({
'senderId': { $in: [
mongoose.Types.ObjectId(<senderID>),
]}
})
If the mongo Document is heavier/larger, use lean() at the end of the query. Enabling the lean option tells Mongoose to skip instantiating a full Mongoose document.
await User.find({
'senderId': { $in: [
101,102,103,...
]}
}).lean()

How to properly use populate in mongoose

I'm trying to populate a reference field in NodeJS using mongoose for mongodb interaction.
I have the following code:
Housekeeping model
...
user: {
type: mongoose.Schema.Types.ObjectId
required: true
ref: "User"
}
...
User model
...
userSchema.virtual("housekeepings", {
ref: "Housekeeping",
localField: "_id"
foreignField: "user"
}
...
Housekeeping router
...
const housekeepings = await Housekeeping.find({day:day, house: house})
.populate("user")
.exec(function (err, housekeeping) {
if(err) return
console.log("Username is: " + housekeeping.user.name
)
})
...
This code results in cannot read property "name" of undefined (I do have documents in the database that should be loaded)
Because you're using Model.find() so your housekeeping will be an array. That's why housekeeping.user is undefined. If you want to find one document then use Model.findOne() instead. Also you don't need to use .exec() with await. Just:
const housekeepings = await Housekeeping.find({day:day, house: house}).populate("user");
console.log(housekeepings[0].user.name)
Or:
const housekeeping = await Housekeeping.findOne({day:day, house: house}).populate("user");
console.log(housekeeping.user.name)
So apparently the problem was that I changed the field name in "housekeeping model" from "owner" to "user" without updating the database and POST method.

Mongoose- Run a function to all Entries in database

I am a beginner in NodeJs and MongoDB. I have a user schema where I have a field which is an array that is filled by the user's input value. After users enter the value, the admin also passes an array of correct answers. I want to create a function which runs on all users array field and on correct answer store the score in users schema. Just wanted to know how do I run the function on all entries of the collection.
//Final result schema by the admin
const resultSchema = new mongoose.Schema({
matchday:Number,
homeTeam:String,
awayTeam:String,
utcDate:Date,
finalUpdateTime:Date,
result:Array
})
//The predicted answer Schema
const predictSchema = new mongoose.Schema({
user:{
type:mongoose.Schema.ObjectId,
ref:'User',
required:[true, 'Predicted Team must belong to a User']
},
teamData:Array,
matchday: Number,
score:{
type:Number,
default:0
},
createdAt: {
type:Date,
default:Date.now()
},
lastUpdated:Date,
},{
toJSON: {
virtuals: true,
},
toObject: {
virtuals: true,
},
})
You can define a static method for your schema. Statics are methods that can be invoked directly by a Model.
See here
You can pass array of correct answers to this method and check the answers for each user in your collection. You can retrieve all users using Find
I managed to solve the issue and it works but not sure if its the correct way to do it
exports.updateUserScore = async (req, res, next) => {
const user = await Predict.find({ matchday: req.body.matchday });
user.map(async (el) => {
let score = 0;
el.teamData.map((e) => {
if (req.body.teamData.includes(e)) score = score + 1;
});
console.log(score, el._id);
await Predict.findByIdAndUpdate(el._id, { score: score });
});
res.status(200).json({
status: 'success',
message: 'Updated User Score Successfully',
});
};

How do I prevent Schema fields from being inserted into subdocument?

I'm making a dating app in node js and vue, and everything works however I wish to exclude password from being inserted into subdocument upon creation of a conversation. Right now I know that i can say .select('-password') when using User.findOne() but it doesn't work, when adding the user schema as a subdoc to my Conversations schema, which has user_one and user_two, each referring to a User schema. I need the password field, so I can't ommit it when creating a schema. Right Now my code looks like this:
User.findOne({ _id: fromUserId }, (errUserOne, userOne) => {
User.findOne({ _id: toUserId }, (errUserTwo, userTwo) => {
conversation = new Conversation({
_id: mongoose.Types.ObjectId(),
user_one: userOne,
user_two: userTwo
});
conversation.save();
const message = new Message({
_id: mongoose.Types.ObjectId(),
conversation_id: conversation._id,
message: req.body.message,
user_id: fromUserId
});
message.save();
res.sendStatus(201);
});
});
However this code saves the password to the Conversation collection, which I don't want.
User.find({ _id: :{ $in : [fromUserId,toUserId] }, { password:0 } , (err, userArray) => {
//your code goes here
});
Two things, You are querying two time for getting users. You can merge it into single query and for excluding the password field you can pass {password:0}. Which will exclude it in the documents.
also while you define Conversation schema don't make user_one and user_two type of user. Instead define only whatever properties of user you want to save like:
var Conversation = new Schema({
_id : ObjectId,
user_one : {
_id: ObjectId,
//all other fields
},
user_two : {
_id: ObjectId,
//all other fields
},
});

Saving data to array in mongoose

Users are able to post items which other users can request. So, a user creates one item and many users can request it. So, I thought the best way would be to put an array of users into the product schema for who has requested it. And for now I just want to store that users ID and first name. Here is the schema:
const Schema = mongoose.Schema;
const productSchema = new Schema({
title: {
type: String,
required: true
},
category: {
type: String,
required: true
},
description: {
type: String,
required: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
requests: [
{
userId: {type: Object},
firstName: {type: String}
}
],
});
module.exports = mongoose.model('Product', productSchema);
In my controller I am first finding the item and then calling save().
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findById(productId).then(product => {
product.requests.push(data);
return product
.save()
.then(() => {
res.status(200).json({ message: "success" });
})
.catch(err => {
res.status(500).json({message: 'Something went wrong'});
});
});
};
Firstly, is it okay to do it like this? I found a few posts about this but they don't find and call save, they use findByIdAndUpdate() and $push. Is it 'wrong' to do it how I have done it? This is the second way I tried it and I get the same result in the database:
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findByIdAndUpdate(productId, {
$push: {requests: data}
})
.then(() => {
console.log('succes');
})
.catch(err => {
console.log(err);
})
};
And secondly, if you look at the screen shot is the data in the correct format and structure? I don't know why there is _id in there as well instead of just the user ID and first name.
Normally, Developers will save only the reference of other collection(users) in the collection(product). In addition, you had saved username also. Thats fine.
Both of your methods work. But, second method has been added in MongoDB exactly for your specific need. So, no harm in using second method.
There is nothing wrong doing it the way you have done it. using save after querying gives you the chance to validate some things in the data as well for one.
and you can add additional fields as well (if included in the Schema). for an example if your current json return doesn't have a field called last_name then you can add that and save the doc as well so that's a benefit..
When using findById() you don't actually have the power to make a change other than what you program it to do
One thing I noticed.. In your Schema, after you compile it using mongoose.modal()
export the compiled model so that you can use it everywhere it's required using import. like this..
const Product = module.exports = mongoose.model('Product', productSchema);

Resources