Mongoose NodeJS Schema with array of ref's - node.js

I know there is allot's of answers about it but still I didn't quite get the idea.
I have CourseSchema:
const CourseSchema = new Schema({
course_name: String,
course_number: {type: String, unique : true },
enrolledStudents:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'Student' }]
});
And a StudentSchema:
const StudentSchema = new Schema({
first_name: String,
last_name: String,
enrolledCourses:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'CourseSchema'
}]
});
I want to reffer enrolledStudents at CourseSchema with a student, and enrolledCourses at StudentSchema with a course.
router.post('/addStudentToCourse', function (req, res) {
Course.findById(req.params.courseId, function(err, course){
course.enrolledStudents.push(Student.findById(req.params.studentId, function(error, student){
student.enrolledCourses.push(course).save();
})).save();
});
});
but when posting I get an error:
TypeError: Cannot read property 'enrolledStudents' of null
Ok so after readying Query-populate I did that:
router.post('/addStudentToCourse', function (req, res) {
Course.
findOne({ _id : req.body.courseId }).
populate({
path: 'enrolledStudents'
, match: { _id : req.body.studentId }
}).
exec(function (err, course) {
if (err) return handleError(err);
console.log('The course name is %s', course.course_name);
});
});
And when i'm hitting POST on postman I get on the console:
The course name is intro for cs
but it is loading for ever and later on console I get:
POST /courses/addStudentToCourse - - ms - -

You are missing the populate instruction. For example:
see more about it here
Course.
findOne({ courseId : req.params.courseId }).
populate('enrolledStudents').
exec(function (err, course) {
if (err) return handleError(err);
console.log('The course name is %s', course.name);
});
It is working by using the ref field that "knows" how to populate withput using the push syntax. it is like a foreign key population.
Just call the populate method on the query and an array of documents will be returned in place of the original _ids. you can learn more on the internals of the populate methods in the official docs

Related

Store value of a subquery - mongoose

What im doing:
When I call getData() the backend server .find() all my data.
My documents:
My test document has an _id a name and stuff fields. The stuff field contains the _id to the data document.
My data document has an _id and a age field
My goal:
When I send the data to the frontend I donĀ“t want the stuff field to appear with the _id, I want it to appear with the age field from the correspondingdata.
What I have:
router.route('/data').get((req, res) => {
Test.find((err, aval) => {
if (err)
console.log(err);
else{
var result = [];
aval.forEach(e => {
var age;
// Get the age, only 1
Data.findById(e.stuff, 'age', function (err, a) {
age = a.age;
});
result.push({name: e.name, age: age});
});
res.json(result);
}
});
});
I find all the test documents then, for each one of them, I find the age and put the result in the array. Finaly I send the result array.
My problem:
The age field on my result array is always undefined, why? Any solutions?
UPDATE 1 - The schemas
The test schema
var TestSchema = new Schema(
{
stuff: {type: Schema.Types.ObjectId, ref: 'Data', required: true},
name: {type: String, required: true}
}
);
The data schema
var DataSchema = new Schema(
{
age: {type: Number, required: true}
}
);
router.route('/data').get((req, res) => {
Test.find({})
.populate('stuff')
.exec((err, aval) => {
if (err) console.log(err);
res.json(aval);
});
});
Mongoose model has a populate property that uses the value in the model attribute definition to get data matching the _id from another model.
It's a scop problem with your code try this out :
Data.findById(e.stuff, 'age', function (err, a) {
result.push({name: e.name, age: a.age});
});
But as a better solution think to use the Aggregation Framework

Using $or in a FindOne query - MongoDB

