Query Mongoose Schema. nested array of comments - node.js

I have venues, which each have a comments section. Each comment is a Mongoose Comment schema. Each comment has a creator property, which is a User schema. I'm trying to find all comments a specific user has posted. How can I do this?
var VenueSchema = new mongoose.Schema({
comments: [{
type : mongoose.Schema.Types.ObjectId,
ref: 'Comment',
default: []
}]
},
{minimize: false});
var CommentSchema = new mongoose.Schema({
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}, {minimize: false});
var UserSchema = new mongoose.Schema({
token: String,
venues: [{ //in case we want users to save their favorite venues
type : mongoose.Schema.Types.ObjectId,
ref: 'Venue'
}]
});
I have tried
Venue.find({
"comments.creator": "55f1fa1263877ed0067b78c0"
}, function(err, docs) {
console.log(docs);
res.send(docs);
})
but it returns an empty array. The "55f1fa1263877ed0067b78c0" is a sample creator _id. Thanks in advance!

you cannot search creater by its id inside Venue collection becouse it collects only Comment ID. So in order to search creater by its id you need to change like below:
var VenueSchema = new mongoose.Schema({
comments: [CommentSchema]
},
{minimize: false});
var CommentSchema = new mongoose.Schema({
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}, {minimize: false});
var UserSchema = new mongoose.Schema({
token: String,
venues: [{ //in case we want users to save their favorite venues
type : mongoose.Schema.Types.ObjectId,
ref: 'Venue'
}]
});

As venueSchema is storing only ref to comments (which would be comments _id), you will not be able to query comment using venue model. Either you have embed comment document into comments array of venue schema.
Or
Just query the comment collection using comment model as below
Comment.find({
"creator": "55f1fa1263877ed0067b78c0"
}, function(err, docs) {
console.log(docs);
res.send(docs);
})

Related

Mongoose query: populate top 2 comments from Post Schema

I have 3 collections: User, Post and Comment. Posts has multiple comments.
I want grab 50 posts, populate author, populate comments but I want only top 2 most voted comments sorted by date(_id)
const PostSchema = new Schema({
author: {
type: Schema.Types.ObjectId,
ref: 'User'
},
content: String,
comments: [{
type: Schema.Types.ObjectId,
ref: 'Comment'
}]
});
const Post = mongoose.model('Post', PostSchema);
const CommentSchema = new Schema({
author: {
type: Schema.Types.ObjectId,
ref: 'User'
},
content: String,
votes: Number
});
const Comment = mongoose.model('Comment', CommentSchema);
Post
.find({})
.limit(50)
.populate('author')
.populate('comments')
...
and I dont know how to achieve this.
you can use mongoose populate options to customize your population. In this case limit it to 2.
Try this:
Post
.find({})
.limit(50)
.populate('author')
.populate({
path :'comments',
options : {
sort: { votes: -1 },
limit : 2
}
});
In case you want more cusomization, you can use mongoose Query conditions(select, match, model, path etc.) and options together.
Read Mongoose Population documentation for more detailed information.

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

Mongoose populate ObjectID from multiple possible collections

I have a mongoose model that looks something like this
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: 'article',
index:true,
},
});
But 'item' could be referenced from multiple collections. Is it possible to do something like this?
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: ['article','image'],
index:true,
},
});
The idea being that 'item' could be a document from the 'article' collection OR the 'image' collection.
Is this possible or do i need to manually populate?
Question is old, but maybe someone else still looks for similar issues :)
I found in Mongoose Github issues this:
mongoose 4.x supports using refPath instead of ref:
var schema = new Schema({
name:String,
others: [{ value: {type:mongoose.Types.ObjectId, refPath: 'others.kind' } }, kind: String }]
})
In #CadeEmbery case it would be:
var logSchema = new Schema({
item: {type: mongoose.Types.ObjectId, refPath: 'kind' } },
kind: String
})
But I did't try it yet...
First of all some basics
The ref option says mongoose which collection to get data for when you use populate().
The ref option is not mandatory, when you do not set it up, populate() require you to give dynamically a ref to him using the model option.
#example
populate({ path: 'conversation', model: Conversation }).
Here you say to mongoose that the collection behind the ObjectId is Conversation.
It is not possible to gives populate or Schema an array of refs.
Some others Stackoverflow people asked about it.
Soluce 1: Populate both (Manual)
Try to populate one, if you have no data, populate the second.
Soluce 2: Change your schema
Create two link, and set one of them.
var LogSchema = new Schema({
itemLink1: {
type: ObjectId,
ref: 'image',
index: true,
},
itemLink2: {
type: ObjectId,
ref: 'article',
index: true,
},
});
LogSchema.find({})
.populate('itemLink1')
.populate('itemLink2')
.exec()
Dynamic References via refPath
Mongoose can also populate from multiple collections based on the value of a property in the document. Let's say you're building a schema for storing comments. A user may comment on either a blog post or a product.
body: { type: String, required: true },
on: {
type: Schema.Types.ObjectId,
required: true,
// Instead of a hardcoded model name in `ref`, `refPath` means Mongoose
// will look at the `onModel` property to find the right model.
refPath: 'onModel'
},
onModel: {
type: String,
required: true,
enum: ['BlogPost', 'Product']
}
});
const Product = mongoose.model('Product', new Schema({ name: String }));
const BlogPost = mongoose.model('BlogPost', new Schema({ title: String }));
const Comment = mongoose.model('Comment', commentSchema);

How to create an embedded document that follows a model with Mongoose?

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

MongoDB Mongoose schema design

I have a schema design question. I have a UserSchema and a PostSchema.
var User = new Schema({
name: String
});
var Post = new Schema({
user: { type: Schema.Types.ObjectId }
});
Also, user is able to follow other users. Post can be liked by other users.
I would like to query User's followers and User's following, with mongoose features such as limit, skip, sort, etc. I also want to query Post that a user likes.
Basically, my only attempt of solving this is to keep double reference in each schema. The schemas become
var User = new Schema({
name: String,
followers: [{ type: Schema.Types.ObjectId, ref: "User" }],
following: [{ type: Schema.Types.ObjectId, ref: "User" }]
});
var Post = new Schema({
creator: { type: Schema.Types.ObjectId, ref: "User" },
userLikes: [{ type: Schema.Types.ObjectId, ref: "User" }]
});
so, the code that will be used to query
// Find posts that I create
Post.find({creator: myId}, function(err, post) { ... });
// Find posts that I like
Post.find({userLikes: myId}, function(err, post) { ... });
// Find users that I follow
User.find({followers: myId}, function(err, user) { ... });
// Find users that follow me
User.find({following: myId}, function(err, user) { ... });
Is there a way other than doing double reference like this that seems error prone?
Actally, you don't need the double reference. Let's assume you keep the following reference.
var User = new Schema({
name: String,
following: [{ type: Schema.Types.ObjectId, ref: "User" }]
});
You can use .populate() to get the users you're following:
EDIT: added skip/limit options to show example for pagination
User.findById(myId).populate({ path:'following', options: { skip: 20, limit: 10 } }).exec(function(err, user) {
if (err) {
// handle err
}
if (user) {
// user.following[] <-- contains a populated array of users you're following
}
});
And, as you've already mentioned ...
User.find({following: myId}).exec(function(err, users) { ... });
... retrieves the users that are following you.

Resources