in which case to use objectId over string validation in mongodb? - node.js

getting validation failed error while insertion in comment collection.
I am having 3 collections(Rule, Page and Comment).
Rule collection:-
{validator: { $or: [ { r_name: { $type: "string"} } ] }}
Page collection:-
{validator: { $or: [ { url: { $type: "string"} } ] }}
Comment collection:-
{ validator: { $or: [ { rule_id: { $type: 'objectId' } }, { page_id: { $type: 'objectId' } } ] } }
insertion in Comment collection:-
process
.myDb
.comment
.insertOne({
rule_id: "5a035eb6eea8b4ba363e6f8d",
page_id: "5a035effeea8b4ba363e6f8e"
})
.then(resp => {
console.log('Success');
})
.catch(() => {
// HERE i am getting "Document failed validation"
console.log('Error');
});
Doubts:-
I am not sure what kind of validation to be used in this case for comment collection.
what is the difference between validation $type objectId & string.
as far as I found objectId is binary but the string would be character array implementation.
when to use objectId?
in RDBMS we would create a foreign key how to achieve the same in MongoDB.
this can be solved by mongoose plugin but I cant use that because of some other reasons.

You're passing in strings where your validator expects ObjectIds. You can however compute the corresponding ObjectId from a given string by using ObjectId(stringValue).
In your code:
process
.myDb
.comment
.insertOne({
rule_id: ObjectId("5a035eb6eea8b4ba363e6f8d"),
page_id: ObjectId("5a035effeea8b4ba363e6f8e")
})
.then(resp => {
console.log('Success');
})
.catch(() => {
// HERE i am getting "Document failed validation"
console.log('Error');
});
You might need to prefix ObjectId() with a module name depending on how you import mongoDB etc., but you get the idea.
See here for reference.

Related

mongoose query multiple operations in one request

I'm trying to use the $set, $addToSet and $inc at the same time for my report of sales and
tbh I'm not even sure if I did the right approach since it's not working.
once I send the request, the console gives me the error 404 but when I check the req.body the data was correct. so I was wondering if the problem is my query on mongoose because this was the first time I use multiple operations on mongoose query
export const report_of_sales = async (req, res) => {
const { id } = req.params;
console.log(req.body);
try {
if (!mongoose.Types.ObjectId.isValid(id)) return res.status(404).json({ message: 'Invalid ID' });
let i;
for (i = 0; i < req.body.sales_report.length; i++) {
await OwnerModels.findByIdAndUpdate(id, {
$inc: {
total_clients: req.body.total_clients,
total_product_sold: req.body.sales_report[i].qty,
sales_revenue: req.body.sales_report[i].amount
},
$set: {
"months.$[s].month_digit": req.body.months[i].month_digit,
"months.$[s].targetsales": req.body.months[i].targetsales,
"months.$[s].sales": req.body.months[i].sales,
},
$addToSet: {
sales_report: {
$each: [{
identifier: req.body.sales_report[i].identifier,
product_name: req.body.sales_report[i].product_name,
generic_name: req.body.sales_report[i].generic_name,
description: req.body.sales_report[i].description,
qty: req.body.sales_report[i].qty,
amount: req.body.sales_report[i].amount,
profit: req.body.sales_report[i].profit
}]
}
}
}, {
arrayFilters: [
{
"s.month_digit": req.body.months[i].month_digit
}
],
returnDocument: 'after',
safe: true,
}, { new: true, upsert: true })
}
} catch (error) {
res.status(404).json(error);
}
}
Well, you are looking at the body, but you are actually using query parameter named id. This is probably undefined, which leads to ObjectId.isValid(id) returning false.
You should decide on whether to pass this data as a query param or in the request body and adjust your code accordingly.

How can I find specific document and update a value of specific key inside array?

