References in another Schema - mongoose - node.js

I'm doing some tests with MongoDB and NodeJS for a new project.
Searching the documentation I found that it is possible to make references to other collections and bring this data to JSON.
It was then that I decided to perform the following test:
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const userSchema = new Schema({
name: {
type: String,
required: true,
unique: true
},
email: {
type: String,
required: true,
unique: true,
},
posts: [{
type: Schema.Types.ObjectId,
ref: 'Post'
}]
})
const userModel = mongoose.model('User', userSchema)
const postSchema = new Schema({
title: {
type: String
},
content: {
type: String
},
author: {
type: Schema.Types.ObjectId,
ref: 'User'
}
})
const postModel = mongoose.model('Post', postSchema)
const saveUser = new userModel({
name: 'user',
email: 'user#email.com'
})
saveUser.save()
const savePost = new postModel({
title: 'Lorem',
content: 'Lorem Ipsum',
author: saveUser._id
})
savePost.save()
postModel.find()
.populate('User')
.exec((err, post) => {
console.log(post)
})
However the return of JSON is:
{
_id: 5edd0c24a4f42b0e126f4b15,
title: 'Lorem',
content: 'Lorem Ipsum',
author: 5edd0c24a4f42b0e126f4b14,
__v: 0
}
When should it be:
{
_id: 5edd0c24a4f42b0e126f4b15,
title: 'Lorem',
content: 'Lorem Ipsum',
author: {
_id: 5edd0c24a4f42b0e126f4b14,
name: user,
email: user#email.com
},
__v: 0
}
Does anyone know any solution to this problem where I can insert for all my new Schemas?

Regarding populate you should provide the field name provided in postSchema. So should replace .populate('User') with .populate('author').
Since the post requires the author _id you should save the post only after the author is successfully saved
const saveUser = new userModel({
name: "user",
email: "user#email.com",
});
saveUser.save((err, data) => {
if (err) console.log(err);
else {
const savePost = new postModel({
title: "Lorem",
content: "Lorem Ipsum",
author: saveUser._id,
});
savePost.save();
}
});

Related

Populate does not work in mongoose create

User schema looks something like this
addressId: [{ type: mongoose.Schema.Types.ObjectId, ref: 'address' }],
mongoose.model('user', userSchema);
Address model
mongoose.model('address', postalAddressSchema);
I am trying to do this:
const createdUser = await mongoose.models.user.create(user);
return { success: true, user: createdUser.populate('addressId') };
I am trying to populate address in user creation. It returns null
You may try to find, populate and exec after creating the user. Here is a working example
const theMongoose = await mongoose.connect(mongoServerUri);
const accountModel = theMongoose.model(
'Account',
new Schema({
_id: Schema.Types.ObjectId,
name: String
})
);
const activityModel = theMongoose.model(
'Activity',
new Schema({
account: { type: Schema.Types.ObjectId, ref: 'Account' },
date: Date,
desc: String
})
);
const account = await accountModel.create({ name: "Test User", _id: mongoose.Types.ObjectId.createFromTime(new Date().getTime()/1000) });
await activityModel.create({ date: new Date(), desc: "test", account: account.id });
// find, populate, & exec
const res = await activityModel.find({ desc: "test" }).populate("account").exec()
console.log(res);
And the output is
[
{
_id: new ObjectId("62da50ec77c026e2aad5577b"),
account: {
_id: new ObjectId("62da50ea0000000000000000"),
name: 'Test User',
__v: 0
},
date: 2022-07-22T07:25:32.119Z,
desc: 'test',
__v: 0
}
]

Mongoose - Get and Delete a subrecord

