Populate from two collections - node.js

I have 2 Schemas defined for 2 different collections, and I need to populate one of them into the other:
stationmodel.js
var stationSchema = new Schema({
StationName: 'string',
_id: 'number',
Tripcount: [{ type: Schema.Types.ObjectId, ref: 'Tripcount'}]
},
{collection: 'stations'}
);
module.exports = mongoose.model('Station', stationSchema);
tripmodel.js
var tripSchema = new Schema({
_id: { type: Number, ref: 'Station'},
Tripcount: 'number'
},
{collection: 'trips'}
);
module.exports = mongoose.model('Tripcount', tripSchema);
According to the mongoose populate documentation, this is the way to go. I have the problem that "Tripcount" remains as [] when I use Postman to GET the stations.
My DB Structure for the 'stations' collection:
{
"_id": 1,
"StationName": "Station A",
}
And for the 'trips' collection:
{
"_id": 1,
"Tripcount": 6
}
My routes.js:
module.exports = function(app) {
app.get('/stations', function(req,res) {
var query = Station.find().populate('Tripcount');
query.exec(function(err, stations){
if(err)
res.send(err);
res.json(stations);
});
});
};
I can't seem to find the error, maybe someone here can spot a mistake I made.

You are enclosing the mongoose SchemaTypes in single quotes, you either need to reference the SchemaTypes directly when you define a property in your documents which will be cast to its associated SchemaType.
For example, when you define the Tripcount in the tripSchema it should be cast to the Number SchemaType as
var tripSchema = new Schema({
_id: Number,
Tripcount: Number
}, {collection: 'trips'});
module.exports = mongoose.model('Tripcount', tripSchema);
and the station schema
var stationSchema = new Schema({
_id: Number,
StationName: String,
Tripcount: [{ type: Number, ref: 'Tripcount'}]
}, {collection: 'stations'});
module.exports = mongoose.model('Station', stationSchema);
Then in your stations collection, the documents would ideally have the structure
{
"_id": 1,
"StationName": "Station A",
"Tripcount": [1]
}
for the populate method to work, of which when applied as
Station.find().populate('Tripcount').exec(function(err, docs){
if (err) throw err;
console.log(docs);
// prints { "_id": 1, "StationName": "Station A", "Tripcount": [{"_id": 1, Tripcount: 6 }] }
});
Alternative Approach
Another approach that you could take if the station collection does not have Tripcount field is to use the $lookup operator found in the aggregation framework as:
Station.aggregate([
{
"$lookup": {
"from": "tripcollection",
"localField": "_id",
"foreignField": "_id",
"as": "trips"
}
},
{
"$project": {
"StationName": 1,
"trips": { "$arrayElemAt": ["$trips", 0] }
}
},
{
"$project": {
"StationName": 1,
"Tripcount": "$trips.Tripcount"
}
}
]).exec(function(err, docs){
if (err) throw err;
console.log(docs);
// prints [{ "_id": 1, "StationName": "Station A", "Tripcount": 6 }] }
});

Related

How to get array of objects from current collection and data from another collection in same query

