How do you create a schema that can change depending on the user's input? - mongodb

Just for funsies, I'm creating a similar app to UberEAT.
The database would probably hit the collection limit, so I created two separate collections.
First, I need the restaurant info, so I structured it like this:
var PlaceSchema = new Schema({
id: Number,
menuID: Number,
name: String,
cuisine_type: String, (eg; thai, korean, sushi ,etc)
address: String,
opening_time:Date,
closing_time:Date
});
Then I need to show the menu the restaurant have, the problem is each restaurant have a different number of dishes and prices, so the quantity is not constant.
This is how I structured my menu data, but I know it's completely wrong
var MenuSchema = new Schema({
id: Number,
parentID: Number,
name: String,
price: Number
});
any guidance would be great!

How about this to get started
for restuarant
var PlaceSchema = new Schema({
id: Number,
menusId: [Number],
name: String,
cuisine_type: String, (eg; thai, korean, sushi ,etc)
address: String,
opening_time:Date,
closing_time:Date
});
and for menus
var MenuSchema = new Schema({
id: Number,
placeId: Number,
dishes: [{name: String, price: Number}] //add any other dish related stuff here, like spiceness, app, entree, desert chef special etc etc
});
Now you can have multiple menus for a place, and multiple dishes in a menu.
You can even do array of places Id in the menu, which could be a case if bunch of restaurants same menu.
var MenuSchema = new Schema({
id: Number,
placesId: [Number],
dishes: [{name: String, price: Number}] //add any other dish related stuff here, like spiceness, chef special etc etc
});
Updated for drinks with default
here is an updated menu schema with drinks
var MenuSchema = new mongoose.Schema({
id: Number,
placeId: Number,
dishes: [{name: String, price: Number}], //add any other dish related stuff here, like spiceness, app, entree, desert chef special etc etc
drinks:[{
name: String,
price: Number
}]
});
Like I said in the comment, mongoose doesn't really have any default for arrays.
But we can write a pre save hook to take care of this.
MenuSchema.pre("save", function (next) {
if (!this.drinks.length) {
this.drinks.push({name: "No Drinks", price: 0}); //null or 0 whichever you pefer
}
next();
});
This will default in a default object as described above.
P.S. This defaulting drinks as "No Drinks" doesn't feel right though. Maybe keep it as an empty array, and when we need to use it in the code, to display or whatever just check for the length.
if (!menu.drinks.length) {
console.log("No drinks");
}

Related

Initial POST to MongoDB creates an empty object within an array I haven't added anything to

Using the MERN stack I am able to add a document (a Property Schema in this case) via Mongoose. The issue is one of the Property Keys (Rooms in this case) is an Array of Objects. When I initially create the Property I don't send any data regarding the Rooms but a blank Object is created, albeit with a MongoDB _id?
I thought Mongoose prevented creating blank Objects / Arrays if no data was sent or am I confusing matters? Why is it happening? And is there a way to prevent this?
Just to be clear when I initially create the Property I'm sending no information and I don't even reference the rooms array in the data sent from axios.
Here is my Schema:
const propertySchema = new Schema({
propertyId: String,
propertyName: String,
rooms: [
rId: String,
type: String,
class: String,
view: String,
price: String
]
})
Arrays implicitly have a default value of [] (empty array).
But you can prevent it by giving a default: undefined option like this:
const propertySchema = new Schema({
propertyId: String,
propertyName: String,
rooms: {
type: [
new Schema({
rId: String,
type: String,
class: String,
view: String,
price: String,
}),
],
default: undefined,
},
});
Docs (in the Arrays section)
What I realised was I had two controllers, postPropertyController and patchPropertyController. As described in the question when I post the property for the first time I don't include anything in the req.body about the rooms. However, in the postPropertyController I was still doing this...
const propertySchema = new Schema({
propertyId: String,
propertyName: String,
rooms: [
rId: String,
type: String,
class: String,
view: String,
price: String
]
})
What I needed to do to clear the empty object in the rooms array was this...
const propertySchema = new Schema({
propertyId: String,
propertyName: String,
rooms: []
})
Later in the application flow I used a patch method and the patchPropertyController to update the rooms array.
Shout out to #suleymanSah for suggesting something that made me take another look at the code side by side.

