Mongoose populate not working as expected - node.js

I am very much new in using mongoose and had done simple db work on mongodb.
Seeing my usecase I found this method of doing it in mongodb using mongoose. However I am not getting expected result as shown in tutorials.
My Schemas
var EventsSchema = new mongoose.Schema({
root: {type: mongoose.Schema.Types.ObjectId, ref: 'root'},
voting: [{type: mongoose.Schema.Types.ObjectId, ref: 'Voting'}]
});
var VotingSchema = new mongoose.Schema({
events: {type: mongoose.Schema.Types.ObjectId, ref: 'Events'},
title: {
type: String,
required: true
}
})
var Events = mongoose.model('Events', EventsSchema, 'event');
var Voting = mongoose.model('Voting', VotingSchema, 'voting');
I have these two schemas initially. I want to create a voting event. When voting event is created then in voting schema events id should be stored as suggested and more I want is that voting event reference should be stored in EventSchema.
var events = new Events({});
events.save()
.then((events) => {
var voting = new Voting({ events: events._id, title: "Test Title" });
voting.save()
.then((voting) => {
Voting.findOne({title: 'Test Title'})
.populate('events')
.exec(function(err, voting){
console.log(voting, err);
if(err) res.sendStatus(401).send();
else res.sendStatus(200).send();
})
})
.catch((e) => {
res.sendStatus(401).send();
});
})
.catch((e) => {
res.sendStatus(401).send();
})
What I am getting on console is
{
_id: 5b83eca82a3cfb1dec21ddc9,
events: { voting: [], _id: 5b83eca82a3cfb1dec21ddc8, __v: 0 },
title: 'Test Title',
__v: 0
}
My MongoDB looks like this
voting
{
"_id" : ObjectId("5b83eca82a3cfb1dec21ddc9"),
"events" : ObjectId("5b83eca82a3cfb1dec21ddc8"),
"title" : "Test Title",
"__v" : 0
}
events
{
"_id" : ObjectId("5b83eca82a3cfb1dec21ddc8"),
"voting" : [],
"__v" : 0
}
I am not sure how will my mongodb look like. But after attempting the code once it looks like above.
I must be doing something wrong or missing something important. But this kind of code is there in docs too. docs link. Help me in sorting this issue.
Thanks

Array of ObjIds should be defined this way in schema:
var EventsSchema = new mongoose.Schema({
root: {type: mongoose.Schema.Types.ObjectId, ref: 'root'},
voting: {type: [mongoose.Schema.Types.ObjectId], ref: 'Voting'}//**array bracers**
});
Also calling population over a field of type array like root field will return an empty array in the res of the API, see query hit by mongoose.set('debug', true); you will notice that mongoose is searching in event model not root.
though you have to tell mongoose in the population method which model to use in order to get the population working, unless you tell mongoose which model to search in .
populate({path:'vooting',model:'vooting'})

Related

Using "deleteMany" post middleware in mongoose

In my Nodejs and Express app, I have a mongoose User schema, a Post schema and a Comment schema as follows:
const UserSchema = new Schema({
username: {
type: String,
required: true,
unique: true
},
password: String,
posts : [
{
type : mongoose.Schema.Types.ObjectId,
ref : 'Post'
}
]
});
const PostSchema = new Schema({
author : {
type : mongoose.Schema.Types.ObjectId,
ref : 'User'
},
createdAt: { type: Date, default: Date.now },
text: String,
comments : [
{
type : mongoose.Schema.Types.ObjectId,
ref : 'Comment'
}
],
});
const CommentSchema = new Schema({
author : {
type : mongoose.Schema.Types.ObjectId,
ref : 'User'
},
createdAt: { type: Date, default: Date.now },
text: String
});
I have coded the general CRUD operations for my User. When deleting my user, I can easily delete all posts associated with that user using deleteMany:
Post.deleteMany ({ _id: {$in : user.posts}});
To delete all the comments for all the deleted posts, I can probably loop through posts and delete all the comments, but I looked at mongoose documentation here and it seems that deleteMany function triggers the deleteMany middleware. So In my Post schema, I went ahead and added the following after defining schema and before exporting the model.
PostSchema.post('deleteMany', async (doc) => {
if (doc) {
await Comment.deleteMany({
_id: {
$in: doc.comments
}
})
}
})
When deleting user, this middleware is triggered, but the comments don't get deleted. I got the value of doc using console.log(doc) and I don't think it includes what I need for what I intend to do. Can someone tell me how to use the deleteMany middleware properly or if this is not the correct path, what is the most efficient way for me to delete all the associated comments when I delete the user and their posts?
deleteMany will not give you access to the affected document because it's a query middleware rather than a document middleware (see https://mongoosejs.com/docs/middleware.html#types-of-middleware). Instead it returns the "Receipt-like" object where it tells it successfully deleted n objects and such.
In order for your hook to work as expected, you'll need to use something other than deleteMany, such as getting all of the documents (or their IDs), and loop through each one, using deleteOne.