I have a structure like this:
{
_id: new ObjectId("634aa49f98e3a05346dd2327"),
filmName: 'Film number 1',
episodes: [
{
episodeName: 'Testing 1',
slugEpisode: 'testing-1',
_id: new ObjectId("6351395c17f08335f1dabfc9")
},
{
episodeName: 'Testing 2',
slugEpisode: 'testing-2',
_id: new ObjectId("6351399d9a2533b9be1cbab0")
},
],
},
{
_id: new ObjectId("634aa4cc98e3a05346dd232a"),
filmName: 'Film number 2',
episodes: [
{
episodeName: 'Something 1',
slugEpisode: 'something-1',
_id: new ObjectId("6367cce66d6b85442f850b3a")
},
{
episodeName: 'Something 2',
slugEpisode: 'something-2',
_id: new ObjectId("6367cd0e6d6b85442f850b3e")
},
],
}
I received 3 fields:
_id: Film _id
episodeId: Episode _id
episodeName: The content I wish to update
I tried to find a specific Film ID to get a specific film, and from then on, I pass an Episode ID to find the exact episode in the episodes array. Then, update the episodeName of that specific episode.
Here's my code in NodeJS:
editEpisode: async (req, res) => {
const { _id } = req.params
const { episodeId, episodeName } = req.body
try {
const specificResult = await Films.findOneAndUpdate(
{ _id, 'episodes._id': episodeId },
{ episodeName }
)
console.log(specificResult)
res.json({ msg: "Success update episode name" })
} catch (err) {
return res.status(500).json({ msg: err.message })
}
},
But what console.log display to me is a whole document, and when I check in MongoDB, there was no update at all, does my way of using findOneAndUpdate incorrect?
I'm reading this document: MongooseJS - Find One and Update, they said this one gives me the option to filter and update.
The MongoDB server needs to know which array element to update. If there is just one array element to update, here's one way you could do it. (I picked a specific element. You would use your req.params and req.body.)
db.films.update({
"_id": ObjectId("634aa4cc98e3a05346dd232a"),
"episodes._id": ObjectId("6367cd0e6d6b85442f850b3e")
},
{
"$set": {
"episodes.$.episodeName": "Something Two"
}
})
Try it on mongoplayground.net.
You can use the filtered positional operator $[<identifier>] which essentially finds the element or object (in your case) with a filter condition and updates that.
Query:
const { _id } = req.params
const { episodeId, episodeName } = req.body
await Films.update({
"_id": _id
},
{
$set: {
"episodes.$[elem].episodeName": episodeName
}
},
{
arrayFilters: [
{
"elem._id": episodeId
}
]
})
Check it out here for example purpose I've put ids as numbers and episode name to update as "UpdatedValue"

Get a single element from array based on condition MongoDB

Schema
// Doc
{
_id:"sr_1",
posts:[
{
_id:"1",
title:"Post 1",
tags:["rahul","kumar","thakur"]
},
{
_id:"2",
title:"Post 2",
tags:["shani","kumar","sharma"]
},
....
]
}
What do i want?
I want to get title attribute of a post.
Where
post id 2
post is inside posts array
posts array is inside document with _id sr_1.
My Solution
Blog.find({
_id: "sr_1",
"posts._id": "2"
},
{ "posts.$": 1 }
)
.then(docs => {
docs.forEach(post => console.log(post))
})
.catch(err => {
console.log(err);
})
This solution is working but when i replace Blog.find by Blog.findById or Blog.findOne then i am getting error:
MongoError: positional operator '.$' couldn't find a matching element in the array
Can anyone tell me why i am getting error?
You can use elemMatch and try this:
Blog.find({_id: "sr_1"
},
{ posts: { $elemMatch: { _id: '2', title: 'Post 2' } } },
{ "posts.$": 1}
);

Update multiple objects in nested array

