How to create an embedded document that follows a model with Mongoose? - node.js

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...

Related

How to show relationship in mongoose?

I have two mongoose schemas 'user' and 'project' and i want to show relationship between these schemas like in mysql. How to do this?
And how can i pass user while creating a project?
User.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');
const Schema = mongoose.Schema();
const UserSchema = mongoose.Schema({
fullname: {type: String},
username : {type:String},
password: {type:String}
});
UserSchema.methods.encryptPassword = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(10), null);
};
UserSchema.methods.comparePassword = function(userPassword, cb) {
bcrypt.compare(userPassword, this.password, (err, isMatch) => {
if(err) throw err;
cb(null, isMatch);
});
}
module.exports = mongoose.model('User', UserSchema);
project.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema();
const User = require('./user');
const ProjectSchema = mongoose.Schema({
title: {type: String, required: true},
description: {type:String},
created_at: { type: Date, default: Date.now },
publish : { type: Boolean, default: false}
});
module.exports = mongoose.model('Project', ProjectSchema);
Creating schema in Mongoose isn't like creating schema in Relational DBMS, such as MySQL, PostGreSQL.
You can use objectId, bro.
If one project just can be handled by one user, you can use something like this.
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
But if one project is handled by multi users
users: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
Hope it will help
Schema Change
To create a relationship in Mongoose (MongoDB), create a property on your schema with the following properties
1. type: mongoose.Schema.Types.ObjectId
2. ref: "string name of the Collection this reference lives in"
For example, if you wanted a project to contain a reference to the users inside of it, you could do the fallowing
const mongoose = require('mongoose');
const Schema = mongoose.Schema();
const User = require('./user');
const ProjectSchema = mongoose.Schema({
title: {type: String, required: true},
description: {type:String},
created_at: { type: Date, default: Date.now },
publish : { type: Boolean, default: false},
users: [{type: Schema.Types.ObjectId, ref: 'User'}]
});
module.exports = mongoose.model('Project', ProjectSchema);
Example Opertaion
Given the above Schema, if you wanted to create a new Project and add users to it during creation, you would need to have those users' _id properties on hand (i.e. cached). That could mean making a prior query to the db to get all the users who will be a part of this project or having some client send you the user ids.
A much better option would be to create a project, and update its' users property as users are added or removed.
Here is a quick example
const Project = require('./models/Project.js');
let userIds = // get the user _ids some how
Project.create({
title: 'A sample',
description: 'The usual',
publish: true,
users: userIds
})
.then(callback);

How to push an object in an array in Mongoose (error)

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) { /*...*/ });
});

Individual nested subdocument Mongoose