Mongoose Model ObjectId References Not Working

I'm working with Mongoose models and references. I've been using the code from mongoose's website where it talks about the populate method and references. I am trying to have it save the respective "referenced" ids in both models. It is only saving the reference ids in the story model. Here is the code:
Update: Added schemas at the top to help:
var personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
var Story = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);
(end of schemas)
var author = new Person({
_id: new mongoose.Types.ObjectId(),
name: 'Ian Fleming',
age: 50
});
author.save(function (err) {
if (err) return handleError(err);
var story1 = new Story({
title: 'Casino Royale',
author: author._id // assign the _id from the person
});
story1.save(function (err) {
if (err) return handleError(err);
// thats it!
});
});
When you run this code, it generates this in mongo:
db.people.find()
{ "_id" : ObjectId("5be0a37f1dd61a343115e2c8"), "stories" : [ ], "name" : "Ian Fleming", "age" : 50, "__v" : 0 }
db.stories.find()
{ "_id" : ObjectId("5be0a37f1dd61a343115e2c9"), "title" : "Casino Royale", "author" : ObjectId("5be0a37f1dd61a343115e2c8"), "__v" : 0 }
It appears to not be storing any ids in the people collection within "stories." Wouldn't you want to save the stories ids in the people collection as well?
I tried to modify the code to make it work with (moved the author save function, until after the story id is set):
var author = new Person({
_id: new mongoose.Types.ObjectId(),
name: 'Ian Fleming',
age: 50
});
var story1 = new Story({
_id: new mongoose.Types.ObjectId(),
title: 'Casino Royale',
author: author._id // assign the _id from the person
});
author.stories = story1._id;
author.save(function (err) {
if (err) return handleError(err);
story1.save(function (err) {
if (err) return handleError(err);
// thats it!
});
This gives me an author undefined.
Mongo wouldn't automatically add to the Person "stories" field just because you added a Story object.
You don't really need to store the story ids in Person objects anyway, as you can always get a list of stories by an author with
db.stories.find({author: <id>})
Storing in both places would create redundant information and you'd have to pick one to be the truth in the case of a mismatch. Better to not duplicate, methinks.
UPDATE:
References appear to help you populate referenced fields in queries automatically. According to this post you can retrieve an author and their stories like this:
db.persons.find({_id: <id>}).populate('stories')
Haven't personally used this but it looks pretty handy.
Mongoose docs for populate: https://mongoosejs.com/docs/populate.html

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.

Mongoose query populate match id of find elements

I'm trying to populate a model with data from another model. Those two models looks like this:
var postSchema = mongoose.Schema({
_comments: { type: mongoose.Schema.Types.ObjectId, ref: 'Comment' },
type: String,
body: String,
});
var commentSchema = mongoose.Schema({
id_post: mongoose.Schema.Types.ObjectId,
body: String,
});
I want to find all posts and populate them with comments that have id_post == _id from founded Posts. Something like this:
Post.find({}).populate({
path: '_comments',
select: 'body',
match: { post_id: Post._id }
options: { limit: 5 }
})
.exec(function (err, posts){...});
First of all, There are few problems in the code you wrote.
If each post may have many comments you should implement one-to-many relationship between your schemas, you can do it by surrounding the comment ref with []
var postSchema = mongoose.Schema({
_comments: [ {type: mongoose.Schema.Types.ObjectId, ref: 'Comment'} ] ,
type: String,
body: String,
});
id_post is not just a field of type ObjectId, it should be written like this:
var commentSchema = mongoose.Schema({
post: { type: mongoose.Schema.Types.ObjectId, ref: 'Post' },
body: String,
});
When saving a new comment make sure you connect it to its post:
var comment = new Comment({
body: "Hello",
post: post._id // assign the _id from the post
});
comment.save(function (err) {
if (err) return handleError(err);
// thats it!
});
Now when you want to find a post and populate its comments you should write something like this:
Post
.find(...)
.populate({
path: '_comments',
select: 'body',
options: { limit: 5 }
})
.exec()
The reason I dropped the match is that match should be used when you want to filter according to a specific field, in your case you can use match to get only comments with type='something'.
populate should work because when you inserted the comment you made the bond to its post.
More information on the right way of using populate can be found here - Mongoose Query Population
Post data should be persisted the following way:
{
body: "some body",
type: "some type",
_comments: [12346789, 234567890, ...]
}
More information about the way the ref will be persisted here - One-to-Many Relationships with Document References

Mongoose Relationship Populate Doesn't Return results

var SecuritySchema = new Mongoose.Schema({
_bids: [{
type: Mongoose.Schema.Types.ObjectId,
ref: 'BuyOrder'
}],
_asks: [{
type: Mongoose.Schema.Types.ObjectId,
ref: 'SellOrder'
}]
});
var OrdersSchema = new Mongoose.Schema({
_security: {
type: Mongoose.Schema.Types.ObjectId,
ref: 'Security'
},
price: {
type: Number,
required: true
},
quantity: {
type: Number,
required: true
}
});
// declare seat covers here too
var models = {
Security: Mongoose.model('Security', SecuritySchema),
BuyOrder: Mongoose.model('BuyOrder', OrdersSchema),
SellOrder: Mongoose.model('SellOrder', OrdersSchema)
};
return models;
And than when I save a new BuyOrder for example:
// I put the 'id' of the security: order.__security = security._id on the client-side
var order = new models.BuyOrder(req.body.order);
order.save(function(err) {
if (err) return console.log(err);
});
And attempt to re-retrieve the associated security:
models.Security.findById(req.params.id).populate({
path: '_bids'
}).exec(function(err, security) {
// the '_bids' array is empty.
});
I think this is some sort of naming issue, but I'm not sure, I've seen examples here and on the moongoose website that use Number as the Id type: http://mongoosejs.com/docs/populate.html
The ref field should use the singular model name
Also, just do:
models.Security.findById(req.params.id).populate('_bids').exec(...
My main suspicion given your snippet at the moment is your req.body.order has _security as a string instead of an array containing a string.
Also, you don't need an id property. Mongodb itself will automatically do the _id as a real BSON ObjectId, and mongoose will add id as a string representation of the same value, so don't worry about that.
While I don't understand your schema (and the circular nature of it?), this code works:
var order = new models.BuyOrder({ price: 100, quantity: 5});
order.save(function(err, orderDoc) {
var security = new models.Security();
security._bids.push(orderDoc);
security.save(function(err, doc) {
models.Security.findById({ _id: doc._id })
.populate("_bids").exec(function(err, security) {
console.log(security);
});
});
});
It:
creates a BuyOrder
saves it
creates a Security
adds to the array of _bids the new orderDoc's _id
saves it
searches for the match and populates
Note that there's not an automatic method for adding the document to the array of _bids, so I've done that manually.
Results:
{ _id: 5224e73af7c90a2017000002,
__v: 0,
_asks: [],
_bids: [ { price: 100,
quantity: 5,
_id: 5224e72ef7c90a2017000001, __v: 0 } ] }

Resources