Mongoose - Self-referential deep populate error - javascript

I'm trying to populate a self-referential model a few times recursively. Here's my schema:
var TestSchema = new Schema({
title: { type: String },
counter: { type: Number },
children: [ { type: Schema.Types.ObjectId, ref: 'test' } ]
});
var models = {
Test: mongoose.model('test', TestSchema)
};
So far, this functioning code populates everything one level:
models.Test.find().populate('children').exec(function(err, doc) {
if (err)
res.send(err);
else {
res.send(doc);
} });
But when I try to do something like:
models.Test.find().populate('children').populate('children.children').exec(function(err, doc) {
or even:
models.Test.find().populate('children').exec(function(err, doc) {
if (err)
res.send(err);
else {
models.Test.populate(doc, 'children.children', function(err, doc) {
res.send(doc);
});
}
});
I get this error:
TypeError: Cannot call method 'path' of undefined
at search (..../api/node_modules/mongoose/lib/model.js:2059:28)
at search (..../api/node_modules/mongoose/lib/model.js:2078:22)
at Function._getSchema (..../api/node_modules/mongoose/lib/model.js:2085:5)
at populate (..../api/node_modules/mongoose/lib/model.js:1706:22)
at Function.Model.populate (..../api/node_modules/mongoose/lib/model.js:1686:5)
at Promise.<anonymous> (..../api/api.js:22:19)
at Promise.<anonymous> (..../api/node_modules/mongoose/node_modules/mpromise/lib/promise.js:162:8)
at Promise.EventEmitter.emit (events.js:95:17)
at Promise.emit (..../api/node_modules/mongoose/node_modules/mpromise/lib/promise.js:79:38)
at Promise.fulfill (..../api/node_modules/mongoose/node_modules/mpromise/lib/promise.js:92:20)
The mongoose 3.6 release notes say that deep populates are allowed using Model.populate, but that's giving me an error. Does anyone know what's going on?

The Mongoose docs for the Model.populate method states that the second parameter should be an options object, and not a string.
Here's the example they provide:
User.findById(id, function (err, user) {
var opts = [
{ path: 'company', match: { x: 1 }, select: 'name' }
, { path: 'notes', options: { limit: 10 }, model: 'override' }
]
User.populate(user, opts, function (err, user) {
console.log(user);
})
})
So yours should look something like:
models.Test.find().populate('children').exec(function(err, doc) {
if (err)
res.send(err);
else {
var opts = [
{ path: 'children.children' }
];
models.Test.populate(doc, opts, function(err, doc) {
res.send(doc);
});
}
});

Related

mongodb Error mongoose do not push object in array $pushAll

I have a simple app with User and Post models,
var mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/assoc", {useMongoClient:true});
mongoose.Promise = global.Promise;
//Post
var postSchema = new mongoose.Schema({
title: String,
content: String
});
var Post = mongoose.model("Post", postSchema);
//User
var userSchema = new mongoose.Schema({
email: String,
name: String,
posts: [postSchema]
});
var User = mongoose.model("User", userSchema);
I Create a user before (name: "gino") and push a post into:
// var newUser = new User({
// email: "a.b#c.it",
// name: "gino"
// });
//
// newUser.posts.push({
// title: "gino's post",
// content: "this is content"
// });
//
// newUser.save(function (err, user) {
// if (err) {
// console.log(err);
// } else {
// console.log(user);
// }
// });
Also create another post to check if Post model works:
// var newPost = new Post({
// title: "honky",
// content: "tonky"
// });
//
// newPost.save(function (err, post) {
// if (err) {
// console.log(err);
// } else {
// console.log(post);
// }
// });
When I try to find "gino" and push a new item into the posts array I have an error trying to save user (user.save) with this snippet:
User.findOne({name: "gino"}, function (err, user) {
if (err) {
console.log(err);
} else {
console.log(user);
user.posts.push({
title: "post",
content: "content"
});
user.save(function (err, user) {
if (err) {
console.log(err);
} else {
console.log(user);
}
});
}
});
When I run the app i got this:
{ MongoError: Unknown modifier: $pushAll
at Function.MongoError.create (appFolder\node_modules\mongodb-core\lib\error.js:31:11)
at toError (appFolder\node_modules\mongodb\lib\utils.js:139:22)
at appFolder\node_modules\mongodb\lib\collection.js:1059:67
at appFolder\node_modules\mongodb-core\lib\connection\pool.js:469:18
at _combinedTickCallback (internal/process/next_tick.js:131:7)
at process._tickCallback (internal/process/next_tick.js:180:9)
name: 'MongoError',
message: 'Unknown modifier: $pushAll',
driver: true,
index: 0,
code: 9,
errmsg: 'Unknown modifier: $pushAll' }
Someone can help me?
Try using findOneAndUpdate instead.
User.findOneAndUpdate(
{ name: "gino" },
{ $push: { posts: { title: 'post', content: 'content' } } },
{ new: true },
function (err, user) {
if(err) console.log("Something wrong when updating data");
console.log(user);
});
Hope it helps!
If you are using 3.5 MongoDB version or higher, can be an issue with $pushAll, which is deprecated.
I founded an option to work around setting usePushEach to true:
new Schema({ arr: [String] }, { usePushEach: true });
Founded in:
https://github.com/Automattic/mongoose/issues/5574#issuecomment-332290518
Can be useful to use the with .push.

Populate Query Options with Async Waterfall

I'm trying mongoose populate query options but i don't know why the query options doesn't work.
I have user schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema(
{
username: { type: String, required: true },
email: { type: String },
name: { type: String },
address: { type: String }
},
{ timestamps: true }
);
module.exports = mongoose.model('User', UserSchema);
and feed schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const FeedSchema = new Schema(
{
user: { type: Schema.ObjectId, ref: 'User' },
notes: { type: String, required: true },
trx_date: { type: Date },
status: { type: Boolean, Default: true }
},
{ timestamps: true }
);
FeedSchema.set('toObject', { getters: true });
module.exports = mongoose.model('Feed', FeedSchema);
I want to find all feed by user id, i used async waterfall like the following code:
async.waterfall([
function(callback) {
User
.findOne({ 'username': username })
.exec((err, result) => {
if (result) {
callback(null, result);
} else {
callback(err);
}
});
},
function(userid, callback) {
// find user's feed
Feed
.find({})
// .populate('user', {_id: userid._id}) <== this one also doesn't work
.populate({
path: 'user',
match: { '_id': { $in: userid._id } }
})
.exec(callback);
}
], function(err, docs) {
if (err) {
return next(err);
}
console.log(docs);
});
With above code, i got all feeds, and it seems like the query option do not work at all, did i doing it wrong ?
Any help would be appreciate.
Not sure why you are looking to match "after" population when the value of _id is what is already stored in the "user" property "before" you even populate.
As such it's really just a simple "query" condition to .find() instead:
async.waterfall([
(callback) =>
User.findOne({ 'username': username }).exec(callback),
(user, callback) => {
if (!user) callback(new Error('not found')); // throw here if not found
// find user's feed
Feed
.find({ user: user._id })
.populate('user')
.exec(callback);
}
], function(err, docs) {
if (err) {
return next(err);
}
console.log(docs);
});
Keeping in mind of course that the .findOne() is returning the whole document, so you just want the _id property in the new query. Also note that the "juggling" in the initial waterfall function is not necessary. If there is an error then it will "fast fail" to the end callback, or otherwise pass through the result where it is not. Delate "not found" to the next method instead.
Of course this really is not necessary since "Promises" have been around for some time and you really should be using them:
User.findOne({ "username": username })
.then( user => Feed.find({ "user": user._id }).populate('user') )
.then( feeds => /* do something */ )
.catch(err => /* do something with any error */)
Or indeed using $lookup where you MongoDB supports it:
User.aggregate([
{ "$match": { "username": username } },
{ "$lookup": {
"from": Feed.collection.name,
"localField": "_id",
"foreignField": "user",
"as": "feeds"
}}
]).then( user => /* User with feeds in array /* )
Which is a bit different in output, and you could actually change it to look the same with a bit of manipulation, but this should give you the general idea.
Importantly is generally better to let the server do the join rather than issue multiple requests, which increases latency at the very least.

issues with count property of object inside object or array

I try to count property of one object inside another but get a wrong value: I want to count the object inside property of productInfo of order object which is 15 but my function return 46.
router.get("/product", isLoggedIn, function (req, res) {
products.find({}, function (err, products) {
if (err) {
console.log("ERROR!");
} else {
orders.find({
customerInfo: req.user
}, function (err, orders) {
if (err) {
console.log("ERROR!");
} else {
res.render("allProduct", {
data1: _.keys(orders[0].productInfo).length,//here object must counted!
data:products
});
}
});
}
});
});
and here are my models:
var mongoose = require("mongoose");
var order = new mongoose.Schema({
orderNo: Number,
customerInfo: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}],
productInfo: [{
type: mongoose.Schema.Types.ObjectId,
ref: "product"
},]
});
//EXPORT
module.exports = mongoose.model("order", order);
and:
var mongoose =require("mongoose");
var product = new mongoose.Schema({
productNo: Number,
productName: String,
productDes:String,
productPrice:Number,
});
//EXPORT
module.exports = mongoose.model("product",product);
I solve this issues with replace :
_.keys(orders[0].productInfo).length,
with:
orders[0].productInfo.length

How do I $cond a $push/$pull in a MongoDB update with upsert:true

I'm trying to do a push or pull based on a condition, along with an upsert
myCollection.update(
{'id': location},
{
$set: { count },
$setOnInsert: {
id: location,
users: []
},
},
{
$cond: {
if: (increment==1),
then: {$push: { users: userToken }},
else: {$pull: { users: userToken }}
}
},
{'upsert':true},
(err, data) => {
...
I'm trying to DRY this up (which works):
mongo.connect(dbUrl, (err, db) => {
if (err) throw err
let myCollection = db.collection('myCollection')
if(increment==1){
myCollection.update(
{'id': location},
{
$set: { count },
$push: { users: userToken },
$setOnInsert: {
id: location
}
},
{'upsert':true},
(err, data) => {
if (err) throw err
console.log(data);
callback()
db.close()
}
)
}
else{
myCollection.update(
...
$pull: { users: userToken },
...
)
}
})
It's not adding anything to the DB when I have $cond. Where should the $cond be?
$cond is not applicable here but in the aggregation framework. What you need is a pure old native JS conditional statement where you create the update document prior to using it in the update operation, and this of course should be set in a condition block. Consider the following example:
let queryObj = { 'id': location },
usersObj = { 'users': userToken },
updateObj = {
'$set': { count },
'$setOnInsert': queryObj
},
options = { 'upsert': true },
updateOperator = '$pull';
if (increment == 1) updateOperator = '$push';
updateObj[updateOperator] = usersObj;
myCollection.update(queryObj, updateObj, options,
(err, data) => {
if (err) throw err
console.log(data);
callback();
db.close();
}
)

Can't push a json object into array using javascript and mongodb

I have a problem pushing into my Student model data and its schema looks as below:
var StudentSchema = new Schema({
firstName: {
type: String,
trim: true,
default: ''
//validate: [validateLocalStrategyProperty, 'Please fill in your first name']
},
lastName: {
type: String,
trim: true,
default: ''
//validate: [validateLocalStrategyProperty, 'Please fill in your last name']
},
worksnap: {
user: {
type: Object
},
timeEntries : [],
},
timeEntries : []
});
While my javascript code for pushing items looks like this:
Student.findOne({
'worksnap.user.user_id': item.user_id[0]
})
.populate('user')
.exec(function (err, student) {
if (err) {
throw err;
}
//student.timeEntries.push(item); // this works
student.worksnap.timeEntries.push(item); // this does not work
student.save(function (err) {
if (err) {
//return res.status(400).send({
// message: errorHandler.getErrorMessage(err)
//});
} else {
console.log('item inserted...');
}
});
});
As you can see, if I use timeEntries array outside the worksnap object it works fine, it inserts the item as object into that array... I just don't know why it is not working the same being inside worksnap object.
Is there any other option that I can add json objects into an array type in mongo
Thanks
Use .lean()
Documents returned from queries with the lean option enabled are plain JavaScript objects, not MongooseDocuments. They have no save method, getters/setters or other Mongoose magic applied.
Student.findOne({
'worksnap.user.user_id': item.user_id[0]
})
.populate('user')
.lean()//-----Added!
.exec(function(err, student) {
if (err) {
throw err;
}
//student.timeEntries.push(item); // this works
student.worksnap.timeEntries.push(item); // this does not work
student.save(function(err) {
if (err) {
//return res.status(400).send({
// message: errorHandler.getErrorMessage(err)
//});
} else {
console.log('item inserted...');
}
});
});

Categories

Resources