Mongoose update entire nested elements - mongodb

I am using mongoose and MongoDB. Is there any way to update the entire schedule array in the schema? or do I have to use a for loop and insert one by one?
My req.body is an array to replace the entire schedules array object.
[{"_id":"1","description":"hi","time":"Jul 29, 2020 8:55 PM","url":"aaa.com"},{"_id":"2","description":"hi","time":"Jul 29, 2020 8:58 PM","url":"bbb.com"},{"_id":"3","description":"hi"}]
here is my schema.
const schedule = new mongoose.Schema({
user_id: Number,
schedules: [{
description: String,
time: String,
url: String
}]
});
Thank you.

If you're using mongoose, we can avoid using $set. #jitendra's answer is a pure mongodb query which you could run in the mongo shell.
You can refer to this link https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate .
As the link says,
var query = { name: 'borne' };
Model.findOneAndUpdate(query, { name: 'jason bourne' }, options, callback)
// is sent as
Model.findOneAndUpdate(query, { $set: { name: 'jason bourne' }}, options, callback)
This helps prevent accidentally overwriting your document with { name: 'jason bourne' }.
So in your case we just need to write :
Schedule.findOneAndUpdate({_id: "id_of_object"}, {schedules: req.body});
That should do it for you. But internally, as the doc says, it is being sent as:
Schedule.findOneAndUpdate({_id: "id_of_object"}, {$set: {schedules: req.body}})
Ofcourse this assumes that your req.body consists of the array of schedules only. Most likely you're sending it as an object from the front end so maybe it's req.body.object_name . Up to you.

You can update the entire schedule array by using $set , try as follow:
db.collection.update({query},{ $set: { schedules: req.body } },{option});
Here your req.body should be the array with same keys as per defined in your schema.

Related

Mongoose $push to array inside subdocument

