Overriding Mongoose _id and using properties directly from the schema - mongodb

I need to create a patient model and override the _id property. I know I can override it by writing a schema like so:
const PatientSchema: Schema = new Schema({
_id: {type: String, required: true},
name: { type: String, required: true },
surname: { type: String, required: true },
provider: { type: Schema.Types.ObjectId, ref: "Provider" },
});
Is there a way to define the _id property inside the schema to reference the name + surname properties?:
_id: name+surname, (?)
or do I have to explicitly define it when creating and saving a new model?:
const patient = new Patient();
patient._id = name+surname;
Also, what should I consider if patients have the same name and surname? What is considered best practice in this case if the _id needs to = name + surname?
Thanks

you could use mongoose pre-save middleware in your schema
Basically, middlewares are functions that are called during the execution of a model query/method.
there are 2 types of middleware :
"pre" middlewares that are executed before the query
"post" middlewares that are executed after the query.
syntax :
schema.pre([method], function (next) {
console.log("pre middleware")
next();
});
schema.post([method], function (next) {
console.log("post middleware")
next();
});
/*[method] can be
"save","updateOne","findOne","findOneAndUpdate",etc...*/
//"pre" will always be executed before "post"
And depending on the method you are going to use, middlewares can change in the value of "this", there are 4 types of these:
where "this" refers to the document E.g."save"
where "this" refers to the query E.g."findOne"
where "this" refers to an aggregate E.g." aggregate"
where "this" refers to the model. E.g. "insertMany"
Solution :
const PatientSchema = new Schema({
_id: { type: String },
name: { type: String, required: true },
surname: { type: String, required: true },
provider: { type: Schema.Types.ObjectId, ref: "Provider" },
});
//will change _id before save
PatientSchema.pre("save", async function (next) {
try {
const patient = this; //the target document
patient._id = patient.name + " " + patient.surnam
next()
}
catch (err) { next(err) }
});
module.exports = model("Patients", PatientSchema);
and when you create a patient it will have the id based on the combination of their first and last name
const patient = new Patient({
name: "John",
surname: "smith"
})
await patient.save();
result : {
_id: "John smith ";
name: "John";
surname: "smith";
__v: 0;
}
[ Edit ]
if you want your id to be unique you could create an ObjectId and concatenate
it to the final id
const { ObjectId } = require("mongodb");
PatientSchema.pre("save", async function (next) {
try {
const patient = this; //the target document
const objectId = ObjectId();
patient._id = `${patient.name} ${patient.surname} ${objectId}`;
next();
} catch (err) {
next(err);
}
});

Related

MongoDB, collection name appears to be wrong

When I try to call this request
const presidentModel = require('./modules/president.js')
app.get('/president', (req, res) => {
presidentModel.find({}, (err, result) => {
if (err) {
console.log(err)
} {
console.log(result)
}
})
})
It only returns an empty array []
then it creates a new collection with the name 'presidents'
Here is my Schema
const mongoose = require("mongoose")
const presidentSchema = new mongoose.Schema({
nickname: {
type: String,
required: true
},
fullname: {
type: String,
required: true,
},
votes: {
type: Number,
required: true
}
})
const president = mongoose.model("president", presidentSchema)
module.exports = president
The request should return the data on collection "president" but what it does is it creates a new collection with the name "presidents". I don't know where is it coming from tho
it is good practice to have your collections as plural, and therefore mongoose implicitly tries to make collections plural (as there are multiple items to be stored in them).
To override this, you can pass a third parameter to .model() with the name of the collection:
const president = mongoose.model("president", presidentSchema, "president")

Load nested virtual during mongodb query

