Mongoose: setting default populate options - javascript

I have a schema in mongoose that references another schema, eg:
OrderSchema = new Schema({
createdBy: {
ref: 'User',
type: ObjectId
}
})
In my user schema, I'm using a soft-delete plugin to keep references. For normal user find queries, the plugin adds a where {deleted: {$ne: true}} to the query using a pre find hook.
When I try to find all orders with createdBy populated, the deleted query is also applied so that any (soft) deleted users are not populated. I can bybass the soft delete query by supplying a "includeDeleted" parameter in population options, this works well for specific queries.
I would like to be able to specify this option in the schema definition so that im not relying on every query to include the options, eg:
// doesnt work, options are not supplied to populate query
OrderSchema = new Schema({
createdBy: {
ref: 'User',
type: ObjectId,
options: {
includeDeleted: true
}
}
})
Virtual populates does work this way:
// WORKS
OrderSchema.virtual('_createdBy', {
ref: 'User',
...,
options: {
includeDeleted: true
}
})
Maybe theres another options to supply default populate options in the schema definition? I havent been able to find anything in the documentation.
Another solution would be to manually lookup the population options in the soft-delete plugin, but that requires me to know if a query is a "population query" in the pre find hook.

https://mongoosejs.com/docs/schematypes.html,
populate: Object, sets default populate options
this may help .
i think you should use it this way
OrderSchema = new Schema({
createdBy: {
ref: 'User',
type: ObjectId,
populate: {
includeDeleted: true
}
}
})

Related

Populating an Object with Information from a Child Object with Different Model (JS/Mongoose)

Here is what I have. I created a project model that references a user model for an array of members.
var ProjectSchema = new mongoose.Schema(
{title: {
type: String,
required: true
},
members: [
{user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users'
}
}],
});
User Schema (I have code that creates a model from both of these schemas)
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
}
});
In a separate file, I want to export the JSON of a found project and include the information of the users in the members array, but I am not sure how to do that. This is the code I have right now.
const project = await Project.findById(req.params.proj_id).populate(
'members'
);
res.json(project);
It has no trouble finding the project but the only information I can get on the members is their id. I tried using for loops to gather the information from members separately using the id that I can get from the project, but the code gets messy and I am hoping to find a simpler way to do it.
You can use mongoose populate query to get all members associated with a project. It should populate array of objects users associated to a project. You should be doing something like this:
const project = await Project.findById(req.params.proj_id)
await project.populate('members').execPopulate()
console.log(project.members)
Mongoose docs for the reference: Populate
You can give the model to your mongoose populate.
const project = await Project.findById(req.params.proj_id)
.populate({
'members',
model: UserModel
})
.exec()
res.json(project);
keep in mind you've to create UserModel just like
const UserModel = mongoose.model('User', userSchema);

In mongoose, how to find records based on value in related collection?

In Mongoose, I have two collections, with one referencing the other. Is it possible to have a find query that selects records based on a value in the other. An example of what I am try to get at (not actual schemas):
const CarModelSchema = new mongoose.Schema({
name: String,
brand: { type: mongoose.Schema.Types.ObjectId, ref: 'CarBrand' }
});
const CarBrandSchema = new mongoose.Schema({
name: String,
country: String
});
I then want to perform a query of the form, without needing to do two queries:
CarModelSchema.find({ 'brand.country': 'GER' });
So far I haven't been able to make this work, so I am wondering whether this can be done in Mongo or whether I am approaching it wrong?
Yes it is possible.
I realize you don't have models for your schemas so add them like this:
const CarModel = mongoose.model('CarModel', CarModelSchema);
const CarBrand = mongoose.model('CarBrand', CarBrandSchema);
Also brands should be defined like this:
brand: [{ type: mongoose.Schema.Types.ObjectId, ref: 'CarBrand' }] //added the brackets
You can then run a find query to filter by country by doing the following:
CarModel.
find(...).
populate({
path: 'brand',
match: { country: { $eq: 'GER' }},
// You can even select the field you want using select like below,
select: 'name -_id',
//Even limit the amount of documents returned in the array
options: { limit: 5 }
}).
exec();
And that should do it, as long as the ObjectIds saved in brands array in the CarModel collection are valid or exist.
Using match in your population will do the work.
CarModel.find()
.populate({
path: 'brand',
model: CarBrandModel,
match: { country: { $eq: 'GER' }},
})
.exec()
Keep in mind you have to define CarModel and CarBrandModel like this:
const CarModel = mongoose.model('CarModel', CarModelSchema)
const CarBrandModel = mongoose.model('CarBrandModel', CarBrandSchema)
Yes, you are doing it wrong.
In CarModelSchema.brand there is not string saved, there is ObjectId saved, therefore you have to find that ObjectId (the reference).
You can do it manually - first finding the CarBrandSchema.find({ 'country': 'GER' }); and then use its ObjectId (=_id), or you can use https://mongoosejs.com/docs/populate.html to populate your CarModel with the CarBrand object.

