mongoose update item in array by position/index - node.js

Is there a way to update for example the second item in an array with mongoose?
Schema:
title: String,
answer: [{
content: String,
votes: Number
}]
In my case I want to update the first or second answer, so it has 1 vote more.

I'm not a mongoose expert. But there seems to be some answer you can reference.
So the short answer is yes, you can update an array element by specify its index. In shell it would be:
db.collection.update({...}, {$inc: {"answer.0.votes": 1}})
where 0 is the index of element you want to update. I'm sure you can find corresponding grammar how to write this in mongoose.
In my opinion, to update an array by its index is not really a good idea. What if an element is removed/inserted? Your embedded array element is like a one to many relation in a RDBMS. They should have unique identifier like an ObjectId to be located more easily. Suggested data structure:
{
title: "This is a question",
answer: [{
id: ObjectId("584e6c496c9b4252733ea6fb"),
content: "Answer",
votes: 0
}]
}
And query by id to find the exact answer you are voting up:
db.collection.update({"answer": {$elemMatch: {id: ObjectId("584e6c496c9b4252733ea6fb")}}}, {$inc: {"answer.$.votes": 1}});
where $ means the matched element in the array.
edit - 2018-04-03
if there's only one condition, $elemMatch is not necessary. It's meant for matching 2 or more conditions in one element. Without $elemMatch, different conditions may match different element in the array. Which is not expected in most scenarios.
As of 3.6 MongoDB introduced $[], which allows you to update all elements in an array in one update.

Try this .I hope this will work
Model.findOneAndUpdate({query},{["answer.${element index}.content:new_data"]},{new:true},(err,docs)=>{})

Try this:
Model.findOne({ title: 'your-title' }, function (err, doc){
doc.answer.0.votes.$inc(); // increases votes on first answer by 1
doc.save();
});

Related

mongodb get list of subdocuments that matched with list of values

my document schema goes like this
_id: kkj33h2kjkjh32jk34
events: [
{
_id: k234j3lk4k2j3h4j3j4
},
{
_id: k234j3lk4k2j3h4j3j4
},
{
_id: k234j3lk4k2j3h4j3j4
}
]
here is my query, I have a list of _ids of the subdocuments of events field and I need to get all the matched subdocuments as the response from the event field I have tried to use $in and many but failed can anyone suggest me how to do this
tried this
subarr=['fh576hgfu658uyg7h','k234j3lk4k2j3h4j3j4']
model.findOne({
clgid: req.query.clgid,
'events._id': {$in:subarr}
},{"events.$":1});
but the problem with the above code is that it is fetching the first matching subdocument. but I need all the matching subdocuments.
suggest me the right way to do this query so that I get all the matched subdocuments that match from array
The issue of your query matching only the first subdocument is the use of {"events.$":1} in your projection.
I'm not sure what you are actually intending to do.
{"events.$":1} will limit to the first (sub)document matching your query, as per the documentation of the $ operator.
Maybe you're trying only to get the _id of the subdocuments and then, please try the following:
subarr=['fh576hgfu658uyg7h','k234j3lk4k2j3h4j3j4']
model.findOne({
clgid: req.query.clgid,
'events._id': {$in:subarr}
},{"events._id":1});

Remove element in array by value | MongoDB | NodeJS

I have a mongoDB with a collection called users. Each element has a usrName, UserID, token, and then 3 arrays which are friends, incoming and outgoing (all of them relative to 'friends'). Im looking for something like:
db.collection('users').find({user:"some userID"}).update or something similar.
I expect the search for the correct element (by the id) and then remove some element from the 'incoming' array by the value. (its a string array)
here is the DB structure
The update would look something like this:
db.collection('users').updateOne({user: "some userID"}, {$pull: { incoming: "removeThisValue" }})
$pull takes an object which describes the array field you are pulling from and the criteria you are using to remove it. Since you said it's a string array you'll be looking for equality. But just note that you could also use mongodb operators such as $gte, $lt and others if working with different values.
If you were removing multiple values from the array at once:
db.collection('users').updateOne({user: "some userID"}, {$pull: { incoming: { $in: ["removeMe", "removeMeToo"] } }})
Its also worth noting if you ever have an array of documents you could also target specific key/values. Let's say a user has an array of hobbies where each hobby has "title" and "timePerWeek" keys. We could remove the hobby with the title of "Gaming" like this:
db.collection('users').updateOne({user: "some userID"}, {$pull: { hobbies: {title: "Gaming"} }})

Mongoose - get length of array in model