I'm new to using a key other than ObjectId to link data from other collections. Currently, I have appointments with various other data I'd like to bring in so I can evaluate whether payment is due or not.
My query worked, except it doesn't bring in the plan information for each patient. I understand that it makes a separate query for each populate, so I'd have to do it after I populate the patient information with populate('patientID'):
const appts = await Appt.find(searchParams)
.populate('patientID')
.populate('patientID.plan')
.populate('status')
.populate('type')
.sort({ scheduled: -1 });
The above doesn't work for bringing in the nested JSON of the plan information, but it DOES work for bringing in the patient collection, status, and type. Only patientID.plan populate doesn't work.
My schemas:
const familySchema = new mongoose.Schema({
ID: {
type: Number,
index: true
},
family: String
});
const paymentplanSchema = new mongoose.Schema({
ID: {
type: Number,
index: true
},
plan: String,
planamt: Number
});
const patientSchema = new mongoose.Schema({
ID: {
type: Number
},
familyID: Number,
first: String,
last: String,
careplanID: Number,
otherData: variousTypes
});
patientSchema.virtual('plan', {
ref: 'PaymentPlan', // The model to use
localField: 'careplanID', // Find people where `localField`
foreignField: 'ID' // is equal to `foreignField`
});
patientSchema.pre('find', function() {
this.populate('plan');
});
const typeSchema = new mongoose.Schema({
ID: Number,
appttype: String,
abbr: String,
amt: Number,
code: String,
length: Number
});
const statusSchema = new mongoose.Schema({
ID: Number,
status: String
});
const apptSchema = new mongoose.Schema({
ID: Number,
patientID: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Patient'
},
oldPatientID: Number,
status: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ApptStatus'
},
type: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ApptType'
},
scheduled: Date,
note: String
});
mongoose.model('Appt', apptSchema);
mongoose.model('ApptStatus', statusSchema);
mongoose.model('ApptType', typeSchema);
mongoose.model('Patient', patientSchema);
mongoose.model('PaymentPlan', paymentplanSchema);
How do I get the patient data to load WITH the plan data? I don't get what I'm doing wrong, and I've got other things I'd like to connect in this way (via index instead of ObjectId) but just don't get what I'm doing wrong.
UPDATED TO ADD MORE DETAIL:
My query on the backend to get the appointments is this:
module.exports.search = async (req, res) => {
console.log('GET the appts');
const searchParams =
req.params.query === 'today'
? { scheduled: { $gt: new Date(dayStart), $lt: new Date(dayEnd) } }
: req.body;
console.log(searchParams);
try {
const appts = await Appt.find(searchParams)
.populate({
path: 'patientID',
populate: { path: 'plan' }
})
.populate('status')
.populate('type')
.sort({ scheduled: -1 });
if (!appts) {
console.log(`No appointments found`);
}
appts.forEach(p => {
const patient = p.patientID ? p.patientID.nickname : 'NONE';
const plan =
p.patientID && p.patientID.plan ? p.patientID.plan.planamt : 0;
console.log(patient, plan);
});
console.log(appts.length, 'appts found');
res.send(appts);
} catch (err) {
console.log(`Error`, err);
return res.status(500).send(err);
}
};
In the console, It's logging correctly (example):
CarF 60
8075 'appts found'
In the frontend, all the objects are populated EXCEPT patientID.plan. The patientID object does not include a plan field on any of the entries. patientID, status, and type all populated the corresponding objects.
WHY is this logging on the backend, but not visible on the frontend?
You should be able to do it by passing a path option to populate():
const appts = await Appt.find(searchParams)
.populate('patientID')
.populate({
path: 'patientID',
populate: {path: 'plan'}
})
.populate('status')
.populate('type')
.sort({ scheduled: -1 });
See https://mongoosejs.com/docs/populate.html#deep-populate in official docs

Mongoose Model containing arrays

