deep populating in mongoose - mongodb

I've got two schemas,
one for user and another one for post
in the user schema, I've got a property for latestPost which would be an ObjectId of an entry in the post schema
when I load up the user object,
I want to get the lastestPost as an object that includes the author's username from the user schema where the author is an ObjectId that'd match an _id field in the user schema.
the mongoose tutorials seem to use the syntax of
User.findOne({ _id: req.user.id})
.populate('latestPost')
.populate({ path: 'latestPost', populate: 'author'})
but it doesn't work
it's showing
{ _id: 58f54fa51febfa307d02d356,
username: 'test',
email: 'test#test',
firstName: 'test',
lastName: 'test',
__v: 0,
latestPost:
{ _id: 58f54fa51febfa307d02d357,
user: 58f54fa51febfa307d02d356,
author: 58f54fa51febfa307d02d356,
date: 2017-04-17T23:28:37.960Z,
post: 'Test',
__v: 0 } }
but I want it to show
latestPost:
{
author: {
username : something
}
}
how does one do something like this? is there something wrong with the design of the schema or the query?
var UserSchema = new Schema({
username : String,
firstName : String,
lastName : String,
email : String,
password : String,
views : Number,
latestPost : { type: Schema.Types.ObjectId, ref: 'Post' }
});
var PostSchema = new Schema({
user : { type: Schema.Types.ObjectId, ref: 'User' },
author : { type: Schema.Types.ObjectId, ref: 'User' },
date : Date,
body : String
});
var User = mongoose.model('User', UserSchema);
var Post = mongoose.model('Post', PostSchema);
User.findOne({ _id: req.user.id})
.populate('latestPost')
.populate({ path: 'latestPost', populate: 'author'})
.exec(function(err, user) {
if (err) res.json(err)
console.log(user)
})

Maybe just this.
I don't think you need .populate('latestPost') as your next .populate() should take care of populating the latestPost. Maybe that is interfering with the next one.
User.findOne({ _id: req.user.id }).populate({
path: 'latestPost',
model: 'Post',
populate: {
path: 'author',
model: 'User'
}
}).exec(function (err, user) {
});

You need to provide the model name also in populate function:
var UserSchema = new Schema({
username : String,
firstName : String,
lastName : String,
email : String,
password : String,
views : Number,
latestPost : { type: Schema.Types.ObjectId, ref: 'Post' }
});
var PostSchema = new Schema({
user : { type: Schema.Types.ObjectId, ref: 'User' },
author : { type: Schema.Types.ObjectId, ref: 'User' },
date : Date,
body : String
});
var User = mongoose.model('User', UserSchema);
var Post = mongoose.model('Post', PostSchema);
User.findOne({ _id: req.user.id})
.populate('latestPost')
.populate({
model: 'Post',
path: 'latestPost',
select: 'author -_id'
})
.exec(function(err, user) {
if (err) res.json(err)
console.log(user)
})

Related

Ensure a unique index on nested reference on a mongoose schema

What I want is that a user can like a post only once, hence I uniquely indexed the user in the likes array to ensure the same, but it isn't working and I can't find out what is wrong here .
The schema looks like this :
const mongoose = require('mongoose')
const postSchema = new mongoose.Schema({
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User' // User model
},
text: {
type: String,
required: [true, 'Post must have some text']
},
likes: [
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}
],
comments: [
{
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
text: {
type: String,
required: [true, 'Comment must have some text']
},
addedAt: {
type: Date,
default: Date.now
}
}
],
createdAt: {
type: Date,
default: Date.now
}
})
postSchema.pre(/^find/, function(next) {
this.populate({
path: 'author',
select: 'name avatar'
}).populate({
path: 'comments.author',
select: 'name avatar'
})
next()
})
// Ensure a user can like a post only once
postSchema.index({ 'likes.user': 1 }, { unique: true })
const Post = mongoose.model('Post', postSchema)
module.exports = Post
However when I send a post request to like a post twice via the same user it
shows no error.
Here is the postman output
I have tried both the ways listed in this, but none of them worked in this case.
Mongoose Index on a field in nested document
How do I ensure a user can like a post only once from the schema itself ?
Try saving likes in this format in the database
likes:[{type:mongoose.Schema.Types.ObjectId,ref: 'User'}]
making it
likes:[ObjectId("5af03111967c60501d97781f")]
and when the post like API is hit do
{$addToSet: {likedBy: userId}}
in update query,addToSet ensures no duplicate ids are maintained in the array.

Mongoose Populate with Express, not working in production (Heroku)

This is a MERN app, hosted on github, and it works perfectly on localhost. Unfortunately, it does not work on Heroku.
The issue is the API request, it must return an object and populate an array of OIDs (see Department Model). API request is working. I'm getting the data from MLab, but it doesn't populate... instead returns: "surveys":[]
API File
router.get('/department_data/:d_oid', function(req, res) {
Department.findOne({_id: req.params.d_oid}).populate("surveys").exec(function(err,doc){
if(err) throw(err)
res.send(doc)
})
});
Department Model
**Department Model**
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
// Create the survey schema
var departmentSchema = new Schema({
department_name: {
type: String,
trim: true,
required: true
},
surveys: [{
type: Schema.Types.ObjectId,
ref: 'Surveys'
}],
participants: [{
type: String
}],
create_date: {
type: Date,
default: Date.now
},
created_by: {
type: Schema.Types.ObjectId,
ref: 'Created_By'
},
});
departmentSchema.index({ department_name: 1, created_by: 1}, {unique: true});
const Department = mongoose.model('Departments', departmentSchema);
module.exports = Department;
Survey Model
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
// Create the survey schema
var surveySchema = new Schema({
survey_name: {
type: String,
trim: true,
required: true
},
questions: [{
type: Schema.Types.ObjectId,
ref: 'Questions'
}],
created_date: {
type: Date,
default: Date.now
}
});
const Survey = mongoose.model('Surveys', surveySchema);
module.exports = Survey;
Solved.
The problem was in the database: the ref OIDs got scrambled with a previous update, so when it was trying to populate, Mongoose couldn't find any matching OIDs.
Solution: we had to purge and re-seed. When the correct OID references exist, this code works as expected in localhost & Heroku.

