Mongoose Populate not working with Array of ObjectIds - node.js

Here is my schema:
/** Schemas */
var profile = Schema({
EmailAddress: String,
FirstName: String,
LastName: String,
BusinessName: String
});
var convSchema = Schema({
name: String,
users: [{
type: Schema.Types.ObjectId,
ref: 'Profiles'
}],
conversationType: {
type: String,
enum: ['single', 'group'],
default: 'single'
},
created: {
type: Date,
default: Date.now
},
lastUpdated: {
type: Date,
default: Date.now
}
});
/** Models */
db.Profiles = mongoose.model('Profiles', profile);
db.Conversations = mongoose.model('ChatConversations', convSchema);
module.exports = db;
Then I try to populate Users using following code (http://mongoosejs.com/docs/populate.html):
db.Conversations.find(query).populate('users').exec(function (err, records) {
console.log(records);
});
This is returning records but users array as a blank array [].
I also tried the other way around (http://mongoosejs.com/docs/api.html#model_Model.populate):
db.Conversations.find(query, function (err, records) {
db.Conversations.populate(records, {path: "users", select: "BusinessName"}, function (err, records) {
console.log(records);
});
});
Results are same. When I checked references into profile collection records are there.
Any idea what wrong here?

I got it working by renaming model (the 3rd arguement):
mongoose.model( "Profiles", profile, "Profiles" );
The issue was Mongoose was searching for profiles collection but its there as Profiles in database. So I renamed it to Profiles to match the exact name.
Phewww! Thanks to me.

Related

E11000 duplicate key error with MongoDB/Mongoose

I have a user model schema, a work model schema, and a critique model schema. The relationship between these schema's is a user can submit many works (like blog posts), and can comment/review (which we call critiques) other people's posts (works).
So when a user submits a critique (think of it like a review), this is my post route. I find the work by the id, then create a new critique model object, and pass that to the .create() mongoose function. All goes seemingly well until I hit the foundWork.critiques.push(createdCritique) line. the console log errors out saying:
BulkWriteError: E11000 duplicate key error collection: zapper.critiques index: username_1 dup key: { : null }
Obviously, it is saying that there are two username keys in the objects and they're conflicting with each other, but I'm not familiar enough with this to find the root of the issue and fix it in the mongoose models. The models are below. If anyone could help, that'd be greatly appreciated.
// post route for getting the review
router.post('/:id', isLoggedIn, function(req, res) {
Work.findById(req.params.id, function(err, foundWork) {
if (err) {
console.log(err);
} else {
// create a new critique
var newCritique = new Critique ({
reviewerName: {
id: req.user._id,
username: req.user.username
},
work: {
id: foundWork._id,
title: foundWork.title
},
critique : req.body.critique,
date: Date.now(),
rating: 0
});
// save new critique to db
Critique.create(newCritique, function(err, createdCritique) {
if (err) {
console.log(err)
} else {
console.log("Created critique is ");
console.log(createdCritique);
// push the new critique into array of critiques of the work
foundWork.critiques.push(createdCritique);
// save to db
foundWork.save();
}
});
}
});
User model:
var mongoose = require('mongoose');
var passportLocalMongoose = require('passport-local-mongoose');
var UserSchema = new mongoose.Schema({
firstname: String,
lastname: String,
username: String,
password: String,
email: String,
zip: String,
bio: {
type: String,
default: ''
},
influences: {
type: String,
default: ''
},
favBooks: {
type: String,
default: ''
},
notWriting: {
type: String,
default: ''
},
favHero: {
type: String,
default: ''
},
favVillain: {
type: String,
default: ''
},
works: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Work'
}
],
critiques: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Critique'
}
],
friends: [
{
friendId: String,
friendName : String,
friendPic: String
}
],
friendRequests: [
{
sendingFriendId: String,
sendingFriendName : String,
sendingFriendPic: String
}
],
createdDate: {
type: Date,
default: Date.now
},
lastLogin: {
type: Date,
default: Date.now
}
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", UserSchema);
Work model:
var mongoose = require('mongoose');
var WorkSchema = new mongoose.Schema({
title: String,
genre: String,
workType: String,
length: Number,
ageRange: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
manuscriptText: String,
critiques: [
{
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Critique"
}
}
],
ratingNumber: [Number],
ratingSum: {
type: Number,
default: 0
},
date: {
type: Date,
default: Date.now
},
isPublic: {
type: Boolean,
default: true
}
});
module.exports = mongoose.model("Work", WorkSchema);
Critique model:
var mongoose = require('mongoose');
var passportLocalMongoose = require('passport-local-mongoose');
var CritiqueSchema = new mongoose.Schema({
reviewerName: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
work: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Work"
},
title: String
},
critique: String,
date: {
type: Date,
default: Date.now
},
rating: [Number]
});
CritiqueSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("Critique", CritiqueSchema);
When you create a unique index in MongoDB, the default behavior is that it will index null values also.
This means if you have a document in your collection with a username of null, you can not add another one with a username of null.
What you need is a sparse index which only indexes actual values (and ignores documents with null for that field).
Check this link It shows how to create a sparse index vs "normal" one in mongoose (index: true, vs spare: true). Most of the time you would want sparse indexes.

How do I also delete the ObjectId from the parent element with mongoose?

