find object by id which is in array mongoose - node.js

My chat object has got an array with two elements - users id. I have first id from url params, but second id I have in array of users. Now I want to get all chats where first id is this one from url, and second is in the array. I think that example how I tried to do it will be the best explanation of this problem :
Chat.find({
users: { $all: [req.params.id, { $in: usersIdArray }] }
})
where usersIdArray is for example:
[ 5f8777a01d8c122e74cb6f08, 5f8777721d8c122e74cb6f02 ]
they are numbers, not strings. I don't know if it is important...
The error I get now :
(node:12168) UnhandledPromiseRejectionWarning: CastError: Cast to ObjectId failed for value "{ '$in': [ 5f8777a01d8c122e74cb6f08, 5f8777721d8c122e74cb6f02 ] }" at path "users" for model "Chat"
And my chat schema:
// Create schema
const ChatSchema = new Schema({
users: {
type: [{
type: Schema.Types.ObjectId,
ref: 'Users',
}, {
type: Schema.Types.ObjectId,
ref: 'Users',
}],
required: [true, 'Podaj uczestników czatu.'],
},
lastMessage: {
type: Schema.Types.ObjectId,
ref: 'Message'
}
}, { timestamps: opts });

Since the length of your array is fixed (2), you can just query based on array position:
Chat.find({
"users.0": req.params.id,
"users.1": {$in: usersIdArray}
});
If that doesn't work then probably because usersIdArray are actually not ObjectIds, in which case you'd need to map them:
usersIdArray.map(x => ObjectId(x))

#Christian Fritz, I had to add $or to your solution and everything is fine now:
Chat.find({
$or: [
{
"users.1": req.params.id,
"users.0": { $in: usersIdArray }
}, {
"users.0": req.params.id,
"users.1": { $in: usersIdArray }
}]
})

Related

Mongoose remove subdocument

I am struggling to get subdocument removed from the parent.
I am using Mongoose findOneAndUpdate.
unitRouter.delete('/:id/contracts/:cid', async (req, res) => {
Unit.findOneAndUpdate(
{ id: req.params.id },
{$pull: {contracts: { id: req.params.cid }}},
function(err, data){
console.log(err, data);
});
res.redirect(`/units/${req.params.id}`);
});
Schema is as follows:
const unitSchema = new mongoose.Schema({
address: {
type: String,
required: true
}
contracts: [{type: mongoose.Schema.Types.ObjectId, ref: 'Contract'}]
});
And it doesn't remove it from the list, neither from the contract collection.
I have checked similar topics, but didn't got it to work. What am I missing?
First of all, your schema does not match with your query.
Your schema doesn't have any id. Do you mean _id created by default?
contracts field is an array of ObjectId, not an object like { id: XXX }
So, starting from the schema you can have a collection similar to this:
[
{
"contracts": [
"5a934e000102030405000000",
"5a934e000102030405000001",
"5a934e000102030405000002"
],
"_id": "613bd938774f3b0fa8f9c1ce",
"address": "1"
},
{
"contracts": [
"5a934e000102030405000000",
"5a934e000102030405000001",
"5a934e000102030405000002"
],
"_id": "613bd938774f3b0fa8f9c1cf",
"address": "2"
}
]
With this collection (which match with your schema) you need the following query:
Unit.updateOne({
"_id": req.params.id
},
{
"$pull": {
"contracts": req.params.cid
}
})
Example here.
Also, the inverse way, your query is ok but your schema doesn't. Then you need a schema similar to this:
new mongoose.Schema(
{
id:{
type: mongoose.Schema.Types.ObjectId,
required: true
},
address: {
type: String,
required: true
},
contracts: [{
id:{
type: mongoose.Schema.Types.ObjectId,
ref: 'Contract'
}
}]
});
Example here
By the way, take care to not confuse between id and _id. By default is created the field _id.

Is there any way to use Model.find() Querie with populated field filter in node.js

