How can I restructure my database? - mongodb

I have a collection with embedded documents. This worked fine when updating a document within a document, but when I had to update a document within a document within a document problems arose. To make this work I would need to use a double '$' and this is not possible.
This works:
{$push: {
"progress.$.exercise":{
"exercise":exercise
}
}
This is where the problem comes in:
{$push: {
"progress.$.exercise.$.workout":{
"_set":_set,
"reps":reps,
"weight":weight
}
}
Since dobbel $ can't be done and there seems to be no way (that I can find) to do it with elemMatch. I can only come to the conclusion that the way I have structured my Database is wrong....
Here are my Schemas:
var Workout = new mongoose.Schema({
_set : {
type: String
},
reps: {
type: String
},
weight: {
type: String
}
});
var Exercise = new mongoose.Schema({
exerciseName : {
type: String
},
workout : [Workout]
});
var Progress = new mongoose.Schema({
date : {
type: String
},
name : {
type: String
},
exercise : [Exercise]
});
var UserSchema = mongoose.Schema({
username: {
type: String,
index:true
},
password: {
type: String
},
email: {
type: String
},
name: {
type: String
},
progress : [Progress]
});
var User = module.exports = mongoose.model('User', UserSchema);
It seems I need to restructure my database, if so how should I do this?
Do I need to make more collections?
I'm used to relational databases and if I where to make collections out of all of them it would start to look like that.

Related

Overriding Mongoose _id and using properties directly from the schema

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

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

Mongoose: how to set a schema-field to be the ID?

Given the following schema:
var UserSchema = new Schema({
, email : { type: String }
, passwordHash : { type: String }
, roles : { type: [String] }
});
I'd like email to be the key.
How can I define this?
I could do:
var UserSchema = new Schema({
, _id: { type: String }
, passwordHash : { type: String }
, roles : { type: [String] }
});
so MongoDB would recognize it as the id-field, and adapt my code to refer to _id instead of email but that doesn't feel clean to me.
Anyone?
Since you're using Mongoose, one option is to use the email string as the _id field and then add a virtual field named email that returns the _id to clean up the code that uses the email.
var userSchema = new Schema({
_id: {type: String},
passwordHash: {type: String},
roles: {type: [String]}
});
userSchema.virtual('email').get(function() {
return this._id;
});
var User = mongoose.model('User', userSchema);
User.findOne(function(err, doc) {
console.log(doc.email);
});
Note that a virtual field is not included by default when converting a Mongoose doc to a plain JS object or JSON string. To include it you have to set the virtuals: true option in the toObject() or toJSON() call:
var obj = doc.toObject({ virtuals: true });
var json = doc.toJSON({ virtuals: true });