Mongoose Model ObjectId References Not Working - node.js

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

Related

How to add schema to another schema array?

I have address schema and customer schema. I have a field address array inside my customer schema. I will be sending an adress model as my req body and customer id as req param. How can I save that adress to adresses array which is declared inside customer schema?
This is my Customer Schema
const customerSchema = mongoose.Schema ({
_id: mongoose.Schema.Types.ObjectId,
name: String,
phone_number: String,
password: String,
type:{type:String,enum:['admin','user']},
adresses:['Adress'],
orders:[{type: mongoose.Schema.Types.ObjectId, ref: 'Order'}]
});
This is my Adress Schema
const addressSchema= mongoose.Schema({
_id:mongoose.Types.ObjectId,
postalCode:Number,
city:String,
district:String,
neighborhood:String,
streetNumber:String,
no:Number,
buildingName:String,
apartmentNumber:Number,
floor:Number,
coordinates:{
latitude:Number,
longitude:Number
},
addressName:String,
customerId: {type: mongoose.Schema.Types.ObjectId,ref:'Customer'}
});
I could not figure out how am ı going to do this. I am finding the customer which I will going to push my address to like this.
This is how I get the specific customer
Customer.find({_id:req.params.customerId},(err,data)=>{
if(err) return next(err);
else{
//What I am going to do here?
}
});
First what type should I put inside the addresses array which is inside Customer Schema?
Second after finding the customer which I am going to add address to, what should I do? Mongoose 5.4.11 documentation was not enough for me. This link seemed what I needed but I did not figure out how to accomplish this problem.
https://mongoosejs.com/docs/subdocs.html
OK, so basically what You look for is: association. You need to establish a connection between User and Customer model.
We will say that Address belongs to the User and User reference Address object by for example id.
Consider an example:
const personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
const storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);
Now let's try to assign an author to particular created story:
const author = new Person({
_id: new mongoose.Types.ObjectId(),
name: 'Ian Fleming',
age: 50
});
author.save(function (err) {
if (err) return handleError(err);
const 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 define relation between Story and Person, it is easy to manipulate references between them.
In your case you should define a reference in models and then you are able to manipulate the fields:
Customer.findOne({_id:req.params.customerId}, function(error, customer) {
if (error) {
return handleError(error);
}
customer.address = newAddress; // set customer's address to the desired address
// console.log(customer.address.city);
});
Check doc for more.

Mongoose populate not working as expected

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

Mongoose: Referencing a subdocument from another subdocument AND population

I have a document model called school which in turn has two subdocuments:
students
class
Each student belongs to one class.
var classSchema = new Schema({
name: {type : String}
});
var studentSchema = new Schema({
name : {type : String, required:true},
class: { type: ObjectId, ref: 'Institute.classes' },
});
var schoolSchema = new Schema({
name : {type : String, required:true},
students : [studentSchema],
classes : [classSchema]
});
var School = mongoose.model('School', schoolSchema);
Now when I am fetching a student, I want to populate the class as well. I am doing this as:
var school_id = req.params.school_id;
var student_id = req.params.student_id;
School
.findOne({'_id': school_id, 'students._id': student_id})
.populate({path: 'students.class'})
.exec(function (err, data) {
done(err, data);
});
When I do this, I get following error:
"message": "Schema hasn't been registered for model \"School.classes\".\nUse mongoose.model(name, schema)",
"name": "MissingSchemaError"
Is is possible to populate a subdocument from another subdocument?
Any help is appreciated. Thanks :)

Mongoose one-to-many - not quite sure how to implement it

I'm relatively new to Mongoose (2 days at it) and I want to make a one-to-many relationship, as in one person can come from one country, one country has many people.
So, this is what I've got:
var userSchema = new Schema({
name: String,
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
country: {
type: Schema.Types.ObjectId,
ref: 'Country'
}
});
var User = mongoose.model('Person', userSchema);
var countrySchema = new Schema({
name: {
type: String,
required: true,
unique: true
},
created_at: Date,
updated_at: Date,
people: [{
type: Number,
ref: 'User'
}]
});
var Country = mongoose.model('Country', countrySchema);
var UK = new Country({
name: 'UK'
});
usa.save(function(err) {
var user = new User({
username: 'James',
password: 'Bond',
country: UK._id
});
user.save(function(err) {
});
});
Now I have two questions: 1) I've seen that ref can sometimes be an ObjectId or just a number - what's the differences? 2) when saving the data, in my case, I saved country to a person (by _id), how do I save a person to a country? Should I update the instance of the model?
Thanks
UPDATE:
since this question has been marked as a duplicate, let me rephrase the question: consider the official example in this link: http://mongoosejs.com/docs/populate.html
The idea is that one person has many stories, and one story has one author (person). So, the saving would be as follows:
var aaron = new Person({ _id: 0, name: 'Aaron', age: 100 });
aaron.save(function (err) {
if (err) return handleError(err);
var story1 = new Story({
title: "Once upon a timex.",
_creator: aaron._id // assign the _id from the person
});
story1.save(function (err) {
if (err) return handleError(err);
// thats it!
});
});
That's from the official documentation - my question is, where or how do we save story1 to the Author? Author is created before the Story, so, shouldn't the Author be updated with story1._id???
UPDATE 2:
I figured out that if I use only type: Schema.Types.ObjectId and never type: Number, that I can do just this:
var aaron = new Person({ _id: 0, name: 'Aaron', age: 100 });
var story1 = new Story({
title: "Once upon a timex.",
_creator: aaron._id // assign the _id from the person
});
aaron.stories.push(story1._id);
aaron.save(function (err) {
if (err) return handleError(err);
});
story1.save(function (err) {
if (err) return handleError(err);
// thats it!
});
This actually works in a dummy example... are there any problems if there were too many posts in a request that IDs could have get lost/duplicated? What is the shortcoming of this approach?
1) I've seen that ref can sometimes be an ObjectId or just a number - what's the differences?
Please refer to this question Why do they use an ObjectId and a Number in the Mongoose Population example?
where or how do we save story1 to the Author
aaron.save(function (err) {
if (err) return handleError(err);
var story1 = new Story({
title: "Once upon a timex.",
_creator: aaron._id // assign the _id from the person
});
story1.save(function (err) {
if (err) return handleError(err);
// save id of story1 into person here, also you should use `update` operation with `$push` operator.
aaron.stories.push(story1._id);
aaron.save(function(err){
if (err)
handleError(err);
else
console.log('save person successfully...');
})
});
});
The results
> db.stories.find()
{ "_id" : ObjectId("56f72f633cf1e6f00159d5e7"), "title" : "Once upon a timex.", "_creator" : 0, "fans" : [ ], "__v" : 0 }
> db.people.find()
{ "_id" : 0, "name" : "Aaron", "age" : 100, "stories" : [ ObjectId("56f72f633cf1e6f00159d5e7") ], "__v" : 1 }

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