I am making a route in a Node server using Mongoose and Mongo which stores a comment in the comments array in blogPost (I will post model code). When I try to execute the query it gives me the following error:
Postman error
These are my models and the route:
blogPost model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const BlogPostSchema = new Schema({
content: {
type: String,
validate: {
validator: (content) => content.length > 5,
message: 'Content must contain at least 6 characters.'
},
required: [true, 'Content must be filled in.']
},
rating: Number,
title: String,
user: { type: Schema.Types.ObjectId, ref: 'user' },
board: {type: Schema.Types.ObjectId, ref: 'board'},
comments: [{
type: Schema.Types.ObjectId,
ref: 'comment'
}]
});
const BlogPost = mongoose.model('blogPost', BlogPostSchema);
module.exports = BlogPost;
comment model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
content: {
type: String,
validate: {
validator: (content) => content.length > 5,
message: 'Content must contain at least 6 characters.'
},
required: [true, 'Content must be filled in.']
},
user: { type: Schema.Types.ObjectId, ref: 'user' },
rating: Number
// board: Board
});
// UserSchema.virtual('postCount').get(function(){
// return this.posts.length;
// });
const Comment = mongoose.model('comment', CommentSchema);
module.exports = Comment;
Route
routes.put('/blogPosts/:id/comment', function(req, res) {
const blogPostId = req.param('id');
const commentProps = req.body;
BlogPost.findById(req.params.id)
.then((blogPost) => {
blogPost.comments.push(commentProps);
blogPost.save();
})
.catch((error) => res.status(400).json(error))
});
Any help is greatly appreciated.
The problem is that you are pushing an entire comment object into an array that's only supposed to have objectids.
dnickless answer uses a solution with referencing, meaning you have a collection for blogposts and a collection for comments. The blogpost documents will refer to their comments with objectids.
You can also change the blogpost model to use embedding rather than referencing, meaning the comments will be a part of the blogpost documents as subdocuments. There are a couple of nice discussions regarding what's better here and here. The short answer is that it depends on the use case. You can choose what you want to use yourself.
Here's how embedding is done:
Blogpost model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const commentSchema = require('./comment.model');
const BlogPostSchema = new Schema({
content: {
type: String,
validate: {
validator: (content) => content.length > 5,
message: 'Content must contain at least 6 characters.'
},
required: [true, 'Content must be filled in.']
},
rating: Number,
title: String,
user: { type: Schema.Types.ObjectId, ref: 'user' },
board: {type: Schema.Types.ObjectId, ref: 'board'},
comments: [commentSchema]
});
const BlogPost = mongoose.model('blogPost', BlogPostSchema);
module.exports = BlogPost;
Notice how the comments array uses the comment schema rather than being an array of object ids. The comment model has to be changed slightly:
Comment model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
content: {
type: String,
validate: {
validator: (content) => content.length > 5,
message: 'Content must contain at least 6 characters.'
},
required: [true, 'Content must be filled in.']
},
user: { type: Schema.Types.ObjectId, ref: 'user' },
rating: Number
// board: Board
});
// UserSchema.virtual('postCount').get(function(){
// return this.posts.length;
// });
module.exports = CommentSchema;
const Comment = mongoose.model('comment', CommentSchema); was removed. The schema should no longer be registered and comments will not have their own collection. The schema is now being exported rather than the registered model.
The original code for adding comments should work after that. Comments will be a part of the blogpost document, not be in their own collection.
You don't want to push the entire comment into the comments array but just its _id. So something like this (untested):
// first save the comment
Comment.create(commentProps, function(err, comment) {
if(err)
{
// save the world
}
else
{
BlogPost.update({ _id: req.param('id') }, { $push: { comments: comment._id } }, function(err, numberAffected, raw) { /*...*/ });
});
Related
I am trying to populate my user schema with items but for some reason it does not populate anything in to the user schema. Could someone please take a look. I have 1 user and 1 item belonging to that user within my database but nothing is populating and I keep seeing null.
User Schema
var mongoose = require('mongoose')
var userSchema = mongoose.Schema({
name: {
type: String,
required: true
},
discordID: {
type: String,
required: true
},
discordImage: {
type: String,
required: true
},
items: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Item'
}]
})
const User = module.exports = mongoose.model('User', userSchema)
Item Schema
var mongoose = require("mongoose")
var itemSchema = mongoose.Schema({
name: {
type: String,
required: true
},
purchasedPrice: {
type: Number,
required: true
},
purchasedDate: {
type: String,
required: true
},
author: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "User"
}
})
const Item = module.exports = mongoose.model("Item", itemSchema)
Populate Code
app.get("/inventory", async (req, res) => {
try {
await req.user.populate({
path: 'items'
}).execPopulate()
console.log(req.user)
} catch (error) {
console.log(error)
}
res.status(200).render("inventory.ejs", { currentUser: req.user })
})
Objects in the DB:
Item:
User:
Used virtual method on user schema to create association
userSchema.virtual("items", {
ref: "Item",
localField: "_id",
foreignField: "author"
})
Worked fine with original code
I keep seeing null.
and
no its just empty
hints there are no items added to your user. You need to have some ids you can populate.
All populate does is convert an ObjectID into a document. There is no magic that will sync itemSchema.author with userSchema.items.
Hence, it's not enough to add the author to the item. You also need to add the item to the author.
So for example, you could add an item like this:
const item = new Item({author: user._id});
await item.save();
req.user.items.push( item );
await req.user.save();
Now when you log req.user, are there any items there?
Once you see objectIds, then you can go back and add that .populate('items') into the mix and I promise you it'll work.
I have a problem with my mongoose schemas. I was able to populate one document from another, but I am unable to create a similar connection between other document.
I've been looking at this for a long time but I just don't see whats wrong. It seems to be setup correctly, but comments do not populate. I am using mongoose 5.4.5.
blogSchema
const mongoose = require('mongoose')
const blogSchema = mongoose.Schema({
title: String,
author: String,
url: String,
likes: Number,
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
]
})
blogSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
}
})
const Blog = mongoose.model('Blog', blogSchema)
module.exports = Blog
commentSchema
const mongoose = require('mongoose')
const commentSchema = mongoose.Schema({
text: {
type: String,
minlength: 3,
present: true
},
blog: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Blog'
}
})
commentSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
}
})
const Comment = mongoose.model('Comment', commentSchema)
module.exports = Comment
userSchema
const mongoose = require('mongoose')
const uniqueValidator = require('mongoose-unique-validator')
const userSchema = mongoose.Schema({
username: {
type: String,
unique: true,
minlength: 3,
present: true
},
name: String,
passwordHash: String,
blogs: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Blog'
}
],
})
userSchema.plugin(uniqueValidator)
userSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
delete returnedObject.passwordHash
}
})
const User = mongoose.model('User', userSchema)
module.exports = User
populate
router.get('/', async (request, response) => {
const blogs = await Blog.find({})
.populate('comment', { text: 1 })
.populate('user', { username: 1, name: 1 })
response.json(blogs.map(b => b.toJSON()))
})
I am able to populate user correctly to blogSchema, but populating Comment doesnt work. The order of the populate calls do not change the situation and if I call populate only for comment it doesn't work anyway.
I suppose that there is a problem with my schemas, but I just am unable to see it.
Well...In your blog it's called comments but you try to populate comment. I think that's the issue.
I want to make a route in NodeJS which increment the property of a subdocument but I don't know how to do it and the way I am doing it now does not seem to work.
blogPost model
const BlogPostSchema = new Schema({
content: {
type: String,
validate: {
validator: (content) => content.length > 5,
message: 'Content must contain at least 6 characters.'
},
required: [true, 'Content must be filled in.']
},
rating: Number,
title: String,
user: { type: Schema.Types.ObjectId, ref: 'user' },
board: {type: Schema.Types.ObjectId, ref: 'board'},
comments: [commentSchema]
});
const BlogPost = mongoose.model('blogPost', BlogPostSchema);
module.exports = BlogPost;
comment schema
const CommentSchema = new Schema({
content: {
type: String,
validate: {
validator: (content) => content.length > 5,
message: 'Content must contain at least 6 characters.'
},
required: [true, 'Content must be filled in.']
},
user: { type: Schema.Types.ObjectId, ref: 'user' },
rating: Number
});
module.exports = CommentSchema;
NodeJS route
routes.put('/blogPosts/:id/comment/:idm', function(req, res) {
const blogPostId = req.param('id');
const commentId = req.param('idm');
BlogPost.findById(blogPostId)
.then((blogPost) => {
blogPost.comments.findByIdAndUpdate({_id: commentId}, {$inc: {rating: 1}});
})
.then((blogPost) => res.status(200).json({
'status': 'Comment rating is increased.'
}))
.catch((error) => res.status(400).json(error))
});
This is the response Postman
All help is appreciated.
Well the promise hasn't been resolved, so what you can do is use async await functions or JavaScript generators which will make the client wait till the rating is incremented and the results json is sent.
Here's a tutorial on async-await and generators.
I have a simple relation between topics and categories when topic belongs to a category.
So schema looks like this:
const CategorySchema = new mongoose.Schema({
name: String,
slug: String,
description: String
});
And topic
const TopicSchema = new mongoose.Schema({
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category'
},
title: String,
slug: String,
body: String,
created: {type: Date, default: Date.now}
});
I want to implement particular embedding of category into topic
{
category: {
_id: ObjectId('abc'),
slug: 'catslug'
},
title: "Title",
slug: "topictitle",
...
}
It will help me avoid unnecessary population and obtain performance bonuses.
I don't want to embed whole document because I want to changes categories sometimes (it is a rare operation) and maintain references.
Hope this helps, done it in my own project to save some RTTs in common use cases. Make sure you're taking care of both copies on update.
parent.model.js:
const mongoose = require('mongoose');
const childEmbeddedSchema = new mongoose.Schema({
_id: {type: mongoose.Schema.Types.ObjectId, ref: 'Child', auto: false, required: true, index: true},
someFieldIWantEmbedded: {type: String}
});
const parentSchema = new mongoose.Schema({
child: { type: childEmbeddedSchema },
moreChildren: { type: [{type: childEmbeddedSchema }] }
});
module.exports = mongoose.model('Parent', parentSchema);
child.model.js:
const mongoose = require('mongoose');
const childSchema = new mongoose.Schema({
someFieldIWantEmbedded: {type: String},
someFieldIDontWantEmbedded: {type: Number},
anotherFieldIDontWantEmbedded: {type: Date}
});
module.exports = mongoose.model('Child', childSchema);
parent.controller.js:
const mongoose = require('mongoose');
const Parent = require('path/to/parent.model');
exports.getAll = (req, res, next) => {
const query = Parent.find();
// only populate if requested! if true, will replace entire sub-document with fetched one.
if (req.headers.populate === 'true') {
query.populate({
path: 'child._id',
select: `someFieldIWantEmbedded ${req.headers.select}`
});
query.populate({
path: 'moreChildren._id',
select: `someFieldIWantEmbedded ${req.headers.select}`
});
}
query.exec((err, results) => {
if (err) {
next(err);
} else {
res.status(200).json(results);
}
});
};
I have two models, Post and Comment:
My Post model (models/post.js):
var mongoose = require('mongoose');
var Comment = require('../models/comment');
var Schema = mongoose.Schema;
module.exports = mongoose.model('Post', new Schema({
text: {type: String, trim: true},
postedBy: String,
comments: [Comment]
}));
My Comment model (models/comment.js):
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports = mongoose.model('Comment', new Schema({
user: String,
comment: {type: String, trim: true},
created: {type: Date, default: Date.now(), select: false}
}));
When I attempt to create a new post without any comments, the post is created perfectly fine.
Although when I try to $push a comment to the post after creation, nothing happens.
Post.findOneAndUpdate(
{"_id": req.params.id},
{$push: {comments: {
comment: "Hello World",
user: "933ujrfn393r"
}}
}).exec(function(err, post) {
console.log(post);
res.json({success: true});
});
Why is this failing to push the comment to the post? My console.log(post) line simply logs undefined, so not too sure what is happening here. I tried a simple test of Post.findOne({"_id": req.params.id}) and it returned the post successfully, so there is no problem with the find query.
Embedded sub documents
Your usage implies an embedded sub document inside the model which only requires a schema definition for the sub document. This will store both schema's in a single document in a single collection in MongoDB
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CommentSchema = new Schema({
user: String,
comment: {type: String, trim: true},
created: {type: Date, default: Date.now(), select: false}
});
var PostSchema = new Schema({
text: {type: String, trim: true},
postedBy: String,
comments: [CommentSchema]
});
module.exports = mongoose.model('Post', PostSchema);
Then create comments as you were.
Post.findOneAndUpdate(
{"_id": req.params.id},
{$push: {comments: {
comment: "Hello World",
user: "933ujrfn393r"
}}
}).then(function (post) {
console.log(post);
res.json({success: true});
});
Document references
If you want to keep the two models then you would need to use a reference in your Post schema instead. This will create seperate documents in seperate collections in MongoDB and use the _id to look up the second document.
var PostSchema = new Schema({
text: {type: String, trim: true},
postedBy: String,
comments: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
});
Then comments need to be created before you can reference them in the Post model.
c = new Comment({ comment: 'x' })
c.save().then(function (result) {
return Post.findOneAndUpdate(
{ _id: req.params.id },
{ $push: { comments: result._id } }
);
}).then(function (result) {
console.log('updated post');
});
Population can be used to easily retrieve the "foreign" documents.
Based on this question, I believe your problem is that you're embedding the Comment Model instead of the Comment Schema.
Try changing post.js from:
var Comment = require('../models/comment');
to:
var Comment = require('../models/comment').schema;
This also makes sense after looking at the example on the Mongoose docs regarding sub-docs.
P.S.
What helped me investigate this was outputting the err object of the exec callback to see what was actually going on...