In my mongo database I have a tree like this:
-shopping-lists
|- list
|-products
|-item
|-item
|- list
|-products
|-item
|-item
I need to find which shopping list I'm updating and then go to products array, find an item inside and update one property.
I'm trying to solve this from few hours right now, but didn't find any solution.
How to update item which is nested 4 levels down??
After 6 hours of investigation, I have found an answer.
For any of you guys, having the same problem here is the answer.
While adding a new item to the Parent model, I've been just pushing these items as plain objects and the Parent model was like this:
const Parent = new Schema(
{
description: { type: String },
childrenArray: [Object],
},
{ timestamps: { createdAt: 'created_at' }, collection: 'shopping-lists' }
);
The problem was in childrenArray, that in the model I had declared this an array of objects.
Changing this to ChildrenModel solved the problem, cause now these items are saved as mongoose subdocuments and I can operate on then using $ operator.
new Schema looks like this:
const Children = new Schema(
{
prop1: { type: String, required: true },
prop2: { type: String, required: true },
prop3: { type: String },
},
{ timestamps: { createdAt: 'createdAt' } }
);
const Parent = new Schema(
{
description: { type: String },
products: [ItemSchema],
},
{ timestamps: { createdAt: 'created_at' }, collection: 'shopping-lists' }
);
Now I can easily access subdocuments like this:
const { name, isOrdered, author, voterIds, _id, listId } = req.body;
Parent.findOneAndUpdate(
{ _id: listId, 'children._id': _id },
{
$set: {
'children.$.isOrdered': true
}
},
(err, doc) => {
console.log(doc);
}
);
As far as i know We can update the nested document by positional operator that is $ but as according to the mongodb documentation we cannot update property nested more then 2 levels deep array. In simple you cannot use $ positional operator two times like "shoppinglists.$.list.$.products so its better to change the structure of your db to do it in better way
Related
I need a function to remove particular objects from a nested array ,please check the code as follow ,I have already tried a lot times ,but fail ..Could you please help me ?Thank you so much in advance!
UserSchema :
userName: {
type: String,
},
specialList: [
{
type: mongoose.Types.ObjectId,
ref: "Friend",
},
],
FriendSchema:
userName:{
type:string
}
Now ,I need a function to delete some of the friends in a user's specialList by their user's id,
For instance ,
//this is not working like I wish ...I have no idea what is going on ...
const needToRmoveList = ["123","456"];
await UserInfo.findOneAndUpdate(
{ _id: "345" },
{ $pull: { specialList: { id: { $in: [needToRmoveList] } } } },
{ new: true }
);
I have a json array reorderList for a topic:
const reorderList = [
{ _id: '5e6b419c76a16d5c44d87132', order: 0 },
{ _id: '5e6b41a276a16d5c44d87139', order: 1 },
{ _id: '5e6b41a776a16d5c44d87140', order: 2 }
]
And my TopicSchema is like this:
var TopicSchema = new Schema({
topicTitle: String,
topicQuestion: [
{
questionTitle: String,
answer: String,
order: Number
}
]
}
Now I want to update my topic questions order based on the reorderList's _id.
But the below statement will replace all the things from topicQuestion (e.g. questionTitle and answer will be removed)
Topic.findOneAndUpdate(
{ '_id': topicId },
{ $set: { 'topicQuestion': reorderList } }, //replaces here
{ upsert: true },
function (err, response) {
...
});
How to update it based on reorderList and also keep the original data inside topicQuestion?
The schema that you're using is badly designed. What you can do here is create another schema, TopicQuestionSchema and put a ref to the topic it belongs to.
var TopicQuestionSchema = new Schema({
questionTitle: String,
answer: String,
order: Number,
topic: {type: ObjectId, ref: 'Topic'} // the name of your model
}
This way you can still keep track of the topic the questions belong to, and still be able to update the order easily.
This is the comment key pair I have in my post model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const postSchema = new Schema({
user:{
type:Schema.Types.ObjectId,
// required:true,
refPath:'onModel'
},
onModel:{
type:String,
enum:['Doctor','Patient']
},
text:{
type:String,
required:true
},
comments:[{
user:{
type:Schema.Types.ObjectId,
refPath:'onModel'
},
reply:{
type:String
},
date:{
type:Date,
default:Date.now
}
}],
likes:[{
user: {
type: Schema.Types.ObjectId,
ref: 'Patient'
}
}]
})
module.exports= post = mongoose.model('post', postSchema);
When I try pushing object to the likes array by running the following code, it fails. The filter part works fine, just some problem with the update part which ends up executing catch block.
Post.updateOne({ _id: req.params.postid, "likes": { $ne : { user:
authorizedData.jwt_payload.patient._id }}},
{ "$set" : { "likes.$.user": "authorizedData.jwt_payload.patient._id"
}})
.then(re => res.json(re))
.catch(err => res.json("already liked"))
Will really appreciate any help.
Please make changes as below :
const mongoose = require('mongoose');
const patientObjectID = mongoose.Types.ObjectId(authorizedData.jwt_payload.patient._id);
Post.updateOne({
_id: req.params.postid,
'likes.user': {
$ne:
patientObjectID
}
},
{ $push: { likes: { user: patientObjectID } }
}).then(re => res.json(re)).catch(err => res.json("already liked"))
Couple of changes need to be done, So When you've a schema like this ::
likes: [{
user: {
type: Schema.Types.ObjectId,
ref: 'Patient'
}
}]
You need to pass an ObjectId() to user field but not a string, So
first we're converting string to ObjectId() & passing it in query.
Also $set is used to
update existing or insert new fields in a document, but when you wanted to push
new values to an array field in a document then you need to use
$push(this seems to be a normal update operation on a field, but here we're not replacing the likes array, rather we're just adding few more elements to it - different kind of update though, So that's why we need to use $push).
As you already have below filter, we're just doing $push assuming what we're pushing is not a duplicate but in the other way you can blindly use $addToSet to do the same without having to use below filter criteria :
"likes": {
$ne: {
user:
patientObjectID
}
}
About your question on $(update) why it isn't working ? This should be used to update the elements in an array, it helps to update first matching element in an array based on filter criteria, but what you wanted to do here is to add few more elements but not updating existing elements in likes array.
Here you should not send "already liked" in catch block, it should be a custom error for an actual error, in .then(re => res.json(re)) you need to check write result of update operation if anything updated you need to send added user, if not you need to send "already liked".
Hope this solves all your questions :-)
Try using $push aggregation which is used for pushing objects to inner arrays in mongoDB. Your update query should be something like the following:
Post.updateOne({ _id: req.params.postid, "likes": { $ne : { user:
authorizedData.jwt_payload.patient._id }}},
{ "$push" : { "likes": authorizedData.jwt_payload.patient._id
}})
.then(re => res.json(re))
.catch(err => res.json("already liked"))
This is my schema:
var userSchema = {
folders : [ folderSchema ],
...
}
var folderSchema = new mongoose.Schema({
itemlist : [{ type: String, ref: 'Item', required: true }],
foldername : { type: String},
...
});
// Item model
var itemSchema = {
name: { type: String },
...
}
I would like to populate itemlist (entire array) inside of each folderSchema, is this possible?
What I've tried so far but doesn't work:
userModel.findOne({ _id: userId }, null, callback).populate({
path: 'folders.$.itemlist',
select: 'name'
});
This post and this post are similar but they store the folder models and have a ref instead of nested document.
Bonus: is it possible to select only some folders by foldername to populate their itemlist?
I think you are looking for "deep population", see the population section "Populating across multiple levels"
rewrite your populate to:
userModel.findOne({ _id: userId }, null, callback).populate({
path: 'folders',
populate: { path : 'itemlist'}
});
The easiest solution is to actually retrieve the nested folder and perform a find manually. Then simply call find({_id: {$in : folder}}); to find all elements of array.
Using Mongoose version 3.6.4
Say I have a MongoDB document like so:
{
"_id" : "5187b74e66ee9af96c39d3d6",
"profile" : {
"name" : {
"first" : "Joe",
"last" : "Pesci",
"middle" : "Frank"
}
}
}
And I have the following schema for Users:
var UserSchema = new mongoose.Schema({
_id: { type: String },
email: { type: String, required: true, index: { unique: true }},
active: { type: Boolean, required: true, 'default': false },
profile: {
name: {
first: { type: String, required: true },
last: { type: String, required: true },
middle: { type: String }
}
}
created: { type: Date, required: true, 'default': Date.now},
updated: { type: Date, required: true, 'default': Date.now}
);
And I submit a form passing a field named: profile[name][first] with a value of Joseph
and thus I want to update just the user's first name, but leave his last and middle alone, I thought I would just do:
User.update({email: "joe#foo.com"}, req.body, function(err, result){});
But when I do that, it "deletes" the profile.name.last and profile.name.middle properties and I end up with a doc that looks like:
{
"_id" : "5187b74e66ee9af96c39d3d6",
"profile" : {
"name" : {
"first" : "Joseph"
}
}
}
So it's basically overwriting all of profile with req.body.profile, which I guess makes sense. Is there any way around it without having to be more explicit by specifying my fields in the update query instead of req.body?
You are correct, Mongoose converts updates to $set for you. But this doesn't solve your issue. Try it out in the mongodb shell and you'll see the same behavior.
Instead, to update a single deeply nested property you need to specify the full path to the deep property in the $set.
User.update({ email: 'joe#foo.com' }, { 'profile.name.first': 'Joseph' }, callback)
One very easy way to solve this with Moongose 4.1 and the flat package:
var flat = require('flat'),
Schema = mongoose.Schema,
schema = new Schema(
{
name: {
first: {
type: String,
trim: true
},
last: {
type: String,
trim: true
}
}
}
);
schema.pre('findOneAndUpdate', function () {
this._update = flat(this._update);
});
mongoose.model('User', schema);
req.body (for example) can now be:
{
name: {
first: 'updatedFirstName'
}
}
The object will be flattened before the actual query is executed, thus $set will update only the expected properties instead of the entire name object.
I think you are looking for $set
http://docs.mongodb.org/manual/reference/operator/set/
User.update({email: "joe#foo.com"}, { $set : req.body}, function(err, result){});
Try that
Maybe it's a good solution - add option to Model.update, that replace nested objects like:
{field1: 1, fields2: {a: 1, b:2 }} => {'field1': 1, 'field2.a': 1, 'field2.b': 2}
nestedToDotNotation: function(obj, keyPrefix) {
var result;
if (keyPrefix == null) {
keyPrefix = '';
}
result = {};
_.each(obj, function(value, key) {
var nestedObj, result_key;
result_key = keyPrefix + key;
if (!_.isArray(value) && _.isObject(value)) {
result_key += '.';
nestedObj = module.exports.nestedToDotNotation(value, result_key);
return _.extend(result, nestedObj);
} else {
return result[result_key] = value;
}
});
return result;
}
});
need improvements circular reference handling, but this is really useful when working with nested objects
I'm using underscore.js here, but these functions easily can be replaced with other analogs