mongoose response odd format

What I'm trying to do is let the user get a list of posts that they have added to their favorites which is done by simply putting their _id into the post's favorites array.
when running that particular query, it comes back as an array with unnamed entries along with _locals in the array.
I need something more like Posts: [ {}, {} ], _locals.
How do I get it into that format?
var UserSchema = new Schema({
username : String,
firstName : String,
lastName : String,
email : String,
password : String
});
var PostSchema = new Schema({
author : { type: Schema.Types.ObjectId, ref: 'User' },
date : Date,
body : String,
username : String,
sid : String,
views : Number,
likes : [{ type: Schema.Types.ObjectId, ref: 'User' }],
favorites : [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
and the get
app.get('/favorites', function(req, res) {
Post
.find({ 'favorites': '58f4f9e5650785228016287f'})
.exec(function(err, favorites) {
res.render('favorites', favorites)
console.log(favorites)
})
});
I get a response of
[ { _id: 58f4f9e96507852280162880,
author: 58f4f9e5650785228016287f,
body: 'test',
date: 2017-04-17T17:22:49.059Z,
username: 'test',
sid: 'BJ-xquGRl',
__v: 2,
favorites: [ 58f4f9e5650785228016287f ],
likes: [] },
{ _id: 58f500edd8abc7242c90d61c,
author: 58f4f9e5650785228016287f,
body: 'w00p w00p',
date: 2017-04-17T17:52:45.612Z,
username: 'test',
sid: 'BJ8g-tG0e',
__v: 1,
favorites: [ 58f4f9e5650785228016287f ],
likes: [] },
_locals: { user:
{ id: 58f4f9e5650785228016287f,
username: 'test',
firstName: 'test',
lastName: 'test',
email: 'test#test.com' } } ]
That _locals is something that Express adds to the object that you pass to res.render(). If you would reverse the function calls you made, it wouldn't be there anymore:
console.log(favorites);
res.render('favorites', favorites);
However, the main issue is that you're passing an array (favorites) as argument to res.render(), which expects an object instead. The correct way of calling it would be by passing favorites as an object value:
res.render('favorites', { favorites : favorites });
// Or in recent Node versions:
// res.render('favorites', { favorites });

How can I store data other than ObjectId using Mongoose populate?

Taking the example from here
http://mongoosejs.com/docs/populate.html
If I try to tweak it so that 'fans' also contains a rating
var db = require('houselib/db');
var Schema = db.Schema;
var mongoose = db.mongoose;
var PersonSchema = new Schema({
name : String
, age : Number
, stories : [{ type: Schema.ObjectId, ref: 'Story' }]
});
var StorySchema = new Schema({
_creator : { type: Schema.ObjectId, ref: 'Person' }
, title : String
, fans : [{ type: Schema.ObjectId, ref: 'Person', rating: Number}]
});
var Story = mongoose.model('Story', StorySchema);
var Person = mongoose.model('Person', PersonSchema);
var aaron = new Person({ name: 'Aaron', age: 100 });
aaron.save(function (err) {
if (err) throw err;
var story1 = new Story({
title: "A man who cooked Nintendo"
, _creator: aaron._id
, fans: [{type: aaron._id, rating: 4}]
});
story1.save(function (err) {
if (err) throw err;
Story
.find({ _creator: aaron._id })
.populate('_creator') // <-- not really necessary
.run(function (err, stories) {
if (err) throw err;
console.log('The stories JSON is an array: ', stories);
})
});
})
I get the following error
CastError: Cast to undefined failed for value "[object Object]"
The documentation says that manual linking is preferred over DBRef
http://www.mongodb.org/display/DOCS/Database+References#DatabaseReferences-SimpleDirect%2FManualLinking
story.fans is an array of objectids. objectids do cannot have ratings. You need to add the rating to the Person schema instead of the story schema.
var PersonSchema = new Schema({
name : String
, age : Number
, rating: Number
, stories : [{ type: Schema.ObjectId, ref: 'Story' }]
});

Mongoose: how to set a schema-field to be the ID?

Given the following schema:
var UserSchema = new Schema({
, email : { type: String }
, passwordHash : { type: String }
, roles : { type: [String] }
});
I'd like email to be the key.
How can I define this?
I could do:
var UserSchema = new Schema({
, _id: { type: String }
, passwordHash : { type: String }
, roles : { type: [String] }
});
so MongoDB would recognize it as the id-field, and adapt my code to refer to _id instead of email but that doesn't feel clean to me.
Anyone?
Since you're using Mongoose, one option is to use the email string as the _id field and then add a virtual field named email that returns the _id to clean up the code that uses the email.
var userSchema = new Schema({
_id: {type: String},
passwordHash: {type: String},
roles: {type: [String]}
});
userSchema.virtual('email').get(function() {
return this._id;
});
var User = mongoose.model('User', userSchema);
User.findOne(function(err, doc) {
console.log(doc.email);
});
Note that a virtual field is not included by default when converting a Mongoose doc to a plain JS object or JSON string. To include it you have to set the virtuals: true option in the toObject() or toJSON() call:
var obj = doc.toObject({ virtuals: true });
var json = doc.toJSON({ virtuals: true });