Mongoose Schema for User profile, that has one to many properties

I used to use MySQL and now new to Mongodb. In phase of learning, I am trying to create a User Profile like LinkedIn. I am confused to choose the proper way of creating one-to-many relationship in mongodb schemas.
Context: A user can have multiple education qualification and same with experience. Here are two ways, that i tried to create the schema as:
Example 1:
var userSchema = new mongoose.Schema({
name: {
first: String,
middle: String,
last: String,
},
gender: { type: Number, max: 3, min: 0, default: GENDERS.Unspecified },
age: Number,
education: [{type: Schema.Types.ObjectId, ref:'Education'}],
experience: [{type: Schema.Types.ObjectId, ref:'Experience'}],
email: String
});
Example 2:
var userSchema = new mongoose.Schema({
name: {
first: String,
middle: String,
last: String,
},
gender: { type: Number, max: 3, min: 0, default: GENDERS.Unspecified },
age: Number,
education: [{type: String, detail: {
major: String,
degree: String,
grade: String,
startdate: Date,
enddate: Date,
remarks: String
}}],
experience: [{type: String, detail: {
position: String,
company: String,
dateofjoin: String,
dateofretire: String,
location: String,
responsibilities: [{type:String}],
description: String,
}}],
email: String
});
I haven't tested the second option.
Which one is better and easier during writing querying to fetch or add data? Is there a better way to write schema for scenarios like that?
For this particular example I would suggest embedding the documents vs storing as reference, and here is why.
Usually my first step in mongodb schema design is to consider how I will be querying the data?
Will I want to know the educational background of a user, or will I want to know all users who have a particular educational background? Having answers to these types of questions will determine how you want to setup your database.
If you already know your user, having the embedded educational background within that document is quick and easy to access. No separate queries are needed, and the schema still isn't overly complex and difficult to comprehend. This is how I would expect you would want to access the data.
Now, if you want to find all users with a particular educational background, there are obviously large drawbacks to the embedded document schema. In that case you would need to first look at each user, then loop through the education array to see all the various educations.
So the answer will be dependent on the expected queries you will be making to the data, but in this case (unless you are doing reporting on all users) then embedded is probably the way to go.
You may also find this discussion helpful:
MongoDB relationships: embed or reference?

MongoDB schema design for multiple user types

