These are my schemas (Topic is parent and contains a list of 'Thought's):
var TopicSchema = new mongoose.Schema({
title: { type: String, unique: true },
category: String,
thoughts: [ThoughtSchema]
}, {
timestamps: true,
toObject: {virtuals: true},
toJSON: {virtuals: true}
});
var ThoughtSchema = new mongoose.Schema({
text: String,
author: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
votes:[{
_id:false,
voter: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
up: Boolean,
date: {type: Date, default: Date.now}
}]
}, {
timestamps: true,
toObject: {virtuals: true},
toJSON: {virtuals: true}
});
....
I am trying to read the thought's author and change my get Topic api like this:
...
var cursor = Topic.find(query).populate({
path: 'thoughts',
populate: {
path: 'author',
model: 'User'
}
}).sort({popularity : -1, date: -1});
return cursor.exec()
.then(respondWithResult(res))
.catch(handleError(res));
...
But the author is null.. i also do not get any error in the console. What is wrong here?
Edit: Actually i do not need the Thought as a schema, it does not have its own collection in database. It will be saved in topics. But in order to use timestamps option with thoughts, i needed to extract its contents to a new local schema ThoughtSchema. But i have now defined the contents of thoughtSchema directly in the thoughts array of topics, it still does not work.
Edit2: This is the cursor object just before it is executed. Unfortunately i cannot debug in Webstorm, this is a screenshot from node inspector:
Did you try using Model.populate?
Topic.find(query).populate('thoughts')
.sort({popularity : -1, date: -1})
.exec(function(err, docs) {
// Multiple population per level
if(err) return callback(err);
Thought.populate(docs, {
path: 'thoughts.author',
model: 'User'
},
function(err, populatedDocs) {
if(err) return callback(err);
console.log(populatedDocs);
});
});
UPDATE:
You can try with deep populate like this:
Topic.find(query).populate({
path: 'thoughts',
populate: {
path: 'author',
model: 'User'
}
})
.sort({popularity : -1, date: -1})
.exec(function(err, docs) {
if(err) return callback(err);
console.log(docs);
});
How about
Topic.find(query).populate('thoughts')
.sort({popularity : -1, date: -1})
.exec(function(err, docs) {
// Multiple population per level
if(err) return callback(err);
Topic.populate(docs, {
path: 'thoughts.author',
model: 'User'
},
function(err, populatedDocs) {
if(err) return callback(err);
console.log(populatedDocs);
});
});
These are the schemas :
var TopicSchema = new mongoose.Schema({
title: { type: String, unique: true },
category: String,
thoughts: [ThoughtSchema]
}, {
timestamps: true,
toObject: {virtuals: true},
toJSON: {virtuals: true}
});
var ThoughtSchema = new mongoose.Schema({
text: String,
author: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
votes:[{
_id:false,
voter: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
up: Boolean,
date: {type: Date, default: Date.now}
}]
}, {
timestamps: true,
toObject: {virtuals: true},
toJSON: {virtuals: true}
});
Did you try Aggregation Instead of Populate. Aggregate Makes much easier for populating the embedded data using $lookup. Try the below code.
UPDATE
Topic.aggregate([{$unwind: "$thoughts"},{ $lookup: {from: 'users', localField: 'thoughts.author', foreignField: '_id', as: 'thoughts.author'}},{$sort:{{popularity : -1, date: -1}}}],function(err,topics){
console.log(topics) // `topics` is a cursor.
// Perform Other operations here.
})
Explanation:
$unwind: Deconstructs an array field from the input documents to output a document for each element.
$lookup: The $lookup stage does an equality match between a field from the input documents with a field from the documents of the “joined” collection. The lookup does the population's job.
$lookup works like
from : this says from which collection the data needs to be populated.(users in this scenario).
localField : this is the local field which needs to be populated. (thoughts.author in this scenario).
foreignField : this is the foreign field present in the collection from which data needs to be populated (_id field in users collection in this scenario).
as : this is the field as what you want to display the joined value as. (this will project thoughts.author's id as thoughts.author document).
Hope this works.
Related
I have a circle model in my project:
var circleSchema = new Schema({
//circleId: {type: String, unique: true, required: true},
patientID: {type: Schema.Types.ObjectId, ref: "patient"},
circleName: String,
caregivers: [{type: Schema.Types.ObjectId}],
accessLevel: Schema.Types.Mixed
});
circleSchema.virtual('caregiver_details',{
ref: 'caregiver',
localField: 'caregivers',
foreignField: 'userId'
});
caregiver schema:
var cargiverSchema = new Schema({
userId: {type: Schema.ObjectId, unique: true}, //objectId of user document
detailId: {type: Schema.ObjectId, ref: "contactDetails"},
facialId: {type: Schema.ObjectId, ref: "facialLibrary"}, //single image will be enough when using AWS rekognition
circleId: [{type: Schema.Types.ObjectId, ref: "circle"}], //multiple circles can be present array of object id
});
Sample Object:
{
"_id" : ObjectId("58cf4832a96e0e3d9cec6918"),
"patientID" : ObjectId("58fea8ce91f54540c4afa3b4"),
"circleName" : "circle1",
"caregivers" : [
ObjectId("58fea81791f54540c4afa3b3"),
ObjectId("58fea7ca91f54540c4afa3b2")
],
"accessLevel" : {
"location\"" : true,
"notes" : false,
"vitals" : true
}
}
I have tried virtual populate for mongoosejs but I am unable to get it to work.
This seems to be the exact same problem: https://github.com/Automattic/mongoose/issues/4585
circle.find({"patientID": req.user._id}).populate('caregivers').exec(function(err, items){
if(err){console.log(err); return next(err) }
res.json(200,items);
});
I am only getting the object id's in the result. It is not getting populated.
Figured out what the problem was.
By default, the virtual fields are not included in the output.
After adding this in circle schema:
circleSchema.virtual('caregiver_details',{
ref: 'caregiver',
localField: 'caregivers',
foreignField: 'userId'
});
circleSchema.set('toObject', { virtuals: true });
circleSchema.set('toJSON', { virtuals: true });
It now works perfectly.
If you use expressJs res.json method it's ehough to add only:
yourSchema.set('toJSON', { virtuals: true });
Or you can directly use toJSON/toObject:
doc.toObject({ virtuals: true })) // or doc.toJSON({ virtuals: true }))
I am very new to MEAN stack and i am trying to learn async.
I'm trying to combine two collections from the mongodb using async
and applied this iterate over a collection, perform an async task for each item, i'm a trying to learn the simplest and efficient way of doing this simple tasks so it will be easy to understand.
var OrderSchema = new mongoose.Schema({
menu_id: {type:mongoose.Schema.Types.ObjectId, ref: 'Foods'},
menu_name: {type:String,required:false},
customer_id: {type:String,required: true,},
customer_name:{type:String, required: false},
table_no:{type:String,required:true},
cooking:{type:Boolean, require:false, default:false},
ready:{type:Boolean,default:false},
served:{type:Boolean,default:false},
paid:{type:Boolean, default:false},
price: {type:Number, default:0},
quantity: {type:Number,default:0},
created_at: { type: Date, default: Date.now }
}
Payment Model
var mongoose = require('mongoose');
var PaymentSchema = new mongoose.Schema({
order_number: {type:String, required: true, index: true},
order_id: {type:mongoose.Schema.Types.ObjectId, ref: 'Orders'},
date: { type: Date, default: Date.now },
customer_id: {type:mongoose.Schema.Types.ObjectId, ref: 'User'},
amount : { type: Number, required:true },
company_id: {type:mongoose.Schema.Types.ObjectId, ref: 'Company'}
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
module.exports = mongoose.model('Payments', PaymentSchema);
Here is my Code
var data = req.body;
var calls = [];
var local_orders = [];
var OrderModel = require('../models/Order');
var PaymentModel = require('../models/Payment');
OrderModel.find({'table_no': data.table_no}, function(err,orders){
async.forEach(orders, function(vorders, callback){
PaymentModel.find({order_id:vorders.id}, function(err, payments){
vorders.payments = 'payments';
local_orders.push(vorders)
});
return callback(null, local_orders);
}, function(err,local_orders){
if(err){
res.status('500').send(err);
}
res.send(local_orders)
});
})
I am expecting to receive a JSON Object like this, but i'm getting is undefined.
[{ menu_id: {type:mongoose.Schema.Types.ObjectId, ref: 'Foods'},
menu_name: {type:String,required:false},
user_id: {type:String,required: true,},
customer_name:{type:String, required: false},
table_no:{type:String,required:true},
cooking:{type:Boolean, require:false, default:false},
ready:{type:Boolean,default:false},
served:{type:Boolean,default:false},
paid:{type:Boolean, default:false},
price: {type:Number, default:0},
quantity: {type:Number,default:0},
created_at: { type: Date, default: Date.now },
payments : [{ payment1 },{ payment2 }
},...]
Please comment if you need more clarification or something is missing. Thank you! Cheers!
The simplest and most efficient way of doing this simple task is by using the aggregation framework where you can leverage mongo's native operators like $match to filter the document stream to allow only matching documents to pass unmodified into the next pipeline stage and $lookup to perform a left outer join to the payment collection in the same database to filter in documents from the "joined" collection for processing:
var data = req.body;
OrderModel.aggregate([
{ "$match": { "table_no": data.table_no } },
{
"$lookup": {
"from": "payments",
"localField": "_id",
"foreignField": "order_id",
"as": "payments"
}
}
]).exec(function (err, result){
if (err){
res.status('500').send(err);
}
res.send(result)
});
However, as it stands your code is failing here
PaymentModel.find({ order_id: vorders.id }, function(err, payments){
since vorders object does not have any id key but _id, so that should be
PaymentModel.find({ "order_id": vorders._id }, function(err, payments){
I have a Mongoose model which have paths which need to be populated :
var adsSchema = new Schema({
price: { type: Number, min: 0, required: true },
author: { type: ObjectId, ref: 'Users', required: true },
title: { type: String, required: true },
date: { type: Date, default: Date.now },
offers: [{
price: { type: Number, required: true },
date: { type: Date, default: Date.now },
offerBy: { type: ObjectId, ref: 'Users', required: true }
}],
category: { type: ObjectId },
images: [{
type: ObjectId,
ref: 'Images'
}],
videos: [String]
});
In some GET request, I need to populated numerous fields as I was saying, in particular offers with a sorting by 'price' by ascending.
As the documentation of Mongoose is showing there (http://mongoosejs.com/docs/api.html#query_Query-populate), you can sort by a subpath.
My problem is that it isn't the case.
There is my code which is doing it :
Ads.findOne({ _id: adId })
.populate({ path: 'author', select: 'firstName lastName' })
.populate('images')
.populate({ path: 'offers', options: { sort: { 'price': -1 } })
.populate({ path: 'category', select: 'name' })
.exec(function (err, ad) {
if (err)
deferred.reject({ status: 500, message: 'Database error.' });
else if (!ad)
deferred.reject({ status: 500, message: 'The ad doesn\'t exist!.' });
else {
deferred.resolve(ad.toJSON());
}
})
I have read as much as possible questions/answers giving here or in the Mongoose mailing-list :
https://stackoverflow.com/a/19450541/6247732
I know it's not possible to sort documents results from subdocument result, OK. But I just want to sort that subdocument only, and only this one. Some responses here seems to say it's possible :
https://stackoverflow.com/a/31757600/6247732
https://stackoverflow.com/a/16353424/6247732
I have 2 questions around it :
Is it possible (as the Mongoose documentation is written) to sort a subdocument during the population ?
Is it linked to the fact I didn't just put a ObjectId linked to an other Schema ?
Thanks for your answer ;)
Personally, I do not use 'populate' as possible as I can. I had experienced many trouble in the past. So, I do not know how to sort while populating.
Instead of using populate, you can use $lookup method on aggregation and you can easily sort any field. Its almost same way with 'population'.
Ads.aggregate([
{
$lookup: {
from: 'Users',
localField: 'author',
foreignField: '_id',
as: 'authorName'
}
},
{
$lookup: {
from: 'Images',
localField: 'images',
foreignField: '_id',
as: 'image'
}
},
{
$lookup: {
from: 'categories',
localField: 'category',
foreignField: '_id',
as: 'category'
}
},
{
$project: {
'authorName.firstName': 1,
'image.imagePath': 1,
'category.categoryName': 1,
'_id': 0
}
},
{
$sort : { 'authorName.firstName' : -1}
}
]).exec(function(err, adss) {
});
I did not check all fields properly. Please implement this way to your model and hope this can give you some idea. Good luck
I found the solution, and it was the response of my second question.
When you are writing a subdocument in your schema like in my question, you are not creating a "relation" between one Model and an other one. Which means that the populate method doesn't work at all.
You have to make a reference to an ObjectId and the model associated to be able to use populate, and so the options which offers you Mongoose. This is an example :
var adsSchema = new Schema({
price: { type: Number, min: 0, required: true },
author: { type: ObjectId, ref: 'Users', required: true },
offers: [{
type: ObjectId,
ref: 'Offers'
}],
videos: [String]
});
Now, the .populate method is working, because Mongoose (and so MongoDB) can perform the subquery on an other Collection and sort it. It wasn't the case before because it was in the same Model, so Mongoose doesn't perform a subquery on the same Collection (as I seems to understand it).
'use strict';
var mongoose = require('mongoose'),
ItemSchema = null;
module.exports = mongoose.model('Item', {
name: {type: String, required: true},
comments: [{type: mongoose.Schema.ObjectId, ref: 'Comment'}],
rating : {type: Number}
});
'use strict';
var mongoose = require('mongoose');
module.exports = mongoose.model('Comment', {
ItemId: {type: mongoose.Schema.ObjectId, ref: 'Item'},
userId: {type: mongoose.Schema.ObjectId, ref: 'User'},
text: {type: String, required: true},
createdAt: {type: Date, required: true, default: Date.now},
rating : {type: Number, required: true},
votes: {type: Number, default: 0}
});
var Item = mongoose.model('Item', ItemSchema);
module.exports = Item;
schemas are above.. the match is not working how I think it should. Really having a hard time emulating these joins.. I'm just trying to return one item with it's comments and the average rating given for it within the comments..
Item.findOne({_id : request.params.itemId} , 'comments',function(err, item){
Comment.aggregate([
{$match: {_id: {$in: item.comments}}},
{$group: {_id: '$itemId', avgRating: {$avg: '$rating'}}}], function(err, result){
reply(result);
});
});
}
If I take out the match, it returns all items with avgRating... I'm implementing something wrong in the $match.
here's what I've tried with removing the findOne:
Comment.aggregate([
{$match: {itemId : request.params.itemId}},
{$group: {_id: '$itemId', avgRating: {$avg: '$rating'}}}], function(err, result){
reply(result);
console.log('BBBBBB', result);
});
Not sure if this is what you meant by removing the findOne, but this is also not working.. request.params is returning the correct ID. it's definitely a valid field in comments, there is a comment seeded in the DB with this matching ID...
can someone please help me with population of this schema? I need to populate array of Staff by their userId.
var PlaceSchema = new Schema ({
name: { type: String, required: true, trim: true },
permalink: { type: String },
country: { type: String, required: true },
...long story :D...
staff: [staffSchema],
admins: [adminSchema],
masterPlace:{ type: Boolean },
images: []
});
var staffSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account' },
role: { type: Number }
});
var adminSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account'}
})
var Places = mongoose.model('Places', PlaceSchema);
I tried to use this query, but without success.
Places.findOne({'_id' : placeId}).populate('staff.userId').exec(function(err, doc){
console.log(doc);
});
Polpulation is intended as a method for "pulling in" information from the related models in the collection. So rather than specifying a related field "directly", instead reference the related fields so the document appears to have all of those sub-documents embedded in the response:
Places.findOne({'_id' : placeId}).populate('staff','_id')
.exec(function(err, doc){
console.log(doc);
});
The second argument just returns the field that you want. So it "filters" the response.
There is more information on populate in the documentation.