$in returning nothing when array is empty - mongodb

This is my backend code for filters :
db.collection("users")
.find({
$or: [
{ job: { $in: myFilters.jobs } },
{ role: myFilters.role }
]
})
it works really well, when
myFilters.jobs = ["manager","user"]
The problem is when myFilters.jobs is an empty array [""], it should return all documents.
Currently, this is returning nothing, while I need it to return all docs :
myFilters.jobs = [""]
How could I do that, please ?
I've tried out this with no luck :
db.collection("users")
.find({
$or: [
$cond: {
if: { $ne: [myFilters.jobs, ""] },
then: { job: myFilters.jobs },
else: { job: { $in: myFilters.jobs } }
},
{ role: myFilters.role }
]
})

So, I'm trying to explain the solution I've found :
1st . In a node ws, I'm receiving filters, and cleaning them with this function :
This function removes emptys objects or arrays(It is when a user doesn't pick any filter inside of the app).
This is the JSON filters I'm receiving , please notice that, for example, role is empty, because the user has not chosen any role filter.
{"filters":{"role":"","jobs":["database"]}}
It goes through this function, so role gets deleted :
clean(myFilters);
function clean(obj) {
for (var propName in obj) {
if (
obj[propName] === null ||
obj[propName] === undefined ||
obj[propName] === "" ||
!obj[propName].length
) {
delete obj[propName];
}
}
}
2nd ,
Then, I'm pushing the filters dynamically like this :
var find = {};
find.$and = [];
if (myFilters.role) {
find.$and.push({ role: myFilters.role });
}
if (myFilters.jobs) {
find.$and.push({ job: { $in: myFilters.jobs } });
}
Finally , this is the db.collection call :
db.collection("users")
.find(find)
.toArray(function(err, docs) {
res.send(docs);
});
SO , when a user doesn't pick a filter, the clean function is removing emptys selections ... Maybe there are better solutions ?

You can adjust your condition according to the values you get like this:
Using $where:
db.collection("users")
.find({ $where: function() {
return myFilters.jobs.includes(this.job) || this.role === myFilters.role
}})
JS only:
const cond = {};
if(myFilters.jobs.length === 0) {
cond.role = myFilters.role;
} else {
cond.$or = [
{ job: { $in: myFilters.jobs } },
{ role: myFilters.role }
];
}
db.collection("users")
.find(cond)
Update according to the OP's answer:
db.collection("users")
.find({ $where: function() {
let filter = true;
if(myFilters.jobs && myFilters.jobs.length) filter = myFilters.jobs.includes(this.members);
if(myFilters.role) filter = filter && this.name === myFilters.role;
return filter;
}})

Related

How to pass element index in array to mongoDB query?

Building cart on website and when product is added i want to first check if it is already in cart, if yes increment quantity by 1, if not add it. Cart is an array of objects and i want to pass index of object that contains added product to increment function but can't figure out how to do so.
async function add(product, userId) {
const user = await User.findById(userId);
const product = isProductInCart(product, user.cart); // returns true and index in cart if found
if (product.found === true) {
await User.findOneAndUpdate(
{ _id: userId },
{ $inc: { cart[product.index].quantity : 1 }} // not working
);
} else {
await User.findOneAndUpdate({ _id: userId }, { $push: { cart: product } });
}
}
function isProductInCart(product, cart) {
let productFound = { found: false, index: -1 };
for (let i = 0; i < cart.length; i++)
if (cart[i].name === product.name) {
productFound.found = true;
productFound.index = i;
break;
}
return productFound;
}
It looks like your code can be simplified if you consider using the $ positional operator:
let userWithCart = User.findOneAndUpdate(
{ _id: user, 'cart.name': product.name },
{ $inc: { 'cart.$.quantity' : 1 }}
)
if(!userWithCart ){
await User.findOneAndUpdate({ _id: userId }, { $push: { cart: product } });
}
First findOneAndUpdate will return no value when there's no corresponding cart.name (and you need to $push it). Otherwise MongoDB will automatically match the cart you want to update based on cart.name condition and increment the quantity subfield.
EDIT:
If you still need to proceed the way you've started you just need to evaluate the path in JavaScript:
{ $inc: { [`cart.${product.index}.quantity`] : 1 }}

Building MongoDB query with conditions

I need to build a MongoDB query by pushing a new language if it does not exist in the array already. But if it exists I get an error this '$push' is empty. It is correct.
My question is how to build the query adding $push only when it is necessary?
let pushNewLanguage = {};
if (!profile.languages || (profile.languages && !profile.languages.find(l => l === languageId))) {
pushNewLanguage = { languages: languageId };
}
const profileUpdate = await
Profiles.rawCollection().update(
{ userId: this.userId },
{
$inc: { countPublishedPoems: 1 },
$push: pushNewLanguage
}
);
Remove the conditional logic and use $addtoSet instead of $push.
$addToSet will only add the item if it doesn’t exist already.
const profileUpdate = await
Profiles.rawCollection().update(
{ userId: this.userId },
{
$inc: { countPublishedPoems: 1 },
$addToSet: { languages: languageId }
}
);
Since you are writing Javascript, you can create a "base" update object, and then add the $push property if you need:
const update = {
$inc: { countPublishedPoems: 1 }
}
if (!profile.languages || (profile.languages && !profile.languages.find(l => l === languageId))) {
update["$push"] = { languages: languageId };
}
const profileUpdate = await
Profiles.rawCollection().update(
{ userId: this.userId },
update
);

Using mongoose lean after saving

So I am trying to add a key to a returned post. But I can't seem to get lean() to work. How can I manipulate the returned post after save?
I was thinking maybe I need to add lean to my findById like this Post.findById(req.params.id).lean().then(). But that didn't work, plus that only makes the first initial post mutable. It will say
post.save is not a function
if I do it like Post.findById(req.params.id).lean().then() as well
I want to only return the object about to be sent back to the client, I do not want they key saved in the actual document.
Post.findById(req.params.id)
.then(post => {
if (
post.likes.filter(like => like.user.toString() === req.user.id)
.length === 0
) {
return res
.status(400)
.json({ notliked: "You have not yet liked this post" });
}
// Get remove index
const removeIndex = post.likes
.map(item => item.user.toString())
.indexOf(req.user.id);
// Splice out of array
post.likes.splice(removeIndex, 1);
// Save
post.save().then(post => {
post["liked"] = false; <-------
res.json(post);
});
})
edit
Post.findById(req.params.id)
.lean()
.then(post => {
if (
post.likes.filter(like => like.user.toString() === req.user.id)
.length === 0
) {
return res
.status(400)
.json({ notliked: "You have not yet liked this post" });
}
// Get remove index
const removeIndex = post.likes
.map(item => item.user.toString())
.indexOf(req.user.id);
// Splice out of array
post.likes.splice(removeIndex, 1);
post["liked"] = false;
res.json(post);
// Save
post.save();
})
gives error
post.save is not a function
You can simply do this by searching for the req.user.id inside the indexOf likes array
Post.findOne({ _id: req.params.id }).lean().then((post) => {
if (post.likes.indexOf(req.user.id) !== -1) {
post.isLiked = true
}
post.isLiked = false
res.json(post)
})
Far better with the aggregation
Post.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(req.user.id) }},
{ "$addFields": {
"isLiked": { "$in": [mongoose.Types.ObjectId(req.user.id), "$likes"] }
}}
])
EDIT :- If you want to update document then use update query
Post.findOneAndUpdate(
{ _id: req.params.id },
{ $pull: { likes: { user: req.user.id } }},
{ new: true }
).then((post) => {
res.json(post)
})
Post Schema for likes
...
likes: [
{
user: {
type: Schema.Types.ObjectId,
ref: "users"
}
}
]
...