I have a users collection. Each user can have multiple friends whose _id values are stored in an array in their user document.
I want to render a list which has a user's list of friend names and under each individual name I want to have a location but the locations are in another document because each user can have multiple locations with no limit on the number, like their visited/favourite places.
These are the 2 queries in isolation but I need to somehow combine them.
This is the query which looks at the user ID values in the user's array of friends and then gets the relevant user info based on the ID.
friends = await User.findOne({ _id: userId })
.populate("friends", "name mobile", null, { sort: { name: 1 } })
.exec();
Then, to get the places for a particular user:
const place = await Place.find({ creator: userId });
So, I basically want to list the friends in a loop, each with their places under their name like:
Joe Soap
- London Bridge
- Eiffel Tower
Bob Builder
- Marienplatz
The data in mongoDb look like this:
Users:
{
"_id": {
"$oid": "5f2d9ec5053d4a754d6790e8"
},
"friends": [{
"$oid": "5f2da51e053e4a754d5790ec"
}, {
"$oid": "5f2da52e053d4a754d6790ed"
}],
"name": "Bob",
"email": "bob#gmail.com",
"created_at": {
"$date": "2020-08-07T18:34:45.781Z"
}
}
Places:
{
"_id": {
"$oid": "5f3695d79864bd6c7c94e38a"
},
"location": {
"latitude": -12.345678,
"longitude": 12.345678
},
"creator": {
"$oid": "5f2da51e053e4a754d5790ec"
},
}
The first join is basically getting data from the array inside the same user collection. The second one is getting data from another collection, places.
UPDATE: almost working
friends = await User.aggregate([
{ $match: { _id: new mongoose.Types.ObjectId(userId) } },
{
$lookup: {
from: "users",
localField: "friends",
foreignField: "_id",
as: "friends_names",
},
},
{ $unwind: "$friends_names" },
{
$lookup: {
from: "places",
localField: "friends",
foreignField: "creator",
as: "friends_places",
},
},
{ $unwind: "$friends_places" },
{
$project: {
"friends_names.name": 1,
"friends_places.saved_at": 1,
},
},
]);
Data returned:
[
{
_id: 5f2d9ec5053d4a754d6790e8,
friends_names: { name: 'Bob' },
friends_places: { saved_at: 2020-08-17T13:40:28.334Z }
},
{
_id: 5f2d9ec5053d4a754d6790e8,
friends_names: { name: 'Natalie' },
friends_places: { saved_at: 2020-08-17T13:40:28.334Z }
}
]
Edit Okay, I believe I have reverse engineered your minimal schema correctly:
const mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/test", { useNewUrlParser: true });
mongoose.set("debug", true);
const db = mongoose.connection;
db.on("error", console.error.bind(console, "connection error:"));
db.once("open", async function () {
await mongoose.connection.db.dropDatabase();
// we're connected!
console.log("Connected");
const userSchema = new mongoose.Schema({
friends: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
name: String,
});
const placesSchema = new mongoose.Schema({
latitude: String,
creator: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
});
const User = mongoose.model("User", userSchema);
const Place = mongoose.model("Place", placesSchema);
const bob = new User({ name: "Bob", friends: [] });
await bob.save();
const natalie = new User({ name: "Natalie", friends: [bob] });
await natalie.save();
//const chris = new User({ name: "Chris", friends: [] });
//await chris.save();
const john = new User({ name: "John", friends: [natalie, bob] });
await john.save();
const place1 = new Place({ latitude: "Place1", creator: bob });
const place3 = new Place({ latitude: "Place3", creator: bob });
const place2 = new Place({ latitude: "Place2", creator: natalie });
await place1.save();
await place2.save();
await place3.save();
await User.find(function (err, users) {
if (err) return console.error(err);
console.log(users);
});
await Place.find(function (err, places) {
if (err) return console.error(err);
//console.log(places);
});
var cc = await mongoose
.model("User")
.aggregate([
{ $match: { _id: john._id } },
{ $unwind: "$friends" },
{
$lookup: {
from: "places",
localField: "friends",
foreignField: "creator",
as: "friends_places",
}
}, { $lookup: {from: 'users', localField: 'friends', foreignField: '_id', as: 'friend_name'} },//, { $include: 'friends' }
{ $unwind: "$friends_places" }, { $unwind: "$friend_name" }//, { $skip: 1}, {$limit: 1}
])
.exec();
console.log(cc);
});
This is the most relevant part:
var cc = await mongoose
.model("User")
.aggregate([
{ $match: { _id: john._id } },
{ $unwind: "$friends" },
{
$lookup: {
from: "places",
localField: "friends",
foreignField: "creator",
as: "friends_places",
}
}, { $lookup: {from: 'users', localField: 'friends', foreignField: '_id', as: 'friend_name'} },//, { $include: 'friends' }
{ $unwind: "$friends_places" }, { $unwind: "$friend_name" }//, { $skip: 1}, {$limit: 1}
])
.exec();
The first unwind: friends 'flattens' the collections initially. So, basically we've got 'userId | friendId' for each user and friend. Then, for each row, we simply look up places created by him ($lookup). Finally, we unwind friends_places because we don't want them to be rendered as [object] in console output. Additionally, there this $match, because we only want to check one user's friends' places. Considering we want to know friend's name as well, we have to do one more join - this is why there's this second $lookup. After that a simple $unwind to get friend's detail and that's it.
Code yields the following:
[ { _id: 5f3aa3a9c6140e3344c78a45,
friends: 5f3aa3a9c6140e3344c78a44,
name: 'John',
__v: 0,
friends_places:
{ _id: 5f3aa3a9c6140e3344c78a48,
latitude: 'Place2',
creator: 5f3aa3a9c6140e3344c78a44,
__v: 0 },
friend_name:
{ _id: 5f3aa3a9c6140e3344c78a44,
friends: [Array],
name: 'Natalie',
__v: 0 } },
{ _id: 5f3aa3a9c6140e3344c78a45,
friends: 5f3aa3a9c6140e3344c78a43,
name: 'John',
__v: 0,
friends_places:
{ _id: 5f3aa3a9c6140e3344c78a46,
latitude: 'Place1',
creator: 5f3aa3a9c6140e3344c78a43,
__v: 0 },
friend_name:
{ _id: 5f3aa3a9c6140e3344c78a43,
friends: [],
name: 'Bob',
__v: 0 } },
{ _id: 5f3aa3a9c6140e3344c78a45,
friends: 5f3aa3a9c6140e3344c78a43,
name: 'John',
__v: 0,
friends_places:
{ _id: 5f3aa3a9c6140e3344c78a47,
latitude: 'Place3',
creator: 5f3aa3a9c6140e3344c78a43,
__v: 0 },
friend_name:
{ _id: 5f3aa3a9c6140e3344c78a43,
friends: [],
name: 'Bob',
__v: 0 } } ]
So, we've got a flat list of John's friends' places.
Important bit: from: places in $lookup is critical, as we have to use MongoDB's name of the collection, not model name 1.

