Is there anyway to populate distinct values using mongoose - javascript

I am trying to populate my user property which is an array of duplicate id's and a reference to the User Schema.
schema={
// Array of duplicate userId
user: [{
type: mongoose.Schema.Types.ObjectID,
ref:'Users'
}]
}
Can I populate only distinct id's using mongoose here?

Yes, you can by following method.
db.collection_name.ensureIndex( { user : 1 }, { unique:true, sparse:true } );

Related

Query builder how to use "where" clause on populated documents

I'm having some issues using the Mongoose queries. I'm just trying to find documents in a collection using the where clause.
Unfortunately, it seems you can't use the where clause on populated documents.
This is the collection schema
const schema: Schema = new Schema({
game: { type: Schema.Types.ObjectId, ref: 'Game', required: true, index: true },
players: [{ type: Schema.Types.ObjectId, ref: 'Player', required: true, index: true }],
scores: [{ type: String }],
resultDate: { type: Date}
});
I'm trying to find all games where type = 1v1, so I tried this
let query = Matchs.find()
.populate('game')
.populate('players')
.where('game.name').equals('Trackmania')
const matchs: Match[] = await query.exec();
This returns an empty array.
Notes
Removing the where clause returns the correct results (all Matchs)
Any where clause on game returns an empty array
I'd like to use query builder instead of passing a json because I use some parameters to define what I am querying
I read that where clause didn't work on nested documents, but there must be a way to do this right ? Am I missing something ?
You can Use Lookup and Match instead of Where.
MatchesModel.aggregate([
const GameName = req.query;
{"$lookup":{
"from":"games", // name of the foreign collection
"localField":"game",
"foreignField":"_id",
"as":"game"
}},
{"$lookup":{
"from":"players",
"localField":"players",
"foreignField":"_id",
"as":"players"
}},
{"$match":{
"game.name":{
"$eq": GameName
}
}}
])

Add to an array - sub-document without duplicate field values

I am trying to add an object to an array in MongoDB. I don't want it to be duplicated.
I am trying to update the user read array by using $addToset in findOneAndUpdate. However, it is inserting duplicate because of timestamp; the timestamp is an important property. I can't negate it. Can I insert based on key like userId? Please let me know.
{
_id: 'ddeecd8b-79b5-437d-9026-d0663b53ad8d',
message: 'hello world notification',
deliverToUsersList: [ '123-xxx-xx', '124-xxx-xx']
userRead: [
{
isOpened: true,
userId: '123-xxx-xx'
updatedOn: new Date(Date.now()).toISOString()
},
{
isOpened: true,
userId: '124-xxx-xx'
updatedOn: new Date(Date.now()).toISOString()
}
]
}
Add an index to the field userId and enable 'Avoid duplicates' in index settings.
I use Robo3T client to do that.
To add new objects without duplicate information into the userRead array, you have check for the duplicate information in the update method's query filter. For example, the following code will not allow adding new object with duplicate userId field value.
new_userId = "999-xxx-xx"
new_doc = { userId: new_userId, isOpened: true, updatedOn: ISODate() }
db.test_coll.findOneAndUpdate(
{ _id: 'ddeecd8b-79b5-437d-9026-d0663b53ad8d', "userRead.userId": { $ne: new_userId } },
{ $push: { "userRead" : new_doc } },
)

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.

What is the best way to keep track of changes of a document's property in MongoDB?