UPDATE: Changed if (!result.length) to if (!result) as I'm using .findOne and that seems to be working so far. If you spot anything else that can be improved within the code snippet that would be awesome!
Just starting to learn Node JS and MongoDB (using Mongoose) so excuse me if I'm totally out of context.
Im trying to find a row in my MongoDB with the following query:
exports.findById = function(req, res) {
var id = req.params.id;
Team.findOne({'teamid':id, $or:[{'creator':req.user.id}, {userlist: { $in : [req.user.id]}}]}, function(err, result) {
if (err) console.log(err);
if (!result.length)
res.redirect('/');
else
res.render('team', { team : result.teamid });
});
};
I want to retrieve a row that has the field teamid equal to id as well as to check if the field creator is equal to req.user.id OR if req.user.id is in the userlist field. I am expecting only one result from the above query.
Note that this query works just fine, but I just need to look inside the userlist array:
Team.findOne({'teamid':id, 'creator':req.user.id}, function(err, result) {...
And finally the Team schema
var Team = new Schema({
team_name: { type: String, required: true, trim: true},
teamid: { type: String },
creator: String,
created_at: Date,
userlist: Array
});
Any help to figure out what's the problem is greatly appreciated!

mongoose find a document by reference property

I have a first model Person:
var personSchema = new Schema({
firstname: String,
name: String
});
module.exports = mongoose.model('Person', personSchema);
And a second model Couple:
var coupleSchema = new Schema({
person1: [{ type: Schema.Types.ObjectId, ref: 'Person' }],
person2: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
module.exports = mongoose.model('Couple', coupleSchema);
I find a couple with a person ObjectId:
Couple.find({
'person1': req.params.objectid
})
.populate({
path: 'person1 person2'
})
.exec(function (err, couple) {
if (err)
res.send(err);
res.json(couple)
});
But I would like to find a couple by giving a firstname and not an ObjectId of a Person, something like that:
Couple.find({
'person1.firstname': "Bob"
})
.populate({
path: 'person1 person2'
})
.exec(function (err, couple) {
if (err)
res.send(err);
res.json(couple)
});
But it is always empty...
Anyway to solve this?
Thank you for any feedback.
EDIT
I just implemented the answer:
Let's see my Couple model now:
var Person = require('mongoose').model('Person');
var coupleSchema = new Schema({
person1 : [{ type: Schema.Types.ObjectId, ref: 'Person' }],
person2 : [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
coupleSchema.statics.findByUsername = function (username, callback) {
var query = this.findOne()
Person.findOne({'firstname': username}, function (error, person) {
query.where(
{person1: person._id}
).exec(callback);
})
return query
}
module.exports = mongoose.model('Couple', coupleSchema);
With this usage:
Couple.findByUsername(req.params.username, function (err, couple) {
if(err)
res.send(err);
res.json(couple);
});
That works! Thank you for your answer and edits.
In your couple model, person1 is an ObjectID (I know you know it), so it has no obviously no property .firstname.
Actually the best way to achieve this, is to find the user by it's first name, and then query the couple, with the id of the user.
This method could/should stand in the couple model as a static method (simplified code sample):
couple.statics.findByPersonFirstname = function (firstname, callback) {
var query = this.findOne()
Person.findOne({firstname: firstname}, function (error, person) {
query.where($or: [
{person1: person._id},
{person1: person._id}
]).exec(callback);
})
return query
}
Just like this exemple.
EDIT: Also note that the ref must be the _id (so you couldn't store with the first name, that would be a bad idea anyway).
Considering your edit:
Person._id is maybe a String and the reference is an ObjectId, if so, try:
{person1: mongoose.Types.ObjectId(Person._id)}
Also, your variable is person and not Person. Try to log person to see if you get something.
Finally, my code sample is really simple, don't forget to handle errors and all (see the link I gave you above, which is complete).

Nested query with mongoose

I have three models: User, Post and Comment
var User = new Schema({
name: String,
email: String,
password: String // obviously encrypted
});
var Post = new Schema({
title: String,
author: { type: Schema.ObjectId, ref: 'User' }
});
var Comment = new Schema({
text: String,
post: { type: Schema.ObjectId, ref: 'Post' },
author: { type: Schema.ObjectId, ref: 'User' }
});
I need to get all posts in which the user has commented.
I know it should be a very simple and common use case, but right now I can't figure a way to make the query without multiple calls and manually iterating the results.
I've been thinking of adding a comments field to the Post schema (which I'd prefer to avoid) and make something like:
Post.find()
.populate({ path: 'comments', match: { author: user } })
.exec(function (err, posts) {
console.log(posts);
});
Any clues without modifying my original schemas?
Thanks
You have basically a couple of approaches to solving this.
1) Without populating. This uses promises with multiple calls. First query the Comment model for the particular user, then in the callback returned use the post ids in the comments to get the posts. You can use the promises like this:
var promise = Comment.find({ "author": userId }).select("post").exec();
promise.then(function (comments) {
var postIds = comments.map(function (c) {
return c.post;
});
return Post.find({ "_id": { "$in": postIds }).exec();
}).then(function (posts) {
// do something with the posts here
console.log(posts);
}).then(null, function (err) {
// handle error here
});
2) Using populate. Query the Comment model for a particular user using the given userId, select just the post field you want and populate it:
var query = Comment.find({ "author": userId });
query.select("post").populate("post");
query.exec(function(err, results){
console.log(results);
var posts = results.map(function (r) { return r.post; });
console.log(posts);
});

Mongoose error - elemMatch() must be used after where() when called with these arguments

I have the following schemas:
UserSchema = new Schema({
'username': { type: String, validate: [validatePresenceOf, 'a username is required'], index: { unique: true } },
'hashed_password': String,
'salt': String,
'locations': [LocationSchema]
});
LocationSchema = new Schema({
'lat': Number,
'lng': Number,
'address': { type: String, validate: [validatePresenceOf, 'The address is required in order to add a new location.'] },
'name': String
});
I'm trying to find a particular single location document by it's id. To do this I'm attempting to query the users collection items by location key, which is an array of location documents. My query looks like this:
var query = User.find({"locations.$": 1})
.where()
.elemMatch({locations: {_id : ObjectId('531283690315992f05bcdc98')}})
.exec(function(err, data){
console.log(err);
console.log(data);
});
When it runs I get the following error:
Error: elemMatch() must be used after where() when called with these arguments
What does this mean? I can't seem to find a good explanation.
Forgot to mention, I can get the data I want from the mongo shell by running the following: db.users.find({locations: {$elemMatch : {_id : ObjectId('531283690315992f05bcdc98')}}}, {"locations.$": 1});
You need to provide a path to your where call and reorder things a bit:
User.find()
.where('locations')
.elemMatch({_id : ObjectId('531283690315992f05bcdc98')})
.select({'locations.$': 1})
.exec(function(err, data){
console.log(err);
console.log(data);
});
But you could also simplify it somewhat as you don't need to use $elemMatch here and you can let Mongoose take care of the casting:
User.find()
.where('locations._id', '531283690315992f05bcdc98')
.select({'locations.$': 1})
.exec(function(err, data){
console.log(err);
console.log(data);
});

Resources