Update properties that reference another model without duplication

I am trying to create an event list where users can add and remove themselves from events and specify if they are bringing guests with them to that event.
So I have an event schema and a user schema, where the event schema is referencing the user schema. So when a new event is created users can add themselves to that event with their ids.
Now I'm trying to make it so that users can also include guests. How Do I achieve that?
Here's an example
User Schema:
let UserSchema = new mongoose.Schema({
email: {
type: String,
lowercase: true,
unique: true,
required: true
},
name:{
type: String,
require: true
},
password: {
type: String,
required: true
},
...
Event Schema:
let EventSchema = new mongoose.Schema({
date: {
type: Date,
unique: true,
timestamps: true,
required: true
},
title: {
type: String,
require: true
},
// Guest property is ignored
attending: [{
type: mongoose.Schema.Types.ObjectId,
guest: Number, //This is being ingored and never updated
ref: 'User'
}]
})
Second way of defining the relavant part in the schema:
...
//In this example the guest will be added but duplicates will occur
user:[{
guest: Number, // To make it clear this is not referencing anything it's just a number
attending: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}]
How do I update the type and guest properties with addToSet (to prevent duplication) in the above configuration?
Event.findOneAndUpdate({_id:req.body.eventId}, query)
I don't think you understand how mongoose schemas work, you might want to spend some more time on their documentation.
What you have provided as code is what appears to be a field called Events in your Schema which is an array of objects, each object of which has a single field called attending, which itself is required to be an ObjectId type and reference the 'User' collection. There is also a guest property on the field definition which will be ignored by Mongoose as it doesn't understand what you're asking for.
Realize that what this data structure is, is instructions to Mongoose on how to validate and persist your data. It won't generally be updated at runtime for most applications and will not store data directly, again its purpose is to give clues to Mongoose as to how you want the data stored.
/** Edit based on comments and updated question **/
As I said before, you can't directly embed another field into the definition of a field. What you can do is create a mixed type which has both pieces of information, but that will require you to manage things yourself to some degree.
let EventSchema = new mongoose.Schema({
date: {
type: Date,
unique: true,
timestamps: true,
required: true
},
title: {
type: String,
require: true
},
attendees: [{
user : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
guests : Number
}]
})
Anytime anyone is added to the attending list, you'll need to call event.markModified() to make sure it gets saved. If you don't want to allow duplicate users, you'll also need to check that. One way to make sure that happens is to populate() that field when you fetch the event, then just check locally for matches.
/** Edit #2 **/
You can also explicitly create another schema to 'hold' your user and # guests information, which will then create models that Mongoose will watch, and you can apply validation to them via normal Mongoose methods and not worry about dirty checking. That'd look like this:
// in ./models/attendee.js
let AttendeeSchema = new Schema({
user : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
unique : true
},
guests : Number
}
mongoose.model('Attendee', AttendeeSchema);
// in your Events definition
let Attendee = mongoose.model('Attendee');
let EventSchema = new mongoose.Schema({
date: {
type: Date,
unique: true,
timestamps: true,
required: true
},
title: {
type: String,
require: true
},
attendees: [Attendee]
})
/** Edit 3: Now, with queries **/
To insert a new attendee, given an existing event and a known user:
event.attendees.push(new Attendee({user: user, guests: 5}));
event.save(console.log);
To update an existing attendee, you'll need to find the one you're looking for first:
let attendee = event.attendees.find((attendee) => { return attendee._id.toString() === user._id.toString(); });
attendee.guests = 10;
event.save(console.log);

Trying to understand the use of the populate method

I see that one way we use populate is to put one document from another collection into a "parent" collection. I was just going through this question and I was hoping someone could explain the answer to me better. And show me a practical use. Here is an example from the answer.
var PersonSchema = new mongoose.Schema({
t: String
}, {collection: 'persons'});
var User = mongoose.model('User', PersonSchema.extend({
_id: String,
name: String
}));
var ParentSchema = new mongoose.Schema({
s: String
}, {collection: 'parent'});
var Like = mongoose.model('Like', ParentSchema.extend({
_id: String,
user_id: {
type: String,
ref: 'User'
}
}));
Insert Data into DB,
var user = new User({
t: 't1',
_id: '1234567',
name: 'test'
});
var like = new Like({
s: 's1',
_id: '23456789',
});
user.save(function(err, u){
if(err)
console.log(err);
else {
like.user_id = u._id;
console.log(like);
like.save(function(err) {
if (err)
console.log(err);
else
console.log('save like and user....');
});
}
});
Query by
Like.findOne({}).populate('user_id').exec(function(err, doc) {
if (err)
console.log(err);
else
console.log(doc);
});
And the result is
{ _id: '23456789',
__t: 'Like',
user_id: { _id: '1234567', __t: 'User', t: 't1', name: 'test', __v: 0 },
s: 's1',
__v: 0 }
QUESTION
where does __t: 'User' come from?
I was thinking that using populate() or ref that would separate the collections but it looks like at the end the like collection has the users document in it. I think I wanted to use populate so I could make a document smaller.
3.Also if someone really wanted to help explain this to me I have an example that I have been trying to do and I don't know if I should use populate but if I should it would be great if you show me how. Here is the example.
You have
doctors
patients
information about the practice
There could be like a 1000 doctors and lots of patients for each doctor. and the information will be about their practice(like how many employees they have). so I feel that there should be a separation of concern.(one reason is to prevent a single document for a patient from getting to big). So If we're going with the populate method If you could explain how to set it up for this case. I guess I could have a doctor as a parent and a child refs for patients and another child refs for information about practice. so maybe there should be an array of objectId for the patients and an array for Other information
Q1: where does __t: 'User' come from?
Refer to this link.
mongoose now includes schema inheritance and discriminatorKey functionality that breaks mongoose-schema-extend. mongoose now sets the discriminatorKey schema option to __t by default
Q2: I was thinking that using populate() or ref that would separate the collections but it looks like at the end the like collection has the users document in it. I think I wanted to use populate so I could make a document smaller.
It seems you misunderstand the meaning of Population. There are no joins in MongoDB but sometimes we still want references to documents in other collections. This is where population comes in. Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). So populate is not used to make document smaller.
Q3: Doctor, Patient, Practice
Schema could be as following:
var DoctorSchema = new Schema ({
name: String,
// ... other field
});
var PatientSchema = new Schema ({
name: String,
doctor: {type: Schema.ObjectId,
ref: 'Doctor'}
});
var PracticeSchema = new Schema ({
ff: String,
patientId: {type: Schema.ObjectId,
ref: 'Patient'},
doctorId: {type: Schema.ObjectId,
ref: 'Doctor'}
});
As for schema, it is hard to determine which schema is better or not, (with populate or without it). The first thing we should consider is to meet our query requirement, to make the query easy. The design of mongoDB to make the query more efficiently. So our schema should meet it.

Simple Mongodb embedded database structure

i want to build a Mongodb database (Mongoose/Node.js) structure but i face a problem right now. i have two entities. Users and Books and i want to use embedded system(because of lack of joins in mongodb). And my problem is that which of this entities shpuld be an inner value to other.
For example i wiil face this two type of query in my app:
1- Books of an specific user
2- Users of an specific book
Now, Books should be a inner value for Users or contrariwise?
i can do this two:
Users schema:
var schema = new mongoose.Schema({
use_name: String,
user_family: String,
user_books: { type: Schema.Types.ObjectId, ref: 'books' }
});
Or this:
Books Schema:
var schema = new mongoose.Schema({
book_name: String,
book_lang: String,
book_user: { type: Schema.Types.ObjectId, ref: 'users' }
});
which is better? which is standard approach?
if i use both of them, when saving i have to do two save operation. if i has a large database with a lots of collections its gets worse that this...
after a lot of research i find out i have to use embedded system rather that using relation like collections to connect entities to each other, because Mongodb doesn't support joins and has poor support of things like this. embedded system is the correct way for a NoSql database like Mongodb?
Firstly, there's a minor correction that your user_books needs to be an array [].
Secondly, you should only reference one schema into another, otherwise you'll have to add unnecessary complexity in keeping them in sync.
So here's what your schemas should look like:
var UserSchema = new mongoose.Schema({
use_name: String,
user_family: String,
user_books: [{ type: Schema.Types.ObjectId, ref: 'books' }]
});
var BookSchema = new mongoose.Schema({
book_name: String,
book_lang: String,
});
Now "to fetch users that reads an specific book", you'll query like this:
UserSchema.find({ user_books: book._id })
which will give you all users that have BOOK_ID as (at least) one of their books.
If that's all, I guess you don't need population at all then.
Updated on the issue with $elemMatch query not working:
So as it turned out, we don't actually need $elemMatch with referenced docs array, since it's a simple array of _ids.
user // =>
{
_id: 56351c611ca0d2e81274100a
name: ...
books: [56351c611ca0d2e81274100b, 56351c611ca0d2e81274100c, ...]
}
$elemMatch works with array of objects, and would've been in the case of embedded doc:
var BookSchema = new mongoose.Schema({
book_name: String,
book_lang: String,
});
var UserSchema = new mongoose.Schema({
use_name: String,
user_family: String,
user_books: [BookSchema]
});
Since in this case the document would be like this:
user // =>
{
_id: 56351c611ca0d2e81274100a
name: ...
books: [
{ // each book here has an _id:
_id: 56351c611ca0d2e81274100b,
// this is what `$elemMatch: {_id:` would match for
name: ...
// you could do `$elemMatch: {name:`
lang: ...
// or `$elemMatch: {lang:`
}, {
_id: 56351c611ca0d2e81274100c,
name: ...
lang: ...
}, ...
]
}
This is where $elemMatch query would be needed.
UserSchema.find({ user_books: {$elemMatch: {_id: book._id } } })

Categories