I would like to know how to keep track of the values of a document in MongoDB.
It's a MongoDB Database with a Node and Express backend.
Say I have a document, which is part of the Patients collection.
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": "Burn fat"
}
Then I edit the "objective" property, so the document results like this:
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": "Gain muscle"
}
What's the best/most efficient way to keep track of that change? In other words, I would like to know that the "objective" property had the value "Burn fat" in the past, and access it in the future.
Thanks a lot!
Maintaining/tracking history in the same document is not all recommended. As the document size will keep on increasing leading to
probably if there are too many updates, 16mb document size limit
Performance degrades
Instead, you should maintain a separate collection for history. You might have use hibernates' Javers or envers for auditing for your relational databases. if not you can check how they work. A separate table (xyz_AUD) is maintained for each table (xyz). For each row (with primary key abc) in xyz table, there exist multiple rows in xyz_AUD table, where each row is version of that row.
Moreover, Javers also support MongoDB auditing. If you are using java you can directly use it. No need to write your own logic.
Refer - https://nullbeans.com/auditing-using-spring-boot-mongodb-and-javers/
One more thing, Javers Envers Hibernate are java libraries. But I'm sure for other programming languages also, similar libraries will be present.
There is a mongoose plugin as well -
https://www.npmjs.com/package/mongoose-audit (quite oudated 4 years)
https://github.com/nassor/mongoose-history#readme (better)
Maybe you can change the type of "objective" to array and track the changes in it. the last one of the array is the latest value.
Maintain it as a sub-document like below
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": {
obj1: "Gain muscle",
obj2: "Burn fat"
}
}
You can also maintain it as an array field but remember, mongodb doesn't allow you to maintain uniqueness in an array field and if you plan to index the "objective" field, you'll have to create a multi key index
I think the simplest solution would be to use and update an array:
const patientSchema = new Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
objective: { type: String, required: true }
notes: [{
date: { type: Date, default: Date.now() },
note: { type: String, required: true }
}],
});
Then when you want to update the objective...
const updatePatientObjective = async (req, res) => {
try {
// check if _id and new objective exist in req.body
const { _id, objective, date } = req.body;
if (!_id || !objective) throw "Unable to update patient's objective.";
// make sure provided _id is valid
const existingPatient = await Patient.findOne({ _id });
if (!existingPatient) throw "Unable to locate that patient.";
// pull out objective as previousObjective
const { objective: previousObjective } = existingPatient;
// update patient's objective while pushing
// the previous objective into the notes sub document
await existingPatient.updateOne({
// update current objective
$set { objective },
// push an object with a date and note (previouseObjective)
// into a notes array
$push: {
notes: {
date,
note: previousObjective
},
},
}),
);
// send back response
res
.status(201)
.json({ message: "Successfully updated your objective!" });
} catch (err) {
return res.status(400).json({ err: err.toString() });
}
};
Document will look like:
firstName: "John",
lastName: "Smith",
objective: "Lose body fat.",
notes: [
{
date: 2019-07-19T17:45:43-07:00,
note: "Gain muscle".
},
{
date: 2019-08-09T12:00:38-07:00,
note: "Work on cardio."
}
{
date: 2019-08-29T19:00:38-07:00,
note: "Become a fullstack web developer."
}
...etc
]
Alternatively, if you're worried about document size, then create a separate schema for patient history and reference the user's id (or just store the patient's _id as a string instead of referencing an ObjectId, whichever you prefer):
const patientHistorySchema = new Schema({
_id: { type: Schema.Types.ObjectId, ref: "Patient", required: true },
objective: { type: String, required: true }
});
Then create a new patient history document when the objective is updated...
PatientHistory.create({ _id, objective: previousObjective });
And if you need to access to the patient history documents...
PatientHistory.find({ _id });

Update an array of objects mongoose

I know that this question might be beginner level but I haven't find anything yet.
I would like to update an array of objects with mongoose. I am interested in updating one object from the users array according to the index.
Usually one user is getting changed at a time.
Here is my schema:
_id: Schema.Types.ObjectId,
name: { type: String, required: true },
gm: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
users: [],
I want to update an object in the users array which is like this:
{
id:"5bcb7c7ff9c5c01b9482d244",
gm:"5bcb7c7ff9c5c01b9482d246",
name:"room 1"
users: [
{
id:"5bcb7c7ff9c5c01b9482d243",
stats:{
power:10,
mobility: 5,
vitality: 20
},
bag:{itemSlot1: "Knife",itemSlot2:"Sword" }
},
{
id:"5bcb7c7ff9c5c01b9482d241",
stats:{
power:10,
mobility: 5,
vitality: 20
},
bag:{itemSlot1: "Knife",itemSlot2:"Sword" }
]
}
I want to perform a patch or a post request to update one user each time from the user array. i am getting the id of the user from req.body to match it with my db.
My request is like this:
I would like to update based on a request like this:
data = {
stats={
power:"10",
vitality:"20"
}
}
Thanks in advance,
Cheers
You can do an update like this:
YourSchema.update({
'users.id': '5bcb7c7ff9c5c01b9482d243'
}, {
$set: {
'users.$.stats': data.stats
}
})
Which would update the first user with id 5bcb7c7ff9c5c01b9482d243 power stats to 20
This is using the update with the $ positional operator to update the element in the array.
Just have it set up in your post/patch request.

Categories