I'm about to build a Node.js+Express+Mongoose app and I'd like to pick the community's brains and get some advice on best practices and going about creating an efficient schema design.
My application is going to include 2 different user types, i.e "teacher" and "student". Each will have a user profile, but will require different fields for each account type. There will also be relationships between "teacher" and "student" where a "student" will initially have 1 teacher (with the possibility of more in the future), and a "teacher" will have many students.
My initial thoughts about how to approach this is to create a general User model and a profile model for each user type (studentProfile model & teacherProfile model), then reference the appropriate profile model inside the User model, like so (A):
var UserSchema = new Schema({
name: String,
email: String,
password: String,
role: String, /* Student or Teacher */
profile: { type: ObjectID, refPath: 'role' }
});
var studentProfileSchema = new Schema({
age: Number,
grade: Number,
teachers: [{ type: ObjectID, ref: 'Teacher' }]
});
var teacherProfileSchema = new Schema({
school: String,
subject: String
});
Or do I just go ahead and directly embed all the fields for both profiles in the User model and just populate the fields required for the specific user type, like so (B):
var UserSchema = new Schema({
name: String,
email: String,
password: String,
role: String, /* Student or Teacher */
profile: {
age: Number,
grade: Number,
school: String,
subject: String
},
relationships: [{ type: ObjectID, ref: 'User' }]
});
The downside to option B is that I can't really make use of Mongoose's required property for the fields. But should I not be relying on Mongoose for validation in the first place and have my application logic do the validating?
On top of that, there will also be a separate collection/model for logging students' activities and tasks, referencing the student's ID for each logged task, i.e.:
var activitySchema = new Schema({
activity: String,
date: Date,
complete: Boolean,
student_id: ObjectID
});
Am I on the right track with the database design? Any feedback would be greatly appreciated as I value any input from this community and am always looking to learn and improve my skills. What better way than from like minded individuals and experts in the field :)
Also, you can see that I'm taking advantage of Mongoose's population feature. Is there any reason to advise against this?
Thanks again!
You could try using .discriminator({...}) function to build the User schema so the other schemas can directly "inherit" the attributes.
const options = {discriminatorKey: 'kind'};
const UserSchema = new Schema({
name: String,
email: String,
password: String,
/* role: String, Student or Teacher <-- NO NEED FOR THIS. */
profile: { type: ObjectID, refPath: 'role' }
}, options);
const Student = User.discriminator('Student', new Schema({
age: Number,
grade: Number,
teachers: [{ type: ObjectID, ref: 'Teacher' }]
}, options));
const Teacher = User.discriminator('Teacher', new Schema({
school: String,
subject: String
}, options));
const student = new Student({
name: "John Appleseed",
email: "john#gmail.com",
password: "123",
age: 18,
grade: 12,
teachers: [...]
});
console.log(student.kind) // Student
Check the docs.
One approach could be the following:
//Creating a user model for login purposes, where your role will define which portal to navigate to
const userSchema = new mongoose.Schema({
_id:mongoose.Schema.Types.ObjectId,
name: {type:String,required:true},
password: {type: String, required: true},
email: {type: String, required: true},
role:{type:String,required:true}
},{timestamps:true});
export default mongoose.model("User", userSchema);
//A student schema having imp info about student and also carrying an id of teacher from Teachers Model
const studentSchema = new mongoose.Schema({
_id:mongoose.Schema.Types.ObjectId,
age:{type:Number},
grade:{type:String},
teacher:{type:mongoose.Schema.Types.ObjectId,ref:'Teachers'}
},{timestamps:true});
export default mongoose.model("Students", studentSchema);
//A teacher model in which you can keep record of teacher
const teacherSchema = new mongoose.Schema({
_id:mongoose.Schema.Types.ObjectId,
subject:{type:String},
School:{type:String},
},{timestamps:true});
export default mongoose.model("Teachers", teacherSchema);

Mongoose: Repeated object in schema

I'm defining my schema in mongoose and I have an array of book objects, and then an "active book". Now setting it up wasn't an issue, but this seems like unnecessary repetition to define the exact same book object in two different parts of the schema.
var BookSchema = new Schema({
activeBook: {
id: String,
title: String,
author: String,
pages: Number
},
books: [{
id: String,
title: String,
author: String,
pages: Number
}]
});
Is there a cleaner way of writing this so I don't have to write out the same object everywhere I use it?
A cleaner way would be to create a subdocument, a document with a schema of its own which are elements of a parent's document array. So in your example above you can define the child/parent schema as follows:
var ChildSchema = new Schema({
id: String,
title: String,
author: String,
pages: Number
});
var ParentSchema = new Schema({
activeBook: ChildSchema,
books: [ChildSchema]
});

tuples (arrays) as attributes in mongoose.js

MongoDB, and mongoose.js specifically, allows tuples as attributes. For instance, the MongoDB documentation has this example where the attribute comments is itself an array of objects with the attributes [{body: String, date: Date}]. Yay!
var blogSchema = new Schema({
title: String,
author: String,
body: String,
comments: [{ body: String, date: Date }],
date: { type: Date, default: Date.now },
hidden: Boolean,
meta: {
votes: Number,
favs: Number
}
})
Now when i persist that to MongoDB, not only does each instance of blogSchema get its own value for _id (e.g. 502efea0db22660000000002) but each individual value of comment gets its own _id field.
For the most part I don't care, but in my app the analog to comments may have thousands of values. Each of which gets its own huge value of _id.
Can I prevent that? I'll never need to refer to them individually. Or should I learn to stop worrying and love the unique identifier? I grew up programming the Vic20 and TRS80 as a kid, so may be overly paranoid about wasting memory/storage.
The _id can be disabled by setting the noId schema option to true. To pass the option you need to pass an instance of schema instead of using the object literal:
// instead of this...
comments: [{ body: String, date: Date }]
// do this...
var commentSchema = new Schema({ body: String, date: Date }, { noId: true });
var blogSchema = new Schema({
..
comments: [commentSchema]
})