I have this model for a user, the user can also be a driver and the driver have an array of cars. I want to delete a specific car from all the cars of drivers, but my query deletes all the cars.
This is the schema:
const UserSchema = new mongoose.Schema({
name: {
type: String,
},
driverData: {
ExperienceLevel: {
type: String,
},
rides: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "ride.model",
},
],
cars: [
{
id: {
type: mongoose.Schema.Types.ObjectId,
},
licensePlateNumber: {
type: String,
},
},
],
},
});
Code I am using to pull a car by id.
user = await User.findByIdAndUpdate(
{ _id: userId },
{ driverData: { cars: { $pull: { id: carId } } } }
);
Only a single car object and a single car id is left after making this query, all other data is removed. How to only remove only a single car.
Your syntax is incorrect. Try this:
user = await User.findByIdAndUpdate( { _id: userId }, { $pull: { driverData: { cars: { $elemMatch: { id: carId } } } } );
Related
I have this opportunity model that has this field likes, which is an array of users. How do I check first if the user exist already in that array and if they do I pull them and if they don't I push them back I am building a like creteria for posts
Here is my opportunity model
const mongoose = require("mongoose");
const OpportunityModel = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
content: {
type: String,
required: true,
trim: true,
},
likes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
],
image: [
{
type: Object,
},
],
share_with_thoughts: {
type: mongoose.Schema.Types.ObjectId,
ref: "Shares",
},
comments: {
type: mongoose.Schema.Types.ObjectId,
ref: "Comment",
},
is_opportunity_applied: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
],
});
const Opportunity = mongoose.model("Opportunity", OpportunityModel);
module.exports = Opportunity;
What I tried doing but did not work
const likeOrUnlikeOpportunity = expressAsyncHandler(async (req, res) => {
let userId = req.user._id;
let opportunityId = req.params.opportunityId;
let isUserExist = await Opportunity.find({
$and: [{ _id: opportunityId }, { likes: { $elemMatch: { $eq: userId } } }],
}).populate("user", "user_id user_name");
if (isUserExist.length > 0) {
const unliked = await Opportunity.findByIdAndUpdate(
opportunityId,
{
$pull: { users: userId },
},
{ new: true }
).populate("user", "user_id user_name");
if (!unliked) {
res.status(500).send({ ErrMessaage: "an error occured" });
} else {
res.status(200).json(unliked);
}
} else {
const added = await Opportunity.findByIdAndUpdate(
opportunityId,
{
$push: { users: userId },
},
{ new: true }
).populate("user", "user_id user_name");
if (!added) {
res.status(500).send({ ErrMessaage: "an error occured" });
} else {
res.status(200).json(added);
}
}
});
In this case, the action should be dynamic [like | unlike] function, You don't have to chain the function since the like and unlike function/api can't be called at the same time.
Make Your Query Conditional;
Just make sure you have a way to identify between like and unlike.
const {like, postId} = req.body;
let query = {[`${'$' + (like ? 'push' : 'pull')}`]: {likes: userId}};
//assumes that you have the post id
// you can decide not to wait for it to update, just to be sure it did update
await post.findByIdAndUpdate(postId, query).exec()
I hope this helps.
I have a collection in mongodb, called recipe, where I have a document, called comments, which is an array, and in each recipe is saving the comments. Inside the comments array I have a ratings, which type is Number. So I want to calculate the average the ratings, but don't know, how can I use the db.collection().aggregate code to work in the recipe collection, in the comments document with the ratings variable.
Here is the recipe collection in mongodb:
const { Double } = require('bson');
const { timeStamp } = require('console');
const mongoose = require('mongoose');
const recipeSchema = new mongoose.Schema({
name: {
type: String,
required: 'This field is required.'
},
description: {
type: String,
required: 'This field is required.'
},
quantity: {
type: Array,
required: 'This field is required.'
},
ingredients: {
type: Array,
required: 'This field is required.'
},
categoryByServing: {
type: String,
enum: ['Reggeli', 'Ebéd', 'Vacsora', 'Desszert', 'Levesek', 'Egyéb'],
required: 'This field is required.'
},
categoryByNationality: {
type: String,
enum: ['Thai', 'Kínai', 'Indiai', 'Olasz', 'Angol', 'Magyar', 'Egyéb'],
required: 'This field is required.'
},
image: {
type: Array,
required: 'This field is required.'
},
comments: [
{
username: String,
comment: String,
date: {
type: Date,
default: Date.now
},
rating: Number
},{
timestamps: true
}
],
count: {
type: Number
},
likes: {
type: Number
},
recipe_id: {
type: String
}
});
recipeSchema.index({ name: 'text', description: 'text' });
const Recipe = module.exports = mongoose.model('Recipe', recipeSchema);
Here is the code, where I implemented the rating avg calculation, which is inside the commenting post method:
/**
* POST /comment-recipe
* Comment Recipe
*/
module.exports.CommentRecipeOnPost = async(req, res) => {
let recipeId = req.params.id
const comment = new Comment({
username: req.body.username,
comment: req.body.comment,
date: req.body.date,
rating: req.body.rating
});
comment.save((err, result) => {
if (err){
console.log(err)
}else {
Recipe.findById(req.params.id, (err, post) =>{
if(err){
console.log(err);
}else{
post.comments.push(result);
post.save();
db.collection('recipes').aggregate([
{
$unwind: "$comments"
},
{
$group: {
_id: null,
avgrating: {
$avg: "$rating"
}
}
}
]).toArray()
.then(results => {
console.log({ rating: results[0].avgrating })
})
.catch(error => console.error(error))
console.log('====comments=====')
console.log(post.comments);
res.redirect('/recipe/' + recipeId);
}
})
}
})
}
UPDATE
There is a simpler way pointed out by chridam in the comments using only a $project which I didn't figure out at first demo
db.collection.aggregate([
{
$project: {
_id: 0,
name: 1,
avgRating: {
$avg: "$comments.rating"
}
}
}
])
..or using $addFields which will give the average of ratings as a new field avgRating for each record demo . you can use a project step after if need to get only certain fields
db.collection.aggregate([
{
$addFields: {
avgRating: {
$avg: "$comments.rating"
}
}
}
])
You have done the $unwind step correctly and now you will get a record for each comment.
{
"_id": "1",
"comments": {
"comment": "commment1-1",
"rating": 4
},
"name": "recipe 1"
},
{
"_id": "1",
"comments": {
"comment": "comment1-2",
"rating": 3
},
"name": "recipe 1"
},
...
In the $group stage group by something unique like the _id or the name and the $avg should be of $comments.rating instead of $rating.
In the end the pipeline should look something like this. demo
db.collection.aggregate([
{
$unwind: "$comments"
},
{
$group: {
_id: "$name", //group by something unique for that document containing comments
avgRating: {
$avg: "$comments.rating"
}
}
}
])
I cant update a value in a array object with an ObjectId.
I think it fails when comparing the id of the event Id of the User model that is an ObjectId with the id per parameter that is a string
Model:
tareas: [{
eventId: {
type: [Schema.Types.OjectId],
ref: 'Event'
},
tareasDelUsuario: {
type: []
}
}],
I tried in controller:
await User.findOneAndUpdate(
{
_id: id,
'tareas.eventId': eventId
},
{
$push: {
'tareas.$.tareasDelUsuario': tarea
}
});
and:
await User.findOneAndUpdate(
{ _id: id },
{
"$set": { ['tareas.$[el].tareasDelUsuario']: tarea }
},
{
"arrayFilters": [{ 'el.eventId': eventId}]
});
I'm trying to update array of user information objects in mongoose.
I've stored the user core information in the login process, I want to update some of user information when user tries to make an order.
Here is the code for the model
const mongoose = require('mongoose');
const { ObjectId } = mongoose.Schema;
const userSchema = new mongoose.Schema(
{
name: String,
email: {
type: String,
required: true,
index: true,
},
role: {
type: String,
default: 'subscriber',
},
info: [
{ country: String },
{ city: String },
{ address: String },
{ phone: String },
{ birthdate: Date },
{ gender: { type: String, enum: ['Male', 'Female'] } },
],
// wishlist: [{ type: ObjectId, ref: "Product" }],
},
{ timestamps: true }
);
module.exports = mongoose.model('User', userSchema);
In my controller I'm getting the data from front-end react app as JSON format, I want to push some data to info which is an array of objects in the users model above.
exports.createOrder = async (req, res) => {
// Here I constract the data
const { plan, service, fullName, country, city, address } = req.body.order;
const { user_id } = req.body;
// This the method I tried
try {
const user = await User.updateOne(
{
_id: user_id,
},
{
$set: {
'info.$.country': country,
'info.$.city': city,
'info.$.address': address,
},
},
{ new: true }
);
if (user) {
console.log('USER UPDATED', user);
res.json(user);
} else {
res.json((err) => {
console.log(err);
});
}
const newOrder = await new Order({
orderPlan: plan,
orderService: service,
orderUser: user_id,
}).save();
console.log(newOrder);
console.log(req.body);
} catch (error) {
console.log(error);
}
};
I tired other solutions like
const user = await User.updateOne(
{
_id: user_id,
info: { $elemMatch: { country, city, address } },
},
{ new: true }
);
So do I need to reformat my model or there is a way to update this array of objects?
Option 1
Use $[]
db.collection.update(
{},
{ $set: { "info.$[i].country": "a1" }} ,
{ arrayFilters: [ { "i.country": "a" } ] }
)
Demo - https://mongoplayground.net/p/UMxdpyiKpa9
Option 2
if you know the index
Demo - https://mongoplayground.net/p/41S7qs6cYPT
db.collection.update({},
{
$set: {
"info.0.country": "a1",
"info.1.city": "b1",
"info.2.address": "c1",
"info.3.phone": "d1"
}
})
Suggestions -
Change info schema to object instead of an array
I wand to create document inside the user by using his object ID.
This is my schema
const productsSchema = mongoose.Schema({
product_name: {
type: String,
},
product_price: {
type: Number,
},
});
const sellersSchema = mongoose.Schema({
user_firstname: {
type: String
},
user_lastname: {
type: String
},
user_mobile: {
type: Number
},
products: [productsSchema]
});
And this is my code
module.exports.uploadProducts = function(productDetails, callback) {
var id = productDetails.userId;
db.findOneAndUpdate({ "_id": ObjectId(id) }, { $set: { products:
productDetails } }, { new: true }, callback)
}
But in this case products are updating in the particular user but I want to insert document inside that user based on ID.
Use $push:
module.exports.uploadProducts = function(productDetails, callback) {
var id = productDetails.userId;
db.update({ "_id": ObjectId(id) }, { $push: { products:
productDetails } }, callback)
}
Hope this helps you.