Question: Is it possible to update multiple objects in a nested array based on another field in the objects, using a single Mongoose method?
More specifically, I'm trying to update subscribed in each object of the Contact.groups array where the object's name value is included in groupNames. Solution 1 works, but it seems messy and inefficient to use both findOne() and save(). Solution 2 is close to working with just findOneAndUpdate(), but only the first eligible object in Contact.groups is updated. Am I able to update all the eligible objects using just findOneAndUpdate()?
Contact schema (trimmed down to relevant info):
{
phone: { type: String, unique: true },
groups: [
{
name: { type: String },
subscribed: { type: Boolean }
}
]
}
Variables I have at this point:
const phoneToUpdate = '1234567890' // Contact.phone to find
const groupNames = [ 'A', 'B', 'C' ] // Contacts.groups <obj>.name must be one of these
const subStatus = false // Contacts.groups <obj>.subscribed new value
Solution 1 (seems inefficient and messy):
Contact
.findOne({ phone: phoneToUpdate })
.then(contact => {
contact.groups
.filter(g => groupNames.includes(g.name))
.forEach(g => g.subscribed = subStatus)
contact
.save()
.then(c => console.log(c))
.catch(e => console.log(e))
})
.catch(e => console.log(e))
Solution 2 (only updates the first matching object):
Contact
.findOneAndUpdate(
{ phone: phoneToUpdate, 'groups.name': { $in: groupNames } },
{ $set: { 'groups.$.subscribed': subStatus } },
{ new: true }
)
.then(c => console.log(c))
.catch(error => console.log(error))
// Example Contact after the findOneAndUpdate
{
phone: '1234567890',
groups: [
{ name: 'A', subscribed: false },
{ name: 'B', subscribed: true } // Should also be false
]
}
You can not use $ operator since he will act as a placeholder only for the first match.
The positional $ operator acts as a placeholder for the first match of
the update query document.
What you can use is arrayFilters operator. You can modify your query like this:
Contact.findOneAndUpdate({
"phone": phoneToUpdate
},
{
"$set": {
"groups.$[elem].subscribed": subStatus
}
},
{
"arrayFilters": [
{
"elem.name": {
"$in": groupNames
}
}
]
})
Here is a working example: https://mongoplayground.net/p/sBT-aC4zW93

MongoDB request: Filter only specific field from embedded documents in embedded array

I have got some troubles with a MongoDB request that I would like to execute. I am using MongoDB 3.2 with Mongoose in a node.js context. Here is the document:
{
_id: ObjectId('12345'),
name: "The name",
components: [
{
name: "Component 1",
description: "The description",
container: {
parameters: [
{
name: "Parameter 1",
value: "1"
},
// other parameters
],
// other information...
}
},
// other components
]
}
And I would like to list all the parameters name for a specific component (using component name) in the specific document (using _id) with this output:
['Parameter 1', 'Parameter 2', ...]
I have got a mongoose Schema to handle the find or distinct methods in my application. I tried many operations using $ positioning operator. Here is my attempt but return all parameters form all components:
listParameters(req, res) {
DocumentModel.distinct(
"components.container.parameters.name",
{_id: req.params.modelId, "components.name": req.params.compId},
(err, doc) => {
if (err) {
return res.status(500).send(err);
}
res.status(200).json(doc);
}
);
}
but the output is the list of parameter name but without the filter of the specific component. Can you help me to find the right request? (if possible in mongoose JS but if it is a Mongo command line, it will be very good :))
You would need to run an aggregation pipeline that uses the $arrayElemAt and $filter operators to get the desired result.
The $filter operator will filter the components array to return the element satisfying the given condition whilst the $arrayElemAt returns the document from the array at a given index position. With that document you can then project the nested parameters array elements to another array.
Combining the above you ideally want to have the following aggregate operation:
DocumentModel.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(req.params.modelId) } },
{ "$project": {
"component": {
"$arrayElemAt": [
{
"$filter": {
"input": "$components",
"as": "el",
"cond": { "$eq": ["$$el.name", req.params.compId] }
}
}, 0
]
}
} },
{ "$project": {
"parameters": "$component.container.parameters.name"
} }
], (err, doc) => {
if (err) {
return res.status(500).send(err);
}
const result = doc.length >= 1 ? doc[0].parameters : [];
res.status(200).json(result);
})
i don`t know how to do it with mongoose but this will work for you with mongo
db.getCollection('your collaction').aggregate([ // change to youe collaction
{$match: {_id: ObjectId("5a97ff4cf832104b76d29af7")}}, //change to you id
{$unwind: '$components'},
{$match: {'components.name': 'Component 1'}}, // change to the name you want
{$unwind: '$components.container.parameters'},
{
$group: {
_id: '$_id',
params: {$push: '$components.container.parameters.name'}
}
}
]);

Resources