Mongoose: how to insert a field/value into an existing subdocument - node.js

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.

Related

mongoose update item in array by position/index

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();
});

How to Join two collections in MongoDB and NodeJS with $lookup and DbRef?

If i have two collections where one of that have dbref, how is possibile to join using $lookup and dbref?
DBref is a BSON Object and you cannot make a lookup using its value.
BUT, there is a way to do it, transforming a DBRef object into an array.
I have written an answer a few months ago describing how to do it.
Short explanation
Say you have DBRef object like this :
myField: DBRef("otherCollection", ObjectId("582abcd85d2dfa67f44127e0")),
Use $objectToArray on myField like this
db.myColl.aggregate([
{
$project: {
transformedDBRef: {$objectToArray: "$myField"},
}
},
])
The result would be an array of two objects, one object for the reference, one for the ObjectId contained within the DBRef, each with a field "k" and a field "v". It will look like this:
transformedDBRef: [{"k" : "$ref","v" : "otherCollection"},{"k" : "$id","v" : ObjectId("582abcd85d2dfa67f44127e0")}
You can then grep the ObjectId. For the complete solution, please check the link above.

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" ,...} }

How to get only one item of a subdocument in mongoose?

ASchema={item:[BSchema]};
ASchema.findOne({item._id=xx})
It gets a array of BSchema, document.item is a array. how to get only one item which _id is xx?
You want the positional $ operator using query projection to just return your matched array element. For Mongoose you can do this:
ASchema.findOne({"item._id": itemId},"item.$",function(err,doc) {
console.log( doc );
});
Or paired in an object:
ASchema.findOne({"item._id": itemId},{ "item.$": 1 },function(err,doc) {
console.log( doc );
});
Mongoose supports the shorthand syntax with options like "-fieldname" for field removal which is the same as { "fieldname": 0 }. But you cannot mix inclusion and exclusion with the exception of the root _id field.
Therefore you must specify all of the fields you want to appear when using projection.
See also .select() in the mongoose documentation.
I think your syntax for the query is wrong. Try:
ASchema.findOne({'item._id': xx})
This link is helpful for more examples: http://mongoosejs.com/docs/queries.html

How to query mongodb with DBRef

suppose I have the following datastructure:
var user = {_id: 'foo', age: 35};
var post = {_id: '...', author: {$ref: user, $id: 'foo'},...};
How can I query all posts which references user[foo]? I tried the following but not work:
db.post.find('author._id': 'foo');
var u = db.user.find({_id: 'foo'});
db.post.find('author': u);
neither can I find the answer from the official document and google!
Anyone has any idea?
Got it:
db.post.find({'author.$id': 'foo'})
This db.post.find('author.$id': 'foo') has missing the {}, so the correct sentence is:
db.post.find({'author.$id': 'foo'})
Also this can be achieved with:
db.post.find({'author': DBRef("user", ObjectId('foo'))})
But is more compact and practical the first way.
You can use the .$id reference but it will ignore any indexes on those fields.
I would suggest ignoring that method unless you are querying it directly via the terminal or want to look up something quickly. In using large collections you will want to index the field and query it using the below method.
If you want to use an index query using the following:
db.post.find('author' : { "$ref" : 'user', "$id" : 'foo' , "$db" :'database_name' })
If foo is an object id
db.post.find('author' : { "$ref" : 'user', "$id" : ObjectId('foo') , "$db" :'database_name' })
You can create an index on author by
db.post.ensureIndex( {'author' : 1 } );
For anyone looking for a Java solution to this then if you are using mongojack its really easy:
collection.find(DBQuery.is("user", new DBRef(user.getId(), User.class)));
Where collection is a JacksonDBCollection.
In mongoengine you should just use the instance of the referenced object. It should have the ID set.
Suppose the author is the Author document instance. So using this:
Post.objects(author__eq=author)
you can go through all posts of this author.
Post.author should be defined as ReferenceField
Using Mongo 2.4.1 version
This is how you do it on command line for OLA collection where #DBRef dbrefName
db.OLA.find({"dbrefName.someFieldValue" : "Personal"});
Exact query
db.OLA.find({"dbrefName.$id" : ObjectId("1234")});

Resources