push all element of array in subdocument while saving document - node.js

I want to push elements of array to create subdocument,
my schema
var chatGroup = new Schema({
name : {
type : String,
default : null
},
members: {
type : [subSchemaForMember]
},
}, { collection: 'chatGroup' });
var subSchemaForMember = new Schema({
user_id : {type : Schema.Types.ObjectId , ref : 'user'}},{_id : false});
my query to save document is
var chatGroup = new ChatGroup({
name : req.body.name,
image : req.body.image,
created_by : req.body.celebrity_id,
$pushAll : {'members' : req.body.members}
})
where req.body.memebers = ['someid','someid','someid']
Please help I want to do it without any loop

I don't see you actually saving the document, only calling new on the constructor. You need to explicitly call save. on the object after you construct it. For the documentation on creating documents, see here: http://mongoosejs.com/docs/models.html.
Also, the use of $pushAll only applies when you have an object already in mongodb, which has existing values, and you want to retain those values and push additional values onto the array (so in your example you can simply assign the array to members).
Also of note is that the current mongoose documentation indicates that $pushAll is deprecated and you should be using $push together with $each, but the same rules apply, see here:
https://docs.mongodb.com/manual/reference/operator/update/push/#append-multiple-values-to-an-array

Related

Populate reference object which is also a reference object mongoose

I have a schema called Message, defined likewise:
const messageSchema = new mongoose.Schema({
name : {type : String}
});
module.exports('Message',messageSchema);
I have another schema called Topic, which uses 'Message' as a reference object.
const topicSchema = new mongoose.Schema({
topics : { type : mongoose.Schema.Types.ObjectId , ref : 'Message' }
});
module.exports('Topic',topicSchema);
I have a schema called Thread, which uses an array of 'Topic' object references.
const threadSchema = new mongoose.Schema({
thread : [{ type : mongoose.Schema.Types.ObjectId , ref : 'Topic' }],
name : {type : String}
});
module.exports('Thread',threadSchema);
How to access all the 'Message' elements if we have a 'Thread' document with us?
I tried doing the following:
Thread.findOne({name : 'Climate'}).populate('thread').populate('topics').exec(function(err,data){})
but I am getting errors since thread population has an array. Please help in correctly dereferencing the message object.
After further investigations, I was able to solve the problem. An easy solution not involving nested exec statements is described.
const myThread = await Thread.find({name : "Climate"}).populate('thread');
//This populates the 'thread' component of the Thread model, which is essentially an array of 'Topic' elements.
Since we have populated the 'thread' field as an array, we can iterate through each member of that field, populating the 'topic' field present with the base 'message' model.
const myTopic = myThread.thread;
for(let i = 0; i < myTopic.length ; i++)
{
myCurrentTopic = myTopic[i];
var myTopicPopulated = await Topic.find({_id : myCurrentTopic._id}).populate('topic');
//Do further processing
}
This is a simple way to handle such cases, without resorting to usage of the path proxy.

How to find an object inside an array inside a mongoose model?