First of all, I'm pretty new to MongoDB, Mongoose and Express. I'm trying to create a Mongoose model that has two arrays that I want to populate with multiple objects called itemSchema but I'm not sure how I'm supposed to update the array short of using findOneAndUpdate but since my array is initially empty there is no initial ID until a document is created. With the method that I have defined below - any already existing data in the food array is replaced by a new array. Below is my model -
const mongoose = require("mongoose");
const itemSchema = new mongoose.Schema({
id: String,
drinks: [
{
id: String,
name: {
type: String,
required: true
},
price: {
type: String,
required: true
},
description: {
type: String
},
date: {
type: Date,
default: Date.now
}
}
],
food: [
{
name: {
type: String,
required: true
},
price: {
type: String,
required: true
},
description: {
type: String
},
date: {
type: Date,
default: Date.now
}
}
]
});
module.exports = Item = mongoose.model("item", itemSchema);
I don't know if I'm defining the schema correctly. I know that it isn't very DRY ( since both arrays contain the same types ) but since I believe this is such a simple use case I don't want to define two separate schema for Drink and Food when I could just create one Schema.
router.post("/food", async (req, res) => {
try {
// Create an object from the request that includes the name, price and description
const newItem = {
name: req.body.name,
price: req.body.price,
description: req.body.description
};
// pass the object to the Items model
let item = new Items(newItem);
// add to the comments array
console.log("the new comment ", newItem);
item.food.unshift(newItem);
item.save();
// return the new item array to confirm adding the new item is working.
res.json(item);
} catch (error) {
// Display an error if there is one.
res.send(404).json(error);
}
});
The issue with the approach above comes from how I'm supposed to update the array. I defined the function below to update the food array for example but a new array gets created every single time. I believe that is has to do with not having Id param that I can use to provide the model with the findOneAndUpdate method. Any help would be greatly appreciated. Thank you in advance.
As per my opinion you can make your schema more simple as in your food and drinks array all the fields are same so you can simply take one more field as itemType and then you do not need to take two separate sub docs for food and drinks.
const mongoose = require("mongoose");
const itemSchema = new mongoose.Schema({
id: String,
itemType: { type: String }, // FOOD or DRINK
name: {
type: String,
required: true
},
price: {
type: String,
required: true
},
description: {
type: String
},
date: {
type: Date,
default: Date.now
}
});
If you wants to know more about updating in array with findOneAndUpdate() then i will explain two simple task to perform with this function.
CASE:1 If array of your sub doc is empty then you can push new document in your sub doc as below:
var updatedData = await Model.findOneAndUpdate(
{
_id: doc._id
},
{
$push: {
drinks: {
name: drink.name,
price: drink.price,
description: drink.description,
}
},
},{ new: true }
).lean().exec();
CASE:2 If you want to update existing sub doc by sub doc id then you can update as below:
var updatedData = await Model.findOneAndUpdate(
{
'drink._id': drinkId
},
{
$set: {
'drink.$.name': drink.name,
'drink.$.price': drink.price,
'drink.$.description': drink.description,
},
},{ new: true }
).lean().exec();

Mongoose populate array

I can't get mongoose to populate an array of objects.
The schema is as follows:
var topOrganisationsForCategorySchema = new mongoose.Schema({
category: String,
topOrganisations: [{
organisation: {
type: mongoose.Schema.Types.ObjectId,
ref: 'organisation'
},
model: mongoose.Schema.Types.Mixed
}]
});
module.exports = mongoose.model('topOrganisationsForCategory', topOrganisationsForCategorySchema);
I would like all of the objects in this collection populated with an array of organisations.
Here is what i have tried
TopOrganisationsForCategory
.find()
.exec(function(err, organisation) {
var options = {
path: 'topOrganisations.organisation',
model: 'organisation'
};
if (err) return res.json(500);
Organisation.populate(organisation, options, function(err, org) {
res.json(org);
});
});
var organisationSchema = new mongoose.Schema({
name: String,
aliases: [String],
categories: [String],
id: {
type: String,
unique: true
},
idType: String
});
organisationSchema.index({
name: 'text'
});
module.exports = mongoose.model('organisation', organisationSchema);
You're close but a couple notes:
The following code assumes you also have a schema/model declaration for Oranisation.
I am not sure if the model property is meant as an option (which would be invalid) or actually is a property of topOrganisations.
So, I left model in as it shouldn't cause any issues but be aware that if you were using it as an option it is not doing what you might think it is.
// Assuming this schema exists
var organisationSchema = new mongoose.Schema({...});
var topOrganisationsForCategorySchema = new mongoose.Schema({
category: String,
topOrganisations: [{
organisation: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Organisation' // Model name convention is to begin with a capital letter
}
// Is `model` supposed to be for the ref above? If so, that is declared in the
// Organisation model
model: mongoose.Schema.Types.Mixed
}]
});
// Assuming these model definitions exist
var Organisation = mongoose.model('Organisation', organisationSchema);
var TopOrganisationsForCategory = mongoose.model('TopOrganisationsForCategory', TopOrganisationsForCategorySchema);
// Assuming there are documents in the organisations collection
TopOrganisationsForCategory
.find()
// Because the `ref` is specified in the schema, Mongoose knows which
// collection to use to perform the population
.populate('topOrganisations.organisation')
.exec(function(err, orgs) {
if (err) {
return res.json(500);
}
res.json(orgs);
});

