It's possible that i'm just burned out, but I have the following models:
user
const mongoose = require('mongoose');
const validate = require('mongoose-validator');
const Post = require('./post');
let UserSchema = mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: {
type: String, required: true, lowercase: true, trim: true, unique: true, index: true,
validate: [validate({ validator: 'isEmail', message: 'Invalid Email!' })]
},
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
})
module.exports = mongoose.model('User', UserSchema);
posts
const _ = require('lodash');
const mongoose = require('mongoose');
const User = require('./user');
let PostSchema = mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
title: { type: String, required: true },
body: { type: String, require: true }
})
PostSchema.post('save', async function (next) {
await User.update({ _id: this.user }, { $push: { posts: this._id } })
return next();
})
module.exports = mongoose.model('Post', PostSchema);
When trying to add a new post, the post save hook runs, but I get the error User.update is not a function (same goes for findOneAndUpdate, findOne, etc).
I can call user.update from the rest of the app without issues, so not sure whats happening here. Both models are in the same directory.
What you missed is that post middleware has the first argument as the "document" and not the next handler:
user.js
const { Schema } = mongoose = require('mongoose');
const userSchema = new Schema({
firstName: String,
lastName: String,
posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }]
});
post.js
const { Schema } = mongoose = require('mongoose');
const User = require('./user');
const postSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
title: String,
body: String
});
// note that first argument is the "document" as in "post" once it was created
postSchema.post('save', async function(doc, next) {
await User.update({ _id: doc.user._id },{ $push: { posts: doc._id } });
next();
});
index.js
const { Schema } = mongoose = require('mongoose');
const User = require('./user');
const Post = require('./post');
const uri = 'mongodb://localhost/posttest';
mongoose.set('debug', true);
mongoose.Promise = global.Promise;
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
let user = await User.create({ firstName: 'Ted', lastName: 'Logan' });
let post = new Post({ user: user._id, title: 'Hi', body: 'Whoa!' });
post = await post.save();
mongoose.disconnect();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Returns:
Mongoose: users.remove({}, {})
Mongoose: posts.remove({}, {})
Mongoose: users.insertOne({ posts: [], _id: ObjectId("5b0217001b5a55208150cc9b"), firstName: 'Ted', lastName: 'Logan', __v: 0 })
Mongoose: posts.insertOne({ _id: ObjectId("5b0217001b5a55208150cc9c"), user: ObjectId("5b0217001b5a55208150cc9b"), title: 'Hi', body: 'Whoa!', __v: 0 })
Mongoose: users.update({ _id: ObjectId("5b0217001b5a55208150cc9b") }, { '$push': { posts: ObjectId("5b0217001b5a55208150cc9c") } }, {})
Showing that the update fires with the correct detail.
In good design you really should avoid this and simply drop the posts array from the User model. You can always either use a virtual instead:
userSchema.virtual('posts', {
ref: 'Post',
localField: '_id',
foreignField: 'user'
})
Or just get the data via $lookup:
User.aggregate([
{ "$match": { "_id": userId } }
{ "$lookup": {
"from": Post.collection.name,
"localField": "_id",
"foreignField": "user",
"as": "posts"
}}
])
Storing and maintaining arrays of related ObjectId values "on the parent" is kind of an "anti-pattern" and leads to unnecessary overhead such as writing in two places where you only need "one".
Also in general you should be opting for embedding "first", and only considering "referencing" if and when the usage pattern of the application actually demands it. Simply copying the same patterns of an RDBMS with a database engine that was not designed for that is not the best way to utilize it.
Related
I am having a little trouble getting my mongoose virtuals to show up from deep populated fields. Here is the code of the backend function that is not behaving as I'd like it to:
exports.get_user_feed = async (req, res, next) => {
const options = { sort: { date: -1 } };
const user = await User.find(
{ username: req.params.user },
"username posts avatar followers following"
)
.populate({
path: "posts",
options,
populate: [
{
path: "author",
},
{ path: "comments", populate: { path: "author" } },
],
})
.sort({ "posts.date": 1 });
res.json({ ...user });
};
And here is the comment schema:
const mongoose = require("mongoose");
const { DateTime } = require("luxon");
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
targetPost: { type: Schema.Types.ObjectId, ref: "Post", required: true },
author: { type: Schema.Types.ObjectId, ref: "User", required: true },
date: { type: Date, required: true },
content: { type: String, maxlength: 400 },
comments: [{ type: Schema.Types.ObjectId, ref: "Comment" }],
stars: [{ type: Schema.Types.ObjectId, ref: "User" }],
});
// Virtual for post's URL
CommentSchema.virtual("url").get(function () {
return "/" + this.targetPost.url + this._id;
});
// Virtual for formatted date.
CommentSchema.virtual("formatted_date").get(function () {
return (
DateTime.fromJSDate(this.date).toLocaleString(DateTime.DATE_MED) +
" at " +
DateTime.fromJSDate(this.date).toLocaleString(DateTime.TIME_SIMPLE)
);
});
//Export model
module.exports = mongoose.model("Comment", CommentSchema);
My goal is to get the comments from each post to also include the formatted_date of the comment, but this virtual is not getting included in the response that is sent - all the regular properties are being sent but not the virtual. Any help here would be appreciated.
Add this code in your Commnet Schema file before module.exports.
CommentSchema.method('toJSON', function () {
const {
...object
} = this.toObject({ virtuals:true });
return object;
});
I have a Comment Model, User Model and Post model. In Post Model, there is a field called 'comment' and I referenced Comment model there. That way, every comment made on that post will be populated.
Now, if a user deletes any comment, that comments get deleted but the id referenced in the Post Model still remains. Though it is not active but it remains there. In a situation where you have many comments that get deleted, that Post collection with the Comment referenced field will look messy. Is there a way around this? I want once a comment is deleted, it should also delete anywhere it is referenced. Here are my codes:
Post Model
//creating the user models for the database
const mongoose = require("mongoose"); //import mongoose
const Schema = mongoose.Schema;
const PostSchema = new mongoose.Schema(
{
title:{
type: String,
required: true,
unique: true,
},
description:{
type: String,
required: true,
},
postPhoto:{
type: String,
required:false,
},
username:{
type: Schema.Types.ObjectId,
ref: 'User'
},
categories:{
type: Array,
},
comments: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
unique: true,
}]
}, {timestamps: true},
);
//exporting this schema
module.exports = mongoose.model("Post", PostSchema); //the module name is "Post"
Comment Model
const mongoose = require("mongoose"); //import mongoose to be used
const Schema = mongoose.Schema;
const CommentSchema = new mongoose.Schema(
{
commentdescription:{
type: String,
required: true,
},
author:{
type: Schema.Types.ObjectId,
ref: 'User',
},
}, {timestamps: true}
);
//exporting this schema
module.exports = mongoose.model("Comment", CommentSchema); //the module name is "Post"
Codes that delete a comment
//comment delete
router.delete("/posts/:id/comment/:id", async (req, res) =>{
try{
const comment = await Comment.findById(req.params.id)
if(comment.author == req.body.author){
try{
await comment.delete()
res.status(200).json("Comment has been deleted")
}catch(err){
console.log(err)
}
}
else{
res.status(401).json("you can only delete your comment")
}
}catch(err){
console.log(err)
}
})
codes that populates comment in Post
//Get Post
router.get("/:id", async(req, res)=>{
try{
const post = await Post.findById(req.params.id).populate('username').populate({
path: "comments",
populate: {
path: "author",
}
})
See the attached image. You can see that comment field in Post collection is still with a comment ref that has been deleted. The comment is deleted from the Comment Collection. But I will also like to delete all places it is referenced.
my main language is not English, so I apologize for that
Some things need to be corrected
Post Model
const mongoose = require("mongoose"); //import mongoose
const Schema = mongoose.Schema;
const PostSchema = new mongoose.Schema(
{
title: {
type: String,
required: true,
unique: true,
},
description: {
type: String,
required: true,
},
postPhoto: {
type: String,
required: false,
},
// Commented for testing
// username: {
// type: Schema.Types.ObjectId,
// ref: "User",
// },
categories: {
type: Array,
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment",
unique: true,
},
],
},
{ timestamps: true }
);
//exporting this schema
module.exports = mongoose.model("Post", PostSchema);
Comment Model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const CommentSchema = new mongoose.Schema(
{
commentdescription: {
type: String,
required: true,
},
// Commented for testing
// author: {
// type: Schema.Types.ObjectId,
// ref: "User",
// },
postId: {
type: Schema.Types.ObjectId,
ref: "Post",
},
},
{ timestamps: true }
);
module.exports = mongoose.model("Comment", CommentSchema);
index.js
const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true }));
const port = 3000;
var mongoDB = "mongodb://127.0.0.1/my_database";
mongoose.connect(mongoDB);
const Post = require("./Model/Post");
const Comment = require("./Model/Comment");
// add post
app.post("/posts/add", async (req, res) => {
console.log(req.body);
const post = await Post.create(req.body);
res.status(200).json({
success: true,
data: post,
});
});
// add comment
app.post("/comment/add", async (req, res) => {
const comment = await Comment.create(req.body);
const post = await Post.findByIdAndUpdate(
{ _id: comment.postId },
{
$addToSet: { comments: comment._id },
}
);
res.status(200).json({
success: true,
data: comment,
});
});
// get all post
app.get("/posts", async (req, res) => {
const post = await Post.find({});
res.status(200).json({
post,
});
});
// delete comment
app.delete("/comment/:id", async (req, res) => {
const com = await Comment.findById(req.params.id);
console.log("postid", com.postId);
await Post.findByIdAndUpdate(
{ _id: com.postId },
{
$pull: { comments: com._id },
},
{ new: true }
);
await com.delete();
res.status(200).json({
success: true,
});
});
app.listen(port, () => {
console.log("server connect");
});
Models are not smart to understand your intention that because you
deleted the comment, they should be deleted from everywhere.
Computers are stupid, you have to explain to them face to face.
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
I am trying to use populate(), however it seems like it doesn't contain transactions in user.
Is there something wrong in my code?
The transaction Table contains userId. Therefore, I thought it would automatically contains array of transactions that matches with the userId.
User Table
Transaction Table
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema(
{
name:
{
type: String,
required: true
},
transactions: [
{
type: Schema.Types.ObjectId,
ref: 'Transaction'
}
],
},
{
timestamps: true
}
);
module.exports = mongoose.model('User', userSchema)
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const transactionSchema = new Schema(
{
userId:
{
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
payer: String,
points:
{
type: Number,
reqruied: true
}
},
{
timestamps: true
}
)
module.exports = mongoose.model('Transaction', transactionSchema)
exports.getUsers = async (req, res, next) => {
User
.find()
//.findOne({ _id: "6009f3d8019a22479cb21a5d"})
.populate('Transaction')
.then(user => {
console.log(user)
})
}
In your User-Schema you've defined the transactions as transactions, so you need to populate it under this name:
User.find()
.populate('transactions')
.then(user => {
console.log(user)
})
I want to get all the posts with their author details from user model. I am using mongoDB lookup. But getting an empty array. I am matching author.uid from post to _id of user.
I want to get all the posts with their author details from user model. I am using mongoDB lookup. But getting an empty array. I am matching author.uid from post to _id of user.
//Post Model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const postSchema = new Schema({
category : {
type: String
},
content: {
type: String
},
caption: {
type: String
},
tags: [{
type: String
}],
createdAt: {
type: Number,
required: true
},
author: {
uid:{
type: String,
required: true
},
name:{
type: String
}
},
likes:[{
type:String
}],
comments:[{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}]
});
module.exports = mongoose.model('Post', postSchema);
//User Model
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
_id: {
type: String,
required: true
},
name:{
type: String,
required: true
},
avatar:{
type:String
},
bio:{
type: String
},
followers:[
{
type: String
}
],
followings:[
{
type: String
}
],
posts:[{
type: mongoose.Schema.Types.ObjectId,
ref: "Post"
}]
});
module.exports = mongoose.model('User', userSchema);
//Node js
const express = require('express');
const router = express.Router();
const Post = require('../../models/Post');
const User = require('../../models/user');
router.get('/', (req, res) => {
Post.aggregate([
{
$lookup:
{
from: 'User',
localField: "author.uid",
foreignField: "_id",
as: "creator"
}
}
]).exec((err, result) => {
if (err) {
console.log("error" ,err)
}
if (result) {
console.log(JSON.stringify(result));
}
});
});
//Output
{"_id":"5b9c7f30d",
"author": {"uid":"y08RxtsHe","name":"Sujoy Saha"},
"tags": ["#lo"],
"likes":[], "comments[],
"category":"image","content":"jsdnvs","caption":"standing
\n#lol","createdAt":1536982759517,"__v":0,"creator":[]}
You can see, i am getting empty creator array. Please help me out.
mongoose.js pluralizes (adds 's' after your model name) when it creates a collection in MongoDb.
Can you try with from: 'users' in your $lookup clause?