Mongoose: Aggregation return empty value

I'm using aggregation to join 3 collections. But the result of the join is an empty array[].
This is my model for all 3 collections along with queries for aggregate.
Result of console.log(timetable) return []. I;m using references to refer a field in Timetable with corresponding collection.
Timetable Models
var TimetableSchema = new mongoose.Schema ({
timeslot: {
required: true,
'type': String,
},
classroom :{
type: mongoose.Schema.Types.ObjectId,
ref: 'Classroom'
},
subject :{
type: mongoose.Schema.Types.ObjectId,
ref: 'Subject'
},
teacher :{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
day :{
type:String,
required: true,
},
year :{
type:String,
required: true,
},
session :{
type:String,
required:true,
}
})
Classroom Models
var ClassroomSchema = new mongoose.Schema ({
classroom_name: {
type:String,
required:true,
unique: true,
},
classroom_blok:{
type:String,
required:true,
},
classroom_floor: {
type:String,
required:true,
},
});
Subject
var SubjectSchema = new mongoose.Schema ({
subject_id: {
required: true,
'type': String,
'default': shortid.generate
},
subject_name: {
type:String,
required:true,
},
subject_darjah:{
type:String,
required:true,
}
});
Agregate query
router.get('/today_absentee/view/:id',function(req,res){
Teacher.findById(req.session.userId).exec(function (error, user){
if (error){
return next(error);
}else
{
Teacher.find({_id:req.params.id }).exec(function(err, teacher){
if(err)
{
return next(err);
}else
{
Timetable.aggregate([
{
// This is doing the same thing as the previous .find()
$match: { teacher:req.params.id}
},
{
$lookup:{
from: "Classroom", // other table name
localField: "classroom", // name of users table field
foreignField: "_id", // name of userinfo table field
as: "classroom" // alias for userinfo table
}
},
{ $unwind:"$classroom" }, // $unwind used for getting data in object or for one record only
{
$lookup:{
from: "Subject", // other table name
localField: "subject", // name of users table field
foreignField: "_id", // name of userinfo table field
as: "subject" // alias for userinfo table
}
},
{ $unwind:"$subject" }, // $unwind used for getting data in object or for one record only
// define some conditions here
{
$match:{
$and:[{teacher:req.params.id}]
}
},
// define which fields are you want to fetch
{
$project:{
subject_name : "$subject.subject_name",
classroom : "$classroom.classroom_name",
}
}
]).exec(function(err, timetable)
{
// The query output is such that `classroom.classroom_name`
// value is unique for each document
if (err) throw err;
console.log(currentYear);
console.log(timetable);
res.render('creator_content/today_absentee_id',{timetable:timetable, user:user, teacher:teacher});
});
}
});
}
});
});
Models for all 3 collection.
Import mongoose at the top of your file and then use mongoose.Types.ObjectId() when matching against any object id fields in an aggregate query.
I have also used the $addFields aggregation pipeline to add the 2 fields (subject_name, classroom), and then output them in the next stage using $project.
const mongoose = require("mongoose");
router.get('/today_absentee/view/:id', function(req, res) {
Teacher.findById(req.session.userId).exec(function(error, user) {
if (error) {
return next(error);
} else {
Teacher.find({
_id: req.params.id
}).exec(function(err, teacher) {
if (err) {
return next(err);
} else {
Timetable.aggregate([
{
// This is doing the same thing as the previous .find()
$match: {
teacher: mongoose.Types.ObjectId(req.params.id)
}
},
{
$lookup: {
from: "Classroom", // other table name
localField: "classroom", // name of users table field
foreignField: "_id", // name of userinfo table field
as: "classroom" // alias for userinfo table
}
},
{
$unwind: "$classroom"
}, // $unwind used for getting data in object or for one record only
{
$lookup: {
from: "Subject", // other table name
localField: "subject", // name of users table field
foreignField: "_id", // name of userinfo table field
as: "subject" // alias for userinfo table
}
},
{
$unwind: "$subject"
}, // $unwind used for getting data in object or for one record only
// define some conditions here
{
$match: {
$and: [{
teacher: mongoose.Types.ObjectId(req.params.id)
}]
}
},
// Add new field names
{
$addFields: {
subject_name: "$subject.subject_name",
classroom: "$classroom.classroom_name",
}
},
// define which fields are you want to fetch
{
$project: {
subject_name: 1,
classroom: 1,
}
}
]).exec(function(err, timetable) {
// The query output is such that `classroom.classroom_name`
// value is unique for each document
if (err) throw err;
console.log(currentYear);
console.log(timetable);
res.render('creator_content/today_absentee_id', {
timetable: timetable,
user: user,
teacher: teacher
});
});
}
});
}
});
});

How to populate in 3 collection in mongoDB with Mongoose

I have three collections such as User, Program, and `Agenda. Those models as follows.
User Model
const mongoose = require('mongoose');
const UserSchema = mongoose.Schema({
name: {type:String},
email: {type:String}
},{timestamps:true
}
);
module.exports = mongoose.model('User', UserSchema);
Program Model
const mongoose = require('mongoose');
const NoteSchema = mongoose.Schema({
name: {type:String},
timefrom: {type:Date},
timeto: {type:Date},
status: {type:String},
venue: {type:String},
timetype: {type:Number},
userid:{type:mongoose.Schema.Types.ObjectId,ref : 'User', required: true},
logo :{type:String,default: 'programe'}
},{timestamps:true
});
module.exports = mongoose.model('Program', NoteSchema);
Agenda Model
const mongoose = require('mongoose');
const AgendaSchema = mongoose.Schema({
name: {type:String},
timefrom: {type:Date},
timeto: {type:Date},
status: {type:String},
proorder: {type:String},
proid:{type:mongoose.Schema.Types.ObjectId,ref : 'Program', required: true}
},
{timestamps:true}
);
module.exports = mongoose.model('Agenda', AgendaSchema);
Now I only get agenda and program data only.
Agenda Controller
// Retrieve and return all agenda from the database.
exports.findAll = (req, res) => {
Agenda.find()
.populate('proid')
//.populate('userid')
.then(agendas => {
res.send(agendas);
}).catch(err => {
res.status(500).send({
message: err.message || "Some error occurred while retrieving agenda."
});
});
};
When I go to this URL and GET method I want to populate agenda document(done), related program document(done) and related user document(this I want)?
The wanted query as like this
SELECT *
FROM users, programs, agendas
WHERE agendas.proid = programs.id AND programs.userid = users.id
You can either use $lookup aggregation
Agenda.aggregate([
{ "$lookup": {
"from": Program.collection.name,
"let": { "proid": "$proid" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$proid" ] } } },
{ "$lookup": {
"from": User.collection.name,
"let": { "userid": "$userid" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$userid" ] } } },
],
"as": "userid"
}},
{ "$unwind": "$userid" }
],
"as": "proid"
}},
{ "$unwind": "$proid" }
])
Or with populate
Agenda.find()
.populate([{ path: 'proid', populate: { path: 'userid' }}])
Both will give you the same result
You can use .populate() and { populate : { path : 'field' } }.
Example:
Agenda.find(). //Collection 1
populate({path:'proid', //Collection 2
populate:{path:'userid' //Collection 3
}})
.exec();
After the 4 collection, you need to identify the model, if it can't be wrong
Example:
Agenda.find(). //Collection 1
populate({path:'proid', //Collection 2
populate:{path:'userid', //Collection 3
populate:{path:'otherFiel', model: Collection //Collection 4 and more
}}})
.exec();
And to finish, if you want get only some fields of some Collection, you can use the propiertie select
Example:
Agenda.find().
populate({path:'proid', select:'name status venue'
populate:{path:'userid'
}})
.exec();

Filtering a subdocument in mongoose containing an array of objects

Im trying to return a list of ratings from a given user, filtered by category.
var UserSchema = new Schema({
...
ratings: [{
item: { type: Schema.Types.ObjectId, ref: 'Items' },
category: String,
rating: Number
}]
If I do the following, I only get the first rating for that category:
var query = User.findOne(userId, 'ratings');
query.select({ 'ratings': { $elemMatch: { 'category': req.query.category }}});
The following also returns only the first rating:
var query = User.find();
query.where({'_id': userId, 'ratings.category': req.query.category});
query.select({ 'ratings.$': 1 });
I was able to aggregate the correct results with the following, however, I dont think that'll work since I cant populate after an aggregation.
var query = User.aggregate(
{ $match: { _id: userId }},
{ $project: { _id: 1, ratings: 1 }},
{ $unwind: '$ratings' },
{ $match: { 'ratings.category': req.query.category }}
);
You could do the population after your aggregation, something like this:
var pipeline = [
{ $match: { _id: userId }},
{ $project: { _id: 1, ratings: 1 }},
{ $unwind: '$ratings' },
{ $match: { 'ratings.category': req.query.category } }
];
User.aggregate(pipeline, function (err, result){
User.populate(result, {path: "ratings.item"}, callback);
});

How to get a rating average in Mongoose / node

I have a star rating directive working on the front end for angularjs, I can save a rating to the rating collection.
here is my rating schema / model:
var mongoose = require('mongoose');
module.exports = mongoose.model('Rating', {
bourbonId: {type: mongoose.Schema.ObjectId, ref: 'Bourbon'},
userId: {type: mongoose.Schema.ObjectId, ref: 'User'},
rating: {type: Number, required: true},
ratingId : {type: mongoose.Schema.ObjectId}
});
Here is the item that I need an average rating for:
'use strict';
var mongoose = require('mongoose'),
BourbonSchema = null;
module.exports = mongoose.model('Bourbon', {
BourbonId: {type: mongoose.Schema.ObjectId},
name: {type: String, required: true},
blog: {type: String, required: true},
photo: {type: String, required: true},
ratings: {type: mongoose.Schema.ObjectId, ref: 'Rating'},
rating: {type: Number}
});
var Bourbon = mongoose.model('Bourbon', BourbonSchema);
module.exports = Bourbon;
I need to find a way to match by bourbon ID. From looking at stack overflow, it seems using an aggregate function may be the way to go. stack overflow link
Here is the current broken code I have in my controller. I know it's way off, along with failed attempts that i've had using async.map to try and solve this as well:
'use strict';
var Bourbon = require('../../../models/bourbon'),
Rating = require('../../../models/rating');
//async = require('async');
module.exports = {
description: 'Get Bourbons',
notes: 'Get Bourbons',
tags:['bourbons'],
handler: function(request, reply){
Bourbon.find(function(err, bourbons){
Bourbon.findOne(id, 'Rating', function(err, bourbon){
Rating.aggregate([
{$match: {bourbonId: {$in: bourbon.ratings}}},
{$group: {bourbonId: bourbon._id, average: {$avg: '$rating'}}}
], function(err, result){
bourbon.rating = result;
reply({bourbons:bourbons});
console.log('Bourbs', bourbons);
});
});
});
}
};
any help would be much appreciated. Beating my head against a brick wall, just throwing random code out now. ..
here's what i've implemented:
model:
'use strict';
var mongoose = require('mongoose'),
BourbonResultSchema = null;
module.exports = mongoose.model('BourbonResult', {
_Id: {type: mongoose.Schema.ObjectId, 'ref': 'Bourbon'},
avgRating: {type: Number}
});
var BourbonResult = mongoose.model('BourbonResult', BourbonResultSchema, null);
module.exports = BourbonResult;
controller:
'use strict';
var Bourbon = require('../../../models/bourbon'),
Rating = require('../../../models/rating'),
BourbonResult = require('../../../models/bourbonResult');
//async = require('async');
module.exports = {
description: 'Get Bourbons',
notes: 'Get Bourbons',
tags:['bourbons'],
handler: function(request, reply){
Rating.aggregate(
[
{'$group':{
'_id': '$bourbonId',
'avgRating': {'$avg': '$rating'}
}}
],
function(err,bourbons){
// Map plain results to mongoose document objects
bourbons = bourbons.map(function(result){
return new BourbonResult(result);
});
Bourbon.populate(bourbons,{'path': '_id'},function(err,bourbons){
reply({bourbons:bourbons});
console.log('BourbsRESSSSSS', JSON.stringify(bourbons, undefined, 2));
});
}
);
}
};
here's what I get back from the consolelog:
BourbsRESSSSSS [ { _id:
{ _id: 54acf382894ee2bcdebbc7f5,
name: 'example2',
photo: 'http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpg',
blog: 'example2',
__v: 0 },
avgRating: 3.3333333333333335 },
{ _id:
{ _id: 54a77e0fe63c850000f1269c,
name: 'example',
photo: 'http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpg',
blog: 'example',
__v: 0 },
avgRating: 3 } ]
========================================================================
Perfect!
If what you are trying to do is list the "average" rating against each "Bourbon" here in your output there are probably a couple of approaches. But one of the cleaner ways would be to utilize mongoose populate on a special object model representing the structure of the results from aggregation.
You don't appearhave any other "types" of "Ratings" here other than for "bourbon", so it stands to reason that you just want to aggregate the whole collection.
// Set up a schema and model to match result structure
var bourbonResultSchema = new Schema({
"_id": { "type": Schema.Types.ObjectId, "ref": "Bourbon" },
"avgRating": Number
});
// The "null" for the collection is because there will not be any physical storage
var BourbonResult = mongoose.model( "BourbonResult", bourbonResultSchema, null );
// Aggregate an mapping code
Rating.aggregate(
[
{ "$group": {
"_id": "$bourbonId",
"avgRating": { "$avg": { "$ifNull": ["$rating",0 ] } }
}}
],
function(err,results) {
if (err) throw err;
// Map plain results to mongoose document objects
results = results.map(function(result) {
return new BourbonResult(result);
});
Bourbon.populate(results,{ "path": "_id" },function(err,results) {
if (err) throw err;
reply(results);
console.log( JSON.stringify( results, undefined, 2 ) );
})
}
);
So you define a schema and model that will match the structure of the results returned from aggregate. This is done so you can call .populate() later.
The results returned from aggregate are not mongoose documents, but just plain objects. You then cast all the results to BourbonResult objects by passing them through the .map() method in order to return an array of BourbonResult.
Since these are not mongoose documents, you can call the model method of .populate(), which takes an array of mongoose documents as the first argument. The second "options" argument tells the method which field path to use for the popluation, which is _id as defined earlier to reference the Bourbon model.
In the callback to .populate() the returned results merge both the average score returned from aggregation and the complete Bourbon object within the _id field. If you really wished, you could also run further .populate() statements over each Bourbon object in order to pull in any of it's references. A bit more complicated but possible.
As a note, the "bourbonId" field in the "Bourbon" model is probably a bit redundant. MongoDB always has a unique _id field present and the actual value used by referenced object links is that field unless specified otherwise. Even if you needed to define a reference there as I have done for BourbonResult then you can do that as well.
A complete listing with amended schema examples:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
var userSchema = new Schema({
"name": String
});
var ratingSchema = new Schema({
"bourbonId": { "type": Schema.Types.ObjectId, "ref": "Bourbon" },
"userId": { "type": Schema.Types.ObjectId, "ref": "User" },
"rating": { "type": Number, "required": true }
});
var bourbonSchema = new Schema({
"name": { "type": String, "required": true },
"blog": { "type": String, "required": true },
"photo": { "type": String, "required": true },
"ratings": [{ "type": Schema.Types.ObjectId, "ref": "Rating" }],
"rating": { "type": Number }
});
var bourbonResultSchema = new Schema({
"_id": { "type": Schema.Types.ObjectId },
"avgRating": Number
});
var User = mongoose.model( "User", userSchema ),
Rating = mongoose.model( "Rating", ratingSchema ),
Bourbon = mongoose.model( "Bourbon", bourbonSchema ),
BourbonResult = mongoose.model(
"BourbonResult", bourbonResultSchema, null );
mongoose.connect("mongodb://localhost/bourbon");
async.waterfall(
[
function(callback) {
async.each([User,Rating,Bourbon],function(model,callback) {
model.remove({},callback);
},
function(err) {
callback(err);
});
},
function(callback) {
Bourbon.create({
"name": 'test',
"blog": 'test',
"photo": 'test'
},callback);
},
function(bourbon,callback) {
User.create({ "name": 'ted' },function(err,user) {
if (err) callback(err);
Rating.create({
"bourbonId": bourbon,
"userId": user,
"rating": 5
},function(err,rating1) {
callback(err,user,bourbon,rating1)
});
});
},
function(user,bourbon,rating1,callback) {
Rating.create({
"bourbonId": bourbon,
"userId": user,
"rating": 7
},function(err,rating2) {
callback(err,bourbon,rating1,rating2);
});
},
function(bourbon,rating1,rating2,callback) {
Bourbon.findById(bourbon.id,function(err,bourbon) {
bourbon.ratings.push(rating1,rating2);
bourbon.save(function(err,bourbon) {
callback(err)
});
});
},
function(callback) {
Rating.aggregate(
[
{ "$group": {
"_id": "$bourbonId",
"avgRating": { "$avg": { "$ifNull": ["$rating", 0 ] } }
}},
],
function(err,results) {
console.log(results);
results = results.map(function(result) {
return new BourbonResult(result);
});
Bourbon.populate(
results,
{ "path": "_id" },
function(err,results) {
console.log(results);
callback(err);
}
)
}
);
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
)
Gives output:
[ { _id: 54af7581efc755470845005c, avgRating: 6 } ]
[ { _id:
{ _id: 54af7581efc755470845005c,
name: 'test',
blog: 'test',
photo: 'test',
__v: 1,
ratings: [ 54af7581efc755470845005e, 54af7581efc755470845005f ] },
avgRating: 6 } ]

Resources