Does Mongoose Actually Validate the Existence of An Object Id?

I like the validation that comes with Mongoose. We are trying to figure out whether we want to use it, and put up with the overhead. Does anyone know if providing a reference to the parent collection when creating a mongoose schema, (in the child schema, specify the object id of the parent object as a field,) does this then mean that every time you try to save the document it checks the parent collection for the existence of the refereneced object id?
I'm doing it with middleware, performing a search of the element on validation:
ExampleSchema = new mongoose.Schema({
parentId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Example'
}
});
ExampleModel = mongoose.model('Example', ExampleSchema);
ExampleSchema.path('parentId').validate(function (value, respond) {
ExampleModel.findOne({_id: value}, function (err, doc) {
if (err || !doc) {
respond(false);
} else {
respond(true);
}
});
}, 'Example non existent');
I'm using mongoose-id-validator. Works good
var mongoose = require('mongoose');
var idValidator = require('mongoose-id-validator');
var ReferencedModel = new mongoose.Schema({name: String});
var MySchema = new mongoose.Schema({
referencedObj : { type: mongoose.Schema.Types.ObjectId, ref: 'ReferencedModel'},
referencedObjArray: [{ type: mongoose.Schema.Types.ObjectId, ref: 'ReferencedModel' }]
});
MySchema.plugin(idValidator);
No, an ObjectId field that's defined in your schema as a reference to another collection is not checked as existing in the referenced collection on a save. You could do it in Mongoose middleware, if needed.
I found this thread very helpful and this is what I came up with:
This Middleware (I think its one anyway please let me know if not) I wrote checks the referenced model for the id provided in the field.
const mongoose = require('mongoose');
module.exports = (value, respond, modelName) => {
return modelName
.countDocuments({ _id: value })
.exec()
.then(function(count) {
return count > 0;
})
.catch(function(err) {
throw err;
});
};
Example model:
const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
const Schema = mongoose.Schema;
const User = require('./User');
const Cart = require('./Cart');
const refIsValid = require('../middleware/refIsValid');
const orderSchema = new Schema({
name: { type: String, default: Date.now, unique: true },
customerRef: { type: Schema.Types.ObjectId, required: true },
cartRef: { type: Schema.Types.ObjectId, ref: 'Cart', required: true },
total: { type: Number, default: 0 },
city: { type: String, required: true },
street: { type: String, required: true },
deliveryDate: { type: Date, required: true },
dateCreated: { type: Date, default: Date.now() },
ccLastDigits: { type: String, required: true },
});
orderSchema.path('customerRef').validate((value, respond) => {
return refIsValid(value, respond, User);
}, 'Invalid customerRef.');
orderSchema.path('cartRef').validate((value, respond) => {
return refIsValid(value, respond, Cart);
}, 'Invalid cartRef.');
orderSchema.path('ccLastDigits').validate(function(field) {
return field && field.length === 4;
}, 'Invalid ccLastDigits: must be 4 characters');
orderSchema.plugin(uniqueValidator);
module.exports = mongoose.model('order', orderSchema);
I'm a very new dev so any feedback is greatly valued!
You can try https://www.npmjs.com/package/lackey-mongoose-ref-validator (I'm the developer)
It also prevents deletion if the reference is used on another document.
var mongooseRefValidator = require('lackey-mongoose-ref-validator');
mongoSchema.plugin(mongooseRefValidator, {
onDeleteRestrict: ['tags']
});
It's an early version, so some bugs are expected. Just fill in a ticket if you find any.
I know this is an old thread but I had the same problem and I came up with a more "modern" solution.
I'm not an expert myself, hope I'm not misleading anyone, but this seems to work:
for example, in a simple "notes" schema, which contains a user field:
const noteSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
text: String
});
here's the middleware that checks if the userId exists:
noteSchema.path('user').validate(async (value) => {
return await User.findById(value);
}, 'User does not exist');