I'm trying to query an object that's inside an item which is inside a mongoose model, when I'm trying to find that object with the find() method or _.find() method, I can't get access to the object for some reason and when I console.log() it, it gives me undefined or when I use array.filter() it gives me an empty array, which means the object that I'm trying to access does not meet the criteria that I give it in the lodash find method, but then when I look at my database I see that the object does actually have the properties to meet the criteria. So I don't know what I'm doing wrong, here's my code: as you can see I'm trying to get the information of the item that the user clicked on and want to see:
router.get("/:category/:itemId", (req, res) => {
console.log(req.params.itemId);
//gives the item id that the user clicked on
console.log(req.params.category);
//gives the name of category so I can find items inside it
Category.findOne({ name: req.params.category }, (err, category) => {
const items = category.items; //the array of items
console.log(items); //gives an array back
const item = _.find(items, { _id: req.params.itemId });
console.log(item); //gives the value of 'undefined' for whatever reason
});
});
The category Schema:
const catSchema = new mongoose.Schema({
name: {
type: String,
default: "Unlisted",
},
items: [
{
name: String,
price: Number,
description: String,
img: String,
dateAdded: Date,
lastUpdated: Date,
},
],
dateCreated: Date,
lastUpdate: Date,
});
well the answer is a little bit obvious, you are using MongoDB and in Mongo you have "_ID" you can use that "_ID" only with Mongoose! so you just have to remove the underscore and that is it! do it like this const item = _.find(items, { id: req.params.itemId });
hope you are doing better.
When I look at your Schema I see that the item field is an array of objects which doesn't have an _id inside so when you create a new instance of catShema it just generates an _id field for the new instance but not for each item inside the items array, just also enter the id of the item in question because according to my understanding, you must also have a model called items in your database
When you save these records in your database, you will generate an element with this structure
{
_id: String, // auto generated
name: String,
items: [ {"name1", "price1", "description1", "imgUrl1", "dateAdded1", "lastUpdated1"},{"name2", "price2", "description2", "imgUrl2", "dateAdded1", "lastUpdated2"}, ...],
dateCreated: Date,
lastUpdate: Date
}
Note : the code provided at the top is only an illustration of the object which will be registered in the database and not a valid code
Here you can notice that there is not a field called _id in the object sent inside the database.
My suggestion to solve this issue is
To create an additional field inside the items array called _id like :
{
...,
items: [
{
_id: {
type: String,
unique : true,
required: true // to make sure you will always have it when you create a new instance
},
...
... // the rest of fields of items
},
...
Now when you create a new instance make sure in the object you enter in the database the _id is recorded when you call the catInstance.save() so inside the catInstance object you have to add the current Id of the element to be able to filter using this field.
Hope my answer helped, if you have any additional questions, please let me know
Happy coding ...

Defining a map with ObjectId key and array of strings as value in mongoose schema

I'm facing a problem while creating Mongoose schema for my DB. I want to create a map with a objectId as key and an array of string values as its value. The closest that I can get is:
var schema = new Schema({
map: [{myId: {type:mongoose.Schema.Types.ObjectId, ref: 'MyOtherCollection'}, values: [String]}]
});
But somehow this is not working for me. When I perform an update with {upsert: true}, it is not correctly populating the key: value in the map. In fact, I'm not even sure if I have declared the schema correctly.
Can anyone tell me if the schema is correct ? Also, How can I perform an update with {upsert: true} for this schema?
Also, if above is not correct and can;t be achieved then how can I model my requirement by some other way. My use case is I want to keep a list of values for a given objectId. I don't want any duplicates entries with same key, that's why picked map.
Please suggest if the approach is correct or should this be modelled some other way?
Update:
Based on the answer by #phillee and this, I'm just wondering can we modify the schema mentioned in the accepted answer of the mentioned thread like this:
{
"_id" : ObjectId("4f9519d6684c8b1c9e72e367"),
... // other fields
"myId" : {
"4f9519d6684c8b1c9e73e367" : ["a","b","c"],
"4f9519d6684c8b1c9e73e369" : ["a","b"]
}
}
Schema will be something like:
var schema = new Schema({
myId: {String: [String]}
});
If yes, how can I change my { upsert:true } condition accordingly ? Also, complexity wise will it be more simpler/complex compared to the original schema mentioned in the thread?
I'd suggest changing the schema so you have one entry per myId,
var schema = new Schema({
myId : {type:mongoose.Schema.Types.ObjectId, ref: 'MyOtherCollection'},
values : [String]
})
If you want to update/upsert values,
Model.update({ myId : myId }, { $set : { values : newValues } }, { upsert : true })

Mongodb prevent empty values while updating

I have a collection of words, the scheme is as follows:
var wordsSchema = new Schema({
name : { type : String , trim : true , required : true , unique : true , index: true},
definition : { type : String , trim : true , required : true }
});
when I try to save new word with empty name or definition into collection required: true works end return error,but when I try to update collection and set empty name or definition value required:true doesn't works and updated word with empty fields saves into collection I want to prevent saving empty words into collection. How can I achieve this with a shortest way, My Update code looks like this:
words.update({ _id : word._id }, { $set : {
name : word.name,
definition : word.definition
}
} , function(err,data) {
if(err) {
res.status(1033).send("There was error while changing word");
} else {
res.send('Word has successfully changed');
}
});
required:true is not something maintained at the database level, but rather mongoose itself handles that for you. when you do an update operation, mongoose doesn't run your validators (required:true is a type of validator).
There is a change coming up in mongoose 4 where validators get run on updates. For now, your options are
manually check if there's nothing there (enforced by your application)
do a find on the document, then edit the field and do doc.save which will run validators (enforced by mongoose)
add a document with an empty value for 'word'. because you have a unique index on that field, mongodb will not allow you to insert another document without a value for that field (enforced by mongodb)
I would recommend that last option.

Modelling reference to embedding document using Mongoose

I am modelling two types of events (events and subevents) in a MongoDB like this:
var EventSchema = mongoose.Schema({
'name' : String,
'subEvent' : [ SubeventSchema ]
});
var SubeventSchema = mongoose.Schema({
'name' : String
});
Now when I query a subevent I want to be able to also retrieve data about its corresponding superevent, so that some example data retrieved using Mongoose population feature could look like this:
EventModel.findOne({
name : 'Festival'
})
.populate('subEvent')
.execute(function (err, evt) { return evt; });
{
name : 'Festival',
subEvent: [
{ name : 'First Concert' },
{ name : 'Second Concert' }
]
}
EventModel.findOne({
'subEvent.name' : 'FirstConcert'
}, {
'subEvent.$' : 1
})
.populate('superEvent') // This will not work, this is the actual problem of my question
.execute(function (err, subevt) { return subevt; });
{
name: 'First Concert',
superEvent: {
name: 'Festival'
}
}
A solution I can think of is not to embed but to reference like this:
var EventSchema = mongoose.Schema({
'name' : String,
'subEvent' : [ {
'type' : mongoose.Schema.Types.ObjectId,
'ref' : 'SubeventSchema'
} ]
});
var SubeventSchema = mongoose.Schema({
'name' : String,
'superEvent' : {
'type' : mongoose.Schema.Types.ObjectId,
'ref' : 'EventSchema'
}
});
I am looking for a solution based on the first example using embedded subevents, though. Can this be achieved and in case yes, how?
I think your mental model of document embedding isn't correct. The major misunderstanding (and this is very common) is that you "query a subevent" (query an embedded document). According to your current Event schema, a Subevent is just a document embedded in an Event document. The embedded SubEvent is not a top-level document; it's not a member of any collection in MongoDB. Therefore, you don't query for it. You query for Events (which are the actual collection-level documents in your schema) whose subEvents have certain properties. E.g. one way people translate the query
db.events.find({ "subEvent" : { "name" : "First Concert" } })
into plain English is as "find all the subevents with the name "First Concert". This is wrong. The right translation is "find all events that have at least one subevent whose name is "First Concert" (the "at least one" part depends on knowledge that subEvent is an array).
Coming back to the specific question, you can hopefully see now that trying to do a populate of a "superevent" on a subevent makes no sense. Your queries return events. The optimal schema, be it subevents embedded in events, one- or two-way references between events and subevents documents in separate collections, or events denormalized into the constituent subevent documents, cannot be determined from the information in the question because the use case is not specified.
Perhaps this is a situation where you need to modify your thinking rather than the schema itself. Mongoose .populate() supports the basic ideas of MongoDB "projection", or more commonly referred to as "field selection". So rather than try to model around this, just select the fields you want to populate.
So your second schema form is perfectly valid, just change how you populate:
EventModel.find({}).populate("subEvent", "name").execute(function(err,docs) {
// "subevent" array items only contain "name" now
});
This is actually covered in the Mongoose documentation under the "populate" section.

Resources