I have this Mongoose schema:
var postSchema = mongoose.Schema({
postId: {
type: Number,
unique: true
},
upvotes: [
{
type: Number,
unique: true
}
]
});
what the best query to use to get the length of the upvotes array? I don't believe I need to use aggregation because I only want to query for one model, just need the length of the upvotes array for a given model.
Really struggling to find this info online - everything I search for mentions the aggregation methodology which I don't believe I need.
Also, as a side note, the unique schema property of the upvotes array doesn't work, perhaps I am doing that wrong.
find results can only include content from the docs themselves1, while aggregate can project new values that are derived from the doc's content (like an array's length). That's why you need to use aggregate for this, even though you're getting just a single doc.
Post.aggregate([{$match: {postId: 5}}, {$project: {upvotes: {$size: '$upvotes'}}}])
1Single exception is the $meta projection operator to project a $text query result's score.
I'm not normally a fan of caching values, but it might be an option (and after finding this stackoverflow answer is what I'm going to do for my use case) to calculate the length of the field when the record is updated in the pre('validate') hook. For example:
var schema = new mongoose.Schema({
name: String,
upvoteCount: Number,
upvotes: [{}]
});
schema.pre('validate', function (next) {
this.upvoteCount = this.upvotes.length
next();
});
Just note that you need to do your updates the mongoose way by loading the object using find and then saving changes using object.save() - don't use findOneAndUpdate
postSchema.virtual('upvoteCount').get(function () {
return this.upvotes.length
});
let doc = await Post.findById('foobar123')
doc.upvoteCount // length of upvotes
My suggestion would be to pull the entire upvotes fields data and use .length property of returned array in node.js code
//logic only, not a functional code
post.find( filterexpression, {upvote: 1}, function(err, res){
console.log(res.upvotes.length);
});
EDIT:
Other way of doing would be stored Javascript. You can query the
upvote and count the same in mongodb side stored Javascript using
.length

Mongoose: how to insert a field/value into an existing subdocument

I am having trouble understanding how new field:value objects are added to mongo databases.
Say I have the following document:
var people={'firstname': 'John','surname':'Smith',
'favouritefood':[{'fruit':'apples','drink':'coffee'}]}
how do I add 'vegetable':'broccoli' to 'favourite food' so it looks like this:
{'firstname': 'John','surname':'Smith',
'favouritefood':[{'fruit':'apples','drink':'coffee','vegetable':'broccoli'}]}
If I type:
people.findOneAndUpdate({'firstname':'John'},
{$push {favouritefood: {vegetable:broccoli}}},
{upsert:true})
it gives me this:
{'firstname': 'John','surname':'Smith',
'favouritefood':[{'fruit':'apples','drink':'coffee'},{'vegetable':'broccoli'}]}
and if I try:
people.findOneAndUpdate({'favoritefood':'apples'},
{$push {vegetable:broccoli}}},
{upsert:true})
it says:
'$push' is empty. You must specify a field like so: {$push: {<field>: ...}}
Any help is greatly appreciated!
I think you will be able to update the favouritefood array first object following this syntax
But i suggest you the same that people in the comments, change your structure to one more natural in mongoDB. Consider each category of food as an element of the array not an object field of the first element.

need guidance on node.js multilingual presentation

I am new to node (v0.10) stack.
I am trying to achieve the following:
I have (hopefully) multilingual articles in the latest MongoDB such as:
_id
...more fields...
text: [
{lang: 'en', title: 'some title', body: 'body', slug: 'slug'},
....
]
Everytime I display an article in specific language I query as follows:
var query = Model.findOne({'text.slug': slug});
query.exec(function(err, doc){
async.each(doc.text, function(item, callback){
if (item.lang == articleLang) {
//populate the article to display
}
});
res.render('view', {post:articleToDisplay});
});
Slug is unique for each language!
The problem I have is that mongo will return the whole doc with all subdocs and not just the subdoc I searched for. Now I have to choose to iterate over all subdocs and display the appropriate one on client side or use async.each on the server to get the subdoc I need and only send to the views that one. I am doing it with async on the server. Is that OK? Also async iterates asynchronously but node still waits for the whole loop to finish and then renders the view. Am I missing anything thinking that the user is actually blocked until the async.each finishes? I am still trying to wrap my head around this asynchronous execution. Is there a way I can possibly improve how I manage this code? It seems to be quite standard procedure with subdocs!
Thanks in advance for all your help.
To achieve what you want, you need to make use of the aggregation pipeline. Using a simple findOne() would not be of help,
since you would then have to redact sub documents in your code rather than allowing mongodb to do it. find() and findOne() return the whole document when
a document matches the search criteria.
In the aggregation pipleline you could use the $unwind and $match operators to achieve this.
$unwind:
Deconstructs an array field from the input documents to output a
document for each element. Each output document is the input document
with the value of the array field replaced by the element.
First unwind the document based on the text values array.
$match:
Filters the documents to pass only the documents that match the
specified condition(s) to the next pipeline stage.
Then use the $match operator to match the appropriate documents.
db.Model.aggregate([
{$unwind:"$text"},
{$match:{"text.slug":slug,"text.lang":articleLang}}
])
Doing this would return you only one document with its text field containing only one object. such as: (Note that the text field in the output is not an array)
{ "_id" : ... ,.., "text" : { "slug" : "slug", "lang" : "en" ,...} }

Resources