I'm using Model.find() Querie like this
const model = require("../../models/product");
module.exports = async(filters) => {
let data = Object.values(filters.data)[0]
let field = Object.keys(filters.data)[0];
const regex = new RegExp(escapeRegex(data), 'gi');
return await model.find({ $or: [{
[field]: regex }] }).populate("division_id").populate("type_id").populate("category_id").exec()
}
//$or:[ {'_id':objId}, {'name':param}, {'nickname':param} ]
function escapeRegex(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
};
Filter
In filter i'm sending { data : { name : "a" } }
which is giving all products with name started with "a" which is Ok !
but now i want to get filter result which have specific division
response incudes only that specific division product
Product Schemas
_id: mongoose.Schema.Types.ObjectId,
division_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Division', required: [true, "Product Division Id is Required"] },
name: { type: String, required: [true, "Product Name is Required"] },
Division Schemas
_id: mongoose.Schema.Types.ObjectId,
name:{ type: String, required: "Division Name is Required" },
Try using the example in here: https://mongoosejs.com/docs/populate.html#query-conditions
So I assume it should be something like this:
return await model.find({
$or: [
{ [field]: regex } // By the way, if it's the only query in $or, you don't need $or
],
}
.populate({
path: "division_id",
match: { _id: <ObjectId> }
})
.populate("type_id")
.populate("category_id")
.exec();
First .populate will be extended a bit to include specific match. In there, you can pass _id (as an example, or name, based on your schema) to match specific division.

How to query for sub-document in an array with Mongoose

I have a Schema of Project that looks like this:
const ProjectSchema = new mongoose.Schema({
name: {
type: String,
Required: true,
trim: true
},
description: {
type: String,
},
devices: [{
name: {type: String, Required: true},
number: {type: String, trim: true},
deck: {type: String},
room: {type: String},
frame: {type: String}
}],
cables: {
type: Array
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
adminsID: {
type: Array
},
createdAt: {
type: Date,
default: Date.now
}
I want to query an object from array of "devices".
I was able to add, delete and display all sub-documents from this array but I found it really difficult to get single object that matches _id criteria in the array.
The closest I got is this (I'm requesting: '/:id/:deviceID/edit' where ":id" is Project ObjectId.
let device = await Project.find("devices._id": req.params.deviceID).lean()
console.log(device)
which provides me with below info:
[
{
_id: 6009cfb3728ec23034187d3b,
cables: [],
adminsID: [],
name: 'Test project',
description: 'Test project description',
user: 5fff69af08fc5e47a0ce7944,
devices: [ [Object], [Object] ],
createdAt: 2021-01-21T19:02:11.352Z,
__v: 0
}
]
I know this might be really trivial problem, but I have tested for different solutions and nothing seemed to work with me. Thanks for understanding
This is how you can filter only single object from the devices array:
Project.find({"devices._id":req.params.deviceID },{ name:1, devices: { $elemMatch:{ _id:req.params.deviceID } }})
You can use $elemMatch into projection or query stage into find, whatever you want it works:
db.collection.find({
"id": 1,
"devices": { "$elemMatch": { "id": 1 } }
},{
"devices.$": 1
})
or
db.collection.find({
"id": 1
},
{
"devices": { "$elemMatch": { "id": 1 } }
})
Examples here and here
Using mongoose is the same query.
yourModel.findOne({
"id": req.params.id
},
{
"devices": { "$elemMatch": { "id": req.params.deviceID } }
}).then(result => {
console.log("result = ",result.name)
}).catch(e => {
// error
})
You'll need to use aggregate if you wish to get the device alone. This will return an array
Project.aggregate([
{ "$unwind": "$devices" },
{ "$match": { "devices._id": req.params.deviceID } },
{
"$project": {
name: "$devices.name",
// Other fields
}
}
])
You either await this or use .then() at the end.
Or you could use findOne() which will give you the Project + devices with only a single element
Or find, which will give you an array of object with the _id of the project and a single element in devices
Project.findOne({"devices._id": req.params.deviceID}, 'devices.$'})
.then(project => {
console.log(project.devices[0])
})
For now I worked it around with:
let project = await Project.findById(req.params.id).lean()
let device = project.devices.find( _id => req.params.deviceID)
It provides me with what I wanted but I as you can see I request whole project. Hopefuly it won't give me any long lasting troubles in the future.

Remove array entries containing an empty array

I am trying to remove all products from the products array where the product has no rates. In my queries below I tried to check the length of the rates array, but
none of them seem to work. Any help is appreciated.
Thanks in advance
var ProductRateSchema = new Schema({
product: {
type: Schema.ObjectId,
ref: 'products'
},
user: {
type: Schema.ObjectId,
ref: 'User'
},
rates: [{
type: Schema.ObjectId,
ref: 'rates'
}]
});
var InventorySchema = new Schema({
name: {
type: String,
default: '',
required: 'Please enter in a name',
trim: true
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
},
products: [productRateSchema]
});
var inventoryId = req.body.inventoryId;
var productId = req.body.productId;
// none of these queries work
db.inventory.findOneAndUpdate({ '_id': inventoryId,
{$pull: {'products': { product: productId, 'rates': { $eq:[] }}}}, function (err, result) {
});
db.inventory.findOneAndUpdate({ '_id': inventoryId,
{$pull: {'products': { product: productId, 'rates': {$size: {$lt: 1}}}}}, function (err, result) {
});
db.inventory.findOneAndUpdate({ '_id': inventoryId,
{$pull: {'products': { product: productId, 'rates': null }}}, function (err, result) {
});
Don't know what you tried since it is simply not included in your question, but the best way to check for an empty array is to basically look where the 0 index does not match $exists:
Inventory.update(
{ "products.rates.0": { "$exists": false } },
{
"$pull": {
"products": { "rates.0": { "$exists": false } }
}
},
{ "multi": true },
function(err,numAffected) {
}
)
The "query" portion of the .update() statement is making sure that we only even attempt to touch documents which have an empty array in "products.rates". That isn't required, but it does avoid testing the following "update" statement condition on documents where that condition is not true for any array element, and thus makes things a bit faster.
The actual "update" portion applies $pull on the "products" array to remove any of those items where the "inner" "rates" is an empty array. So the "path" within the $pull is actually looking inside the "products" content anyway, so it is relative to that and not to the whole document.
Naturally $pull will remove all elements that match in a single operation. The "multi" is only needed when you really want to update more than one document with the statement

Returning only first element of a sub array with $elemMatch

I'm using node and mongoose, and have a schema that looks like this:
var SubscriberSchema = new Schema({
'user': [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
'level': { type: String, enum: [ 'owner', 'sub', 'commenter', 'poster' ] }
'dateAdded': { type: Date, default: Date.now }
});
// Group Schema
var GroupSchema = new Schema({
'groupOwner': [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
'groupName': String,
'subscribers': [SubscriberSchema],
});
I would like to query the group to find all groups where a user (stored in req.user._id via token authentication) is a subscriber (i.e. their _id is in the subscribers array), and only return the single subscribers array element with their _id.
I've read the Mongo documentation on $elemMatch as this seems to be what I need, and built the query below. This returns the information I want, but returns all elements of the subscribers array. How can I return only the single element of the subscribers array that matches my req.user._id?
Current query, returns all elements of subscribers:
Group
.find( { "subscribers.user": req.user._id}, { subscribers: { $elemMatch: { user: req.user._id }}} )
.sort('groupName')
.populate('groupOwner', 'email firstName lastName')
.populate('subscribers.user', 'email firstName lastName')
.exec(function(err, data) {
if (err) {
logger.error('Unable to retrieve groups for user: ' + err.message);
res.status(500)
} else {
res.json(data);
}
});
This returns the following for subscribers (via util.inspect(data[0].subscribers)):
Subscribers
[{
user:
[ { _id: 1234,
email: 'me#here.com',
firstName: 'Testy',
lastName: 'Testelson' } ] }
user:
[ { _id: 5678,
email: 'you#there.com',
firstName: 'Biggy',
lastName: 'Smalls' } ] }]
Based on the $elemMatch docs, I would assume I would only see user 1234 since that's the record that matches req.user._id. What am I doing wrong here?
Thanks!
In your projection parameter, use the dollar operator:
{"user.$": 1}
This will return a Group with only a single object in its 'subscribers' array.

Resources