So essentially what I have is a location index. Here is the location schema:
var locationSchema = new mongoose.Schema({
name: String,
gps: String,
image: String,
description: String,
catches: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Catch"
}
]
});
In this schema I also have "catches" essentially just a comment. Here is the schema for that:
var catchSchema = mongoose.Schema({
species: String,
weight: String,
image: String,
catchlocation: String,
description: String,
timePosted: { type: Date, default: Date.now },
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
}
},
{timestamps: true}
);
I allow a user to delete a "catch" (or comment) with this route:
app.delete("/locations/:id/catch/:catchid", isUserPost, function(req, res){
Catch.findByIdAndRemove(req.params.catchid, function(err){
if(err){
res.redirect("back");
} else {
req.flash("success", "Your catch has been deleted.");
res.redirect("/locations/" + req.params.id);
}
});
});
Now to the problem, when the "catch" (aka comment) is deleted, it is deleted from the "catches" collection, BUT the ObjectId remains in the location. With mongoose, how would I also delete the catch ObjectId from the parent element?
You have to do it manually. My sugestion is to do it using Mongoose middleware, with pre-remove hook:
catchSchema.pre('remove', function(next) {
// you can use 'this' to extract the _id this._id and find it in locations documents and remove where it appears
/** do the thing **/
next();
});

Need some clarification on mongoose/mongodb populate command

Hello so I am making a basic app with users and posts.
I followed the mongoose documentation on population (http://mongoosejs.com/docs/2.7.x/docs/populate.html) and setup my Schemas so that the users and be connected to posts
var userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: String,
created_at: Date,
updated_at: Date,
admin: Boolean,
posts: [{ type: mongoose.Schema.ObjectId, ref: 'Post' }]
});
var postSchema = new mongoose.Schema({
_user : [{ type: mongoose.Schema.ObjectId, ref: 'User' }],
audioFile: { type: String, required: true },
imageFile: { type: String },
title: { type: String, required: true },
artist: { type: String, required: true },
start: { type: String, required: true },
stop: { type: String, required: true },
genre: { type: String, required: true },
tags: [{ type: String }]
});
app.get('/', function (req, res){
Post.find({}, function(err, allPosts){
if(!err){
res.render('main.njk', {
posts : allPosts,
title : 'Title',
isLogged : req.session.isLogged,
user : req.session.user,
messages : req.flash('alert')
});
} else { return done(err); }
});
});
Thats all fine and gravy and I can run a foreach loop on allPosts to pull each one in my HTML, but when I try to think of how I am going to display all the posts with their respective users attached to each post I am unsure of how to connect the two since all the examples in the mongoose doc is just mainly for findOne.
I was thinking something like this
app.get('/', function (req, res){
Post.find({}, function(err, allPosts){
if(!err){
allPosts.populate('_user', ['username']);
allPosts.exec(function (err, users){
if(err) console.log(err);
console.log(users);
});
res.render('main.njk', {
posts : allPosts,
title : 'Spaurk.net',
isLogged : req.session.isLogged,
user : req.session.user,
messages : req.flash('alert')
});
} else { return done(err); }
});
});
but that doesn't work of course.
So I was wondering if anyone with experience with this situation would be able to help me solve this.
Thanks a lot for any input.
EDIT, thanks to Daves help I was able to get the populate to work properly, I just cant pull the fields I want correctly with
Post.find({}).populate('_user').exec(function(err, allPosts){
In my loop {% for post in posts %}
, when I do post._user it shows the whole user schema, but when I do post._user.username it doesn't return anything. I am unsure as to why this is.
The proper way to structure a populate on a query is like this:
Post.find({})
.populate('_user')
.exec((err, allposts){...})
Then you will have an array of your Posts with the _user array populated. If you need to access a property of a user, you will need to do another loop through the _user array or specify with use you want to use _user[0].<property>

Save array of ObjectId in Schema

I have a model called Shop whos schema looks like this:
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ShopSchema = new Schema({
name: { type: String, required: true },
address: { type: String, required: true },
description: String,
stock: { type: Number, default: 100 },
latitude: { type: Number, required: true },
longitude: { type: Number, required: true },
image: String,
link: String,
tags: [{ type: Schema.ObjectId, ref: 'Tag' }],
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Shop', ShopSchema);
I want to use the array tags to reference to another model via ObjectId obviously. This set up works fine when I add ids into the property via db.shops.update({...}, {$set: {tags: ...}}) and the ids get set properly. But when I try to do it via the Express.js controller assigned to the model, nothing gets updated and there even is no error message. Here is update function in the controller:
// Updates an existing shop in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Shop.findById(req.params.id, function (err, shop) {
if (err) { return handleError(res, err); }
if(!shop) { return res.send(404); }
var updated = _.merge(shop, req.body);
shop.updatedAt = new Date();
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, shop);
});
});
};
This works perfect for any other properties of the Shop model but just not for the tags. I also tried to set the type of the tags to string, but that didn't help.
I guess I am missing something about saving arrays in Mongoose?
It looks like the issue is _.merge() cannot handle merging arrays properly, which is the tags array in your case. A workaround would be adding explicit assignment of tags array after the merge, if it is ok to overwrite the existing tags.
var updated = _.merge(shop, req.body);
if (req.body.tags) {
updated.tags = req.body.tags;
}
Hope this helps.. If the workaround is not sufficient you may visit lodash forums.

Mongoose Populate - array

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.

Resources