Mongoose query using if else possible?

I have this Schema:
const guestSchema = new Schema({
id: String,
cart: [
{
product: {
type: mongoose.Schema.ObjectId,
ref: "products"
},
quantity: Number
}
]
});
I have this query:
Guest.findOneAndUpdate(
{ id: req.sessionID },
{
$cond: [
{ "cart.product": { $ne: req.body.itemID } },
{ $push: { "cart": { product: req.body.itemID, quantity: 1 } } },
{ $inc: { "cart.quantity": 1 } }
]
},
{ upsert: true, new: true }
).exec(function(err, docs) {
err ? console.log(err) : res.send(docs);
});
Basically, what I'm trying to do is update based on a condition. I tried using $cond, but found out that operator isn't used for querys like I'm doing.
Based on this:
{ $cond: [ <boolean-expression>, <true-case>, <false-case> ] }
I want something similar to the functionality of this operator for my query.
Let's break down my condition:
For my boolean expression: I want to check if req.body.itemID is $ne to any of the values in my cart
If true then: $push the itemID and quantity into the cart
Else (then item already exists): $inc the quantity by 1
Question: How would achieve this result? Do I need to make two seperate querys? I'm trying to avoid doing that if possible
I went through all their Update Field Operators, and there's probably no way to do this in the way I want.
I wonder why there is no $cond for update operators. Nonetheless, I have the solution to what I wanted the functionality accomplish. Just not in the elegant fashion that I would like it.
Guest.findOneAndUpdate(
{ id: req.sessionID },
{ id: req.sessionID }, //This is here in case need to upsert new guest
{ upsert: true, new: true }
).exec(function(err, docs) {
if (err) {
console.log(err);
} else {
//Find the index of the item in my cart
//Returns (-1) if not found
const item = doc.cart.findIndex(
item => item.product == req.body.itemID
);
if (item !== -1) {
//Item found, so increment quantity by 1
doc.cart[item].quantity += 1;
} else {
//Item not found, so push into cart array
doc.cart.push({ product: req.body.itemID, quantity: 1 });
}
doc.save();
}
});
This type of logic does not belong within the database query. It should happen in the application layer. MongoDB is also very fast at retrieving and updating single records with an index so that should not be a concern.
Please try doing something like this:
try {
const guest = await Guest.findOne().where({
id: req.sessionID
}).exec();
// your cond logic, and update the object
await guest.save();
res.status(200).json(guest);
} catch (error) {
handleError(res, error.message);
}

Mongoose loop through findOneAndUpdate condition statement

I want to know if this part of code can be written differently, only with Mongoose helper methods of models ? Can I return a success and error if no stock are greater then 0 ?
ProductSchema.statics.substractStock = function (products) {
_.map(products, updateStock)
function updateStock(o) {
mongoose.model('Product').findById(o._id, function (err, product) {
return product
}).then(function(productDB){
if(productDB.stock > o.stock && productDB.stock > 0){
mongoose.model('Product').findOneAndUpdate(o._id, {$inc: {stock: -(o.stock)}}, {},
function (err, doc) {
//return success ??
}
);
} else {
//return 'no update'
}
});
}
};
This could be done with an atomic update where you can ditch the initial findById() call and include the comparison logic
if (productDB.stock > o.stock && productDB.stock > 0) { ... }
within the query as in the following:
function updateStock(o) {
mongoose.model('Product').findOneAndUpdate(
{
"_id": o._id,
"$and": [
{ "stock": { "$gt": o.stock } } ,
{ "stock": { "$gt": 0 } }
]
},
{ "$inc": { "stock": -(o.stock) } },
{ "new": true }, // <-- returns modified document
function (err, doc) {
// check whether there was an update
}
);
}