I have a model defined as so:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const feedbackSchema = new Schema({
Name: {
type: String,
required: true,
},
Email: {
type: String,
required: true,
},
Project: {
type: String,
required: true,
},
Wonder: {
type: String,
required: true,
},
Share: {
type: String,
required: true,
},
Delight: {
type: String,
required: true,
},
Suggestions: {
type: String,
required: true,
},
Rating: {
type: String,
required: true,
},
dateCreated: {
type: Date,
default: Date.now(),
},
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
});
const UserSchema = new Schema({
googleId: {
type: String
},
displayName: {
type: String
},
firstName: {
type: String
},
lastName: {
type: String
},
image: {
type: String
},
createdAt: {
type: Date,
default: Date.now(),
},
feedback: [feedbackSchema],
})
module.exports = mongoose.model("User", UserSchema);
An example document:
{
_id: ObjectId('60b9dc728a516a4669b40dbc'),
createdAt: ISODate('2021-06-04T07:42:01.992Z'),
googleId: '2342987239823908423492837',
displayName: 'User Name',
firstName: 'User',
lastName: 'Name',
image: 'https://lh3.googleusercontent.com/a-/89wf323wefiuhh3f9hwerfiu23f29h34f',
feedback: [
{
dateCreated: ISODate('2021-06-04T07:42:01.988Z'),
_id: ObjectId('60b9dc858a516a4669b40dbd'),
Name: 'Joe Bloggs',
Email: 'joe#bloggs.com',
Project: 'Some Project',
Suggestions: 'Here are some suggestions',
Rating: '10'
},
{
dateCreated: ISODate('2021-06-04T08:06:44.625Z'),
_id: ObjectId('60b9df29641ab05db7aa2264'),
Name: 'Mr Bungle',
Email: 'mr#bungle',
Project: 'The Bungle Project',
Suggestions: 'Wharghable',
Rating: '8'
},
{
dateCreated: ISODate('2021-06-04T08:08:30.958Z'),
_id: ObjectId('60b9df917e85eb6066049eed'),
Name: 'Mike Patton',
Email: 'mike#patton.com',
Project: 'No More Faith',
Suggestions: 'Find the faith',
Rating: '10'
},
],
__v: 0
}
I have two routes defined, the first one is called when the user clicked a button on a feedback item on the UI which takes the user to a "are you sure you want to delete this record"-type page displaying some of the information from the selected feedback record.
A second route which, when the user clicks 'confirm' the subrecord is deleted from the document.
The problem I'm having is I can't seem to pull the feedback from the user in order to select the document by id, here's what I have so far for the confirmation route:
router.get('/delete', ensureAuth, async (req, res) => {
try {
var url = require('url');
var url_parts = url.parse(req.url, true);
var feedbackId = url_parts.query.id;
const allFeedback = await User.feedback;
const feedbackToDelete = await allFeedback.find({ _id: feedbackId });
console.log(feedbackToDelete);
res.render('delete', {
imgSrc: user.image,
displayName: user.firstName,
feedbackToDelete
});
} catch (error) {
console.log(error);
}
})
Help much appreciated
Update
You should be able to do just this:
const feedbackToDelete = await User.feedback.find({ _id: feedbackId });
Or if feedbackId is just a string, which is appears to be, you may have to do something like:
// Create an actual _id object
// That is why in your sample doc you see ObjectId('foobarbaz')
const feedbackId = new mongoose.Types.ObjectId(url_parts.query.id);
const feedbackToDelete = await User.feedback.find({ _id: feedbackId });
Original
Shouldn't this:
const allFeedback = await User.feedback; (a field)
be this:
const allFeedback = await User.feedback(); (a method/function)
?

Subdocument function "pull" and "id" not working in Mongoose