I have a companies model which has payment info stored like this:
let paymentSchema = new Schema({
date: Date,
chargeId: String
amount: Number,
receipt_url: String,
currency: String,
type: String
});
paymentInfo: {
lastPayment: {type: paymentSchema},
paymentHistory: [{type: paymentSchema}],
paymentMethod: paymentMethod,
customerId: customerId
}
)};
The paymentInfo is a subdocument and I am using schema there for some validation purposes.
While trying to update the a company's info using the following query, it updates everything except the paymentHistory :
Companies.updateOne(
{_id: companyId},
{
paymentInfo: {
paymentMethod: paymentMethod,
customerId: customerId,
lastPayment : paymentObject,
$push: {paymentHistory: paymentObject }
}
One could say why save the same paymentObject in lastPayment and also push it to paymentHistory - that's required for a feature we need and that's not causing any trouble.
I also tried the following update query and it gives a conflict error - updates in the following code is a valid object and without $push it works fine
{$set: updates, $push: {'paymentInfo.paymentHistory': paymentObject}}
Conflict Error :
{"driver":true,"name":"MongoError","index":0,"code":40,"errmsg":"Updating the path 'paymentInfo' would create a conflict at 'paymentInfo'"}
What do I want?
I want to update the company document in a single database call - looking at that error it seems like I am supposed to make 2 calls, one for $set and other for $push? Is there anything wrong with the first update query??
Possible Solution
One way to solve this is to take out the paymentHistory out of paymentInfo so that it's part of main company schema - that solves the issue but I want to keep all payment related stuff inside paymentInfo.
Any help would be appreciated. Thanks

Accessing nested documents within nested documents

I'm having a problem that is really bugging me. I don't even want to use this solution I don't think but I want to know if there is one.
I was creating a comment section with mongodb and mongoose and keeping the comments attached to the resource like this:
const MovieSchema = new mongoose.Schema({
movieTitle: {type: String, text: true},
year: Number,
imdb: String,
comments: [{
date: Date,
body: String
}]
})
When editing the comments body I understood I could access a nested document like this:
const query = {
imdb: req.body.movie.imdb,
"comments._id": new ObjectId(req.body.editedComment._id)
}
const update = {
$set: {
"comments.$.body": req.body.newComment
}
}
Movie.findOneAndUpdate(query, update, function(err, movie) {
//do stuff
})
I then wanted to roll out a first level reply to comments, where every reply to a comment or to another reply just appeared as an array of replies for the top level comment (sort of like Facebook, not like reddit). At first I wanted to keep the replies attached to the comments just as I had kept the comments attachted to the resource. So the schema would look something like this:
const MovieSchema = new mongoose.Schema({
movieTitle: {type: String, text: true},
year: Number,
imdb: String,
comments: [{
date: Date,
body: String,
replies: [{
date: Date,
body: String
}]
}]
})
My question is how would you go about accessing a nested nested document. For instance if I wanted to edit a reply it doesn't seem I can use two $ symbols. So how would I do this in mongodb, and is this even possible?
I'm pretty sure I'm going to make Comments have its own model to simplify things but I still want to know if this is possible because it seems like a pretty big drawback of mongodb if not. On the other hand I'd feel pretty stupid using mongodb if I didn't figure out how to edit a nested nested document...
according to this issue: https://jira.mongodb.org/browse/SERVER-27089
updating nested-nested elements can be done this way:
parent.update({},
{$set: {“children.$[i].children.$[j].d”: nuValue}},
{ arrayFilters: [{ “i._id”: childId}, { “j._id”: grandchildId }] });
this is included in MongoDB 3.5.12 development version, in the MongoDB 3.6 production version.
according to https://github.com/Automattic/mongoose/issues/5986#issuecomment-358065800 it's supposed to be supported in mongoose 5+
if you're using an older mongodb or mongoose versions, there are 2 options:
find parent, edit result's grandchild, save parent.
const result = await parent.findById(parentId);
const grandchild = result.children.find(child => child._id.equals(childId))
.children.find(grandchild => grandchild._id.equals(grandchildId));
grandchild.field = value;
parent.save();
know granchild's index "somehow", findByIdAndUpdate parent with:
parent.findByIdAndUpdate(id,
{ $set: { [`children.$.children.${index}.field`]: value }});

How to access the properties of a query result in Mongo

I can find a document on my database. A call to:
subject = await Subject.find({ name: 'Math' });
res.send(subject);
returns the document correctly:
{
"topics": [],
"_id": "5ab71fe102863b28e8fd1a3a",
"name": "Math",
"__v": 0
}
The problem is when I try to access the properties of subject. Any of the following calls returns nothing:
res.send(subject._id);
res.send(subject.name);
I've tried subject.toObject() and subject.toArray() but I receive an error:
(node:2068) UnhandledPromiseRejectionWarning: TypeError: subject.toObject is not a function
Any help will be appreciated. Thanks!
NB:
before res.send(subject), I called console.log(subject) and the output is:
[ { topics: [],
_id: 5ab71fe102863b28e8fd1a3a,
name: 'cocei5',
__v: 0 } ]
That is because find method in MongoDB always returns an array.
subject = await Subject.find({ name: 'Math' });
So in above line the Subject.find({name: 'Math'}) is returning an array. Which you are storing in subject variable. if you are getting only single object from DB then you might access the object properties by using subject[0].propertyName.
like if you want to send just an id you can do it by
res.send(subject[0]._id);
You can always use the es6 destructuring feature to get the first element returned in the array, as long as you are sure the result will always be on the 0th index.
const [subject] = await Subject.find({ name: 'Math' });
res.send(subject._id);
res.send(subject.name);
ref: Destructuring arrays and objects
Details for find api
OR you can either use
const subject = await Subject.findOne({ name: 'Math' });
res.send(subject._id);
res.send(subject.name);
As findOne return object whereas find returns an array of objects.
ref: Details for findOne api

MongoDB: Get all mentioned items

I've got two relations in my Mongoose/MongoDB-Application:
USER:
{
name: String,
items: [{ type: mongoose.Schema.ObjectId, ref: 'Spot' }]
}
and
ITEM
{
title: String,
price: Number
}
As you can see, my user-collection containing a "has-many"-relation to the item-collection.
I'm wondering how to get all Items which are mentioned in the items-field of on specific user.
Guess its very common question, but I haven't found any solution on my own in the Docs or elsewhere. Can anybody help me with that?
If you are Storing items reference in user collection,then fetch all items from user,it will give you a array of object ids of items and then you can access all items bases on their ids
var itemIdsArray = User.items;
Item.find({
'_id': { $in: itemIdsArray}
}, function(err, docs){
console.log(docs);
});
You can get the items at the same time you query for the user, by using Mongoose's support for population:
User.findOne({_id: userId}).populate('items').exec(function(err, user) {
// user.items contains the referenced docs instead of just the ObjectIds
});

How do I use new Meteor.Collection.ObjectID() in my mongo queries with meteor?

I have a Collection that has documents with an array of nested objects.
Here is fixture code to populate the database:
if (Parents.find().count() == 0) {
var parentId = Parents.insert({
name: "Parent One"
});
Children.insert({
parent: parentId,
fields: [
{
_id: new Meteor.Collection.ObjectID(),
position: 3,
name: "three"
},
{
_id: new Meteor.Collection.ObjectID(),
position: 1,
name: "one"
},
{
_id: new Meteor.Collection.ObjectID(),
position: 2,
name: "two"
},
]
});
}
You might be asking yourself, why do I even need an ObjectID when I can just update based off of the names. This is a simplified example to a much more complex schema that I'm currently working on and the the nested object are going to be created dynamically, the ObjectID's are definitely going to be necessary to make this work.
Basically, I need a way to save those nested objects with a unique ID and be able to update the fields by their _id.
Here is my Method, and the call I'm making from the browser console:
Meteor.methods({
upChild: function( options ) {
console.log(new Meteor.Collection.ObjectID());
Children.update({_id: options._id, "fields._id": options.fieldId }, {$set: {"fields.$.position": options.position}}, function(error){
if(error) {
console.log(error);
} else {
console.log("success");
}
});
}
});
My call from the console:
Meteor.call('upChild', {
_id: "5NuiSNQdNcZwau92M",
fieldId: "9b93aa1ef3868d762b84d2f2",
position: 1
});
And here is a screenshot of the html where I'm rendering all of the data for the Parents and Children collections:
Just an observation, as I was looking how generate unique IDs client side for a similar reason. I found calling new Meteor.Collection.ObjectID() was returning a object in the form 'ObjectID("abc...")'. By assigning Meteor.Collection.ObjectID()._str to _id, I got string as 'abc...' instead, which is what I wanted.
I hope this helps, and I'd be curious to know if anyone has a better way of handling this?
Jason
Avoid using the _str because it can change in the future. Use this:
new Meteor.Collection.ObjectID().toHexString() or
new Meteor.Collection.ObjectID().valueOf()
You can also use the official random package:
Random.id()