I'm Trying to embed a subdocument into my main document,like this:
This is the main document.js
var mongoose = require('../../db/mongodb.connector'),
Schema = mongoose.Schema;
require('./document.model');
var Document= mongoose.model('Document');
require('./alert.model');
var Alert = mongoose.model('Alert');
var userSchema = new Schema({
name: { type: String }
created: { type: Date, default: Date.now()},
alerts: {type: Schema.ObjectId,ref: 'Alert'},
documents: [{type: Schema.ObjectId,ref: 'Document'}],
});
module.exports = mongoose.model('User', userSchema);
This is the embed document.js
var mongoose = require('../../db/mongodb.connector'),
Schema = mongoose.Schema;
var alertsSchema = new Schema({
push: {type: String, default: "true"},
email: {type: String, default: "false"},
sms: {type: String, default: "false"}
});
module.exports = mongoose.model('Alert', alertsSchema);
When I Insert a new User document like this:
exports.insertUser = function (userData, res) {
var user = new User({
name: userData.name,
alerts: {push: "true", email:"false", sms: "false"}
});
user.save...
...
The returned data is this:
{ name: 'name',
documents: [],
created: 2017-04-14T10:22:05.612Z
}
The problem is that I don't know if I'm doing correctly the sintax of embed document because the insert doesn't return any error but the alerts object doesn't appear into the inserted new document.
What would be wrong?
You are doing it wrong. You need to first save the alert document and then use its id in the user document.
let alertDoc = await new Alert({push: "true", email:"false", sms: "false"}).save();
// now use the id in the user doc
await new User({name: userData.name,alerts: alertDoc._id }).save()
In case you want to embed the whole document instead of just storing the ref. You could modify schema of user model. Define your schema like this.
var alertsSchema = new Schema({
push: {type: String, default: "true"},
email: {type: String, default: "false"},
sms: {type: String, default: "false"}
});
....
var userSchema = new Schema({
name: { type: String }
created: { type: Date, default: Date.now()},
alerts: alertsSchema,
documents: [{type: Schema.ObjectId,ref: 'Document'}],
});
....
// now this should work
var user = new User({
name: "<some name>",
alerts: {push: "true", email:"false", sms: "false"}
});
There is a small issue in userSchema. From your schema definition, it looks like you want to store only references to alerts and documents. The right syntax here would be alerts: {type: Schema.Types.ObjectId,ref: 'Alert'}. Please notice that extra Types in it.
Another issue here is, you are trying to store complete alert object inside user document. Mongoose can't allow that, as in your schema, you have told mongoose to save only references to the alert document. So what you need to do here is, create an alert document, get it's _id and then store it in alert field of user document.
Whenever you want to fetch the complete user schema, you can just populate alert and documents.
Hope this answer improves your understanding of how mongoose schema works.

How to push data into array using mongoose, with schema having single only single object

I am learning Mongoose, and got struct on pushing data into array blogs.
my schema is
module.exports = function(mongoose) {
var UserSchema = new Schema({
count:String,
_id : {id:false},
blogs: [{ type: Schema.Types.ObjectId, ref: 'Blog' }]
},
{
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
});
var BlogSchema = new Schema({
blogs:[{
post : String,
title: String,
author : String,
userId: {
type: String,
default: function() {
return crypto.randomBytes(12).toString('hex');
}
},
_id: {type: String, required: true}
}],
});
var models = {
User : mongoose.model('User', UserSchema),
Blog : mongoose.model('Blog', BlogSchema)
};
return models;
}
Problem is here userSchema will always have/be a single object, whcih will keep track of count of total blogs.
I know it can be done using findOneAndUpdate but I don't have id/object created for UserSchema.
Any help is appreciated. Thanks

Inheriting Mongoose schemas

I wanted to make a base 'Entity Schema', and other model entities would inherit from it.
I did it, kinda, but then strange thing happened.
Those are my schemas:
AbstractEntitySchema
MessageSchema
UserSchema
RoomSchema
File: https://github.com/mihaelamj/nodechat/blob/master/models/db/mongo/schemas.js
But in MongoDB, they are all saved in the same document store: 'entity models' not separate ones, like Messages, Users..
Did I get what was supposed to happen, but not what I wanted, separate stores?
If so I will just make a basic JSON/object as entity and append the appropriate properties for each entity. Or is there a better way?
Thanks.
Discriminators are a schema inheritance mechanism. They enable you to have multiple models with overlapping schemas on top of the same underlying MongoDB collection. rather than different documents. It seems that you misunderstand the discriminators of mongoose. Here is one article could help you to catch it correctly.
Guide to mongoose discriminators
Here are some codes sample to meet your requirement, to save the derived schema as separated documents
function AbstractEntitySchema() {
//call super
Schema.apply(this, arguments);
//add
this.add({
entityName: {type: String, required: false},
timestamp: {type: Date, default: Date.now},
index: {type: Number, required: false},
objectID: {type: String},
id: {type: String}
});
};
util.inherits(AbstractEntitySchema, Schema);
//Message Schema
var MessageSchema = new AbstractEntitySchema();
MessageSchema.add({
text: {type: String, required: true},
author: {type: String, required: true},
type: {type: String, required: false}
});
//Room Schema
var RoomSchema = new AbstractEntitySchema();
RoomSchema.add({
name: {type: String, required: true},
author: {type: String, required: false},
messages : [MessageSchema],
});
var Message = mongoose.model('Message', MessageSchema);
var Room = mongoose.model('Room', RoomSchema);
// save data to Message and Room
var aMessage = new Message({
entityName: 'message',
text: 'Hello',
author: 'mmj',
type: 'article'
});
var aRoom = new Room({
entityName: 'room',
name: 'Room1',
author: 'mmj',
type: 'article'
});
aRoom.save(function(err, myRoom) {
if (err)
console.log(err);
else
console.log("room is saved");
});
aMessage.save(function(err) {
if (err)
console.log(err);
else
console.log('user is saved');
});
If you want multiple overlapping models with different MongoDB collections, then you use this approach:
function extendSchema (Schema, definition, options) {
return new mongoose.Schema(
Object.assign({}, Schema.obj, definition),
options
);
}
Example
const extendSchema = require('mongoose-extend-schema');
const UserSchema = new mongoose.Schema({
firstname: {type: String},
lastname: {type: String}
});
const ClientSchema = extendSchema(UserSchema, {
phone: {type: String, required: true}
});
You simply extend the original object the schema was created with and recreate a new schema on its basis. This is some sort of abstract schema which you inherit from.
Check this npm module: https://www.npmjs.com/package/mongoose-extend-schema
Since ES6 this works as well:
var ImageSchema: Schema = new Schema({
...CommonMetadataSchema.obj,
src: String,
description: String,
});

Resources