I am pretty new at mongoose and nodejs so I was doing my project referring to mongoose document. I want to remove a particular subdocument in the comment array by identifing the subdocument with its id. I trried doing it using "pull" as well as "id" method as shown in the image. I couldn't find any mistake in my syntax as well but still it is working.
This is sample document from my db:
{
comments: [
{
replyComment: [],
_id: 601a673735644c83e0aa1be3,
username: 'xyz123#gmail.com',
email: 'xyz123#gmail.com',
comment: 'test123'
},
{
replyComment: [],
_id: 601a6c94d1653c618c75ceae,
username: 'xyz123#gmail.com',
email: 'xyz123#gmail.com',
comment: 'reply test'
},
{
replyComment: [],
_id: 601eb7ba7233015d7090c6bf,
username: 'xyz123#gmail.com',
email: 'xyz123#gmail.com',
comment: 'test comment'
},
{
replyComment: [],
_id: 601ec090f5f22d75b41bec7b,
username: 'xyz123#gmail.com',
email: 'xyz123#gmail.com',
comment: 'test comment123'
}
],
_id: 601a3b8038b13e70405cf9ea,
title: 'latest test',
snippet: 'latest test snippet',
body: 'latest test body',
createdAt: 2021-02-03T05:58:24.123Z,
updatedAt: 2021-02-07T06:56:53.902Z,
__v: 15
}
By doing this test findById("601a3b8038b13e70405cf9ea") and console.log(result)
My topicSchema file:
const mongoose = require('mongoose');
const Schema =mongoose.Schema;
const topicSchema = new Schema({
title: {
type: String,
required: true
},
snippet: {
type: String,
required: true
},
body: {
type: String,
required: true
},
comments: {
type: Array,
required: false
}
}, {timestamps: true},{ versionKey: false });
const Topic = mongoose.model('Topic', topicSchema);
module.exports = Topic;
My commentSchema file:
const mongoose = require('mongoose');
const Schema =mongoose.Schema;
const comSchema = new Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
comment: {
type: String,
required: true
},
replyComment: {
type: Array,
required: false
},
}, {timestamps: true},{versionKey: false});
const Comm = mongoose.model('comm', comSchema);
module.exports = Comm;
You have not defined topic and don't using topic = result, with , because it's not necessary
so doing like this :
result.comments.id(commId).remove();
result.save()
if you want to using topic just try
let topic = result;
topic.comments.id(commId).remove();
topic.save()
for this document you can using update and $pull like this:
Topic.updateOne(
{
_id: req.params.id,
},
{
$pull: {
"comments" : { _id: req.params.id1 }
},
},
).then((res)=>{
console.log(res)
}).catch(err=>{
console.log(err)
});
if you can use async/await just try
app.delete('/topic/:id/comments/:id1',async(req,res)=>{
let result = await Topic.findById(req.params.id);
result.comments.id(req.params.id1).remove();
let finalResult = await result.save()
console.log(finalResult)
})
and with .then() .catch approach:
Topic.findById(res.params.id).then(result=>{
let topic = result;
topic.comments.id(res.params.id1).remove();
topic.save().then(result=>{
console.log(result)
}).catch(err=>console.log(err))
}
).catch(err=>{
console.log(err)
});
NOTE: update the mongodb to 4.4 and uninstall mongoose module and install again

Mongoose Populate not working while querying data

I have 2 models, category and story.
Story contains reference id of the category.
In controller story, I have a function mystories which should fetch all the story records of particular user along with category information.
I am getting data from story collection but not from category collection.
The result which I receive is something like this:
category_id: "5d10978c8e0f5d5380fdb3e6"
created_at: "2019-06-25T10:02:47.637Z"
created_by: "5d1066fba920ef2ccfe68594"
image: "uploads/1561456967615_164.jpg"
published: "no"
status: "pending"
text: "<p><strong>Fashion</strong> is a popular aesthetic expression in a certain time and context, especially in clothing, footwear, lifestyle, accessories, makeup, hairstyle and body </p>"
title: "New fashion"
__v: 0
_id: "5d11f14757f8616041616217"
It should however return category collection information instead of
category id.
Category model:
const mongoose = require('mongoose');
const categorySchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: {
type: String,
required: true,
unique: true
},
status: {
type: String,
required: true,
enum: ['active','inactive','deleted']
},
created_at: { type: Date, default: Date.now },
created_by: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true},
});
module.exports = mongoose.model('Category', categorySchema);
Story model:
const mongoose = require('mongoose');
const storySchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
title: {
type: String,
required: true
},
text: {
type: String,
required: true
},
category_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category',
required: true
},
image: {
type: String,
required: true
},
status: {
type: String,
required: true,
enum: ['pending','approved','deleted']
},
published: {
type: String,
required: true,
enum: ['yes','no']
},
created_at: { type: Date, default: Date.now },
created_by: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true},
});
module.exports = mongoose.model('Story', storySchema);
Controller code:
const mongoose = require('mongoose');
const Story = require('../models/story');
const Category = require('../models/category');
exports.mystories = async (req, res, next) => {
const user_id = req.userData.id;
const all_stories = await Story
.find({ created_by: user_id})
.populate('name','category')
.sort({ created_at: -1 })
.exec();
if(all_stories.length > 0) {
return res.status(200).json({
response: all_stories
});
}else {
return res.status(200).json({
response: []
});
}
};
exports.add_story = (req, res, next) => {
console.log(req.file);
const story = new Story({
_id: new mongoose.Types.ObjectId(),
title: req.body.story_title,
text: req.body.story_text,
category_id: req.body.story_category,
image: req.file.path,
status: 'pending',
published: 'no',
created_by: req.userData.id
});
story
.save()
.then(result => {
console.log(result);
res.status(200).json({
response: 'added_story'
});
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
})
});
};
populate takes the field name as it is given in story schema.
it should be :
.populate({path: 'category_id', select: 'name'})

How to implement partial document embedding in Mongoose?

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);
}
});
};

Resources