Mongodb schema design for task - mongodb

I am working on a project which allow user to create task and other user(service provider) can provide them quotation and later let the user decide which service provider is suitable for them.
Here is the service schema:
var ServiceSchema = new Schema({
address: String,
quotations:[{
service_provider:{type:mongoose.Schema.Types.ObjectId, ref:'User'},
price: Number,
date: Date
}],
customers :[{type:mongoose.Schema.Types.ObjectId, ref:'User', childPath:'services_need'}],
});
and here is the user schema
var UserSchema = new Schema({
name:String,
password:String,
email:String,
phone:String,
token:String,
services_need:[{type: mongoose.Schema.Types.ObjectId, ref: 'Service'}],
services_provide:[{type: mongoose.Schema.Types.ObjectId, ref: 'Service'}],
});
The question is that is this in best practise? Any other rules of design I break?
Also , do I need to hold the reference of User in Service?
Thanks

You can do sub document relationships like that without any problems.
What do you mean by "do I need to hold the reference in User "? Please elaborate

Related

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 Mongoose save object with nested objects

I'm new to MongoDB and I'm creating a simple db with Mongoose with the following models: User, Game and Players.
So, one user contains none or many games. Every game has to players, and each player refers to a user. Like this (I simplified the schemas for clarity):
const UserSchema = new Schema({
name: String,
games: [{
type: Schema.Types.ObjectId,
ref: 'game'
}]
});
const GameSchema = new Schema({
mode: Number,
players: {
type: [{
type: Schema.Types.ObjectId,
ref: 'player'
}],
required: true
}
});
const PlayerSchema = new Schema({
order: Number,
isWinner: Boolean,
user: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
}
});
So, now in the frontend I want to send a petition to the backend to create a new game for users Joe (_id:11111) and Bob (_id:22222) and so I send a POST to /api/games with the body { users: [ 11111, 22222 ] }
Now my question is, for the backend to create a new game, it also has to create 2 players. What's the best way to achieve this?
In the Game.create() method, shall I retrieve the data, create and save the players, create the game, assign the players, save the game, and also update the users and add the game ids?
I also read about Mongoose middleware, where you can set certain functions to be executed before or after some operations. So maybe it's better:
pre function before Game.create, to create the players
post function before Game.create, to update the users
This last one seems cleaner.
What's the best way? Maybe another one I have not considered?
Thanks
I would suggest you using the post and pre functions defined in the mongoose middleware. They're pretty straightforward and neat to use. It will probably solve your problem.
Here is a personal example of a problem we had; In our case, we had to assign a userId from a sequence in the database. We used the following code:
var UserSchema = new Schema({
username: { type: String, required: true, unique: true },
id: { type: String },
...
});
UserSchema.pre('save', function(next) {
let doc = this;
let id = 'userSeq'
Sequence.findByIdAndUpdate(id, { $inc : {nextSId : 1} }, function(error,data) {
if(error)
next(error)
doc.id = data.nextSId-1;
next();
})
});
My suggestion is that before you create the game, you can search for the users and add a reference to the game. If I were you, I would use the findAndModify query of mongodb to find the users or create if they do not exist yet.

Mongoose database modeling

I am new to mongoose as well as nosql. I am designing a database which will contain a list of people and each person could have multiple skills - like C, Java, Python. Further the person would have been using the particular skill since a particular time - eg. Since 2010.
I have created a personSchema and a skillSchema. I am not able to figure how to add the "Since" as the since is specific to a person but is also for a particular skill.
I really need the skill to be a separate schema as the list of skills would be used elsewhere.
let personSchema = new mongoose.Schema({
id: { type: String, required: true, unique: true, index: true, dropDups: true},
firstname: String,
lastname: String,
age: Number
mobile: [Number],
skills: [{type: Schema.Types.ObjectId, ref: 'Skill'}]
});
let skillSchema = new mongoose.Schema({
skillName: String
});
Now where to store "since"?
E.g Tom is working on C++ since 2010 - The 2010 is related to both Tom and C++
skills : [
{
skill : {type: Schema.Types.ObjectId, ref: 'Skill'}
since : Number
}]
Adding 'Since' this way will make more sense as each skill reference will have its since value with it.
Hope it helps.

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 Reference for simple code reference values?

I'm designing a MongoDB schema to save a fairly large/nested document. I'm planning on embedding as much as possible into a single document, but wasn't sure what to do with code/lookup values. For example, if we have a code table representing "priority", with the possible values being:
low
medium
high
Is this something I should use a Mongoose reference for, and create a simple document to hold priority, eg something like:
var PrioritySchema = new Schema({
description: String
});
This would then be referenced with something like the following:
var AnotherSchema = new Schema({
name: String,
active: Boolean,
priority: { type: String, ref: 'Priority' }
});
Or is this overkill? The thing I want to avoid is directly storing these "descriptions" in the main/overall model, then having a requirement change sometime in the future. For example, someone decides that instead of "medium", we need to call it "somewhat". In that situation, I assume I'd be stuck doing some sort of data migration?
you can do this :
var PrioritySchema = new Schema({
description: String
});
and this
var AnotherSchema = new Schema({
name: String,
active: Boolean,
priority: { PrioritySchema }
});
But if you want what you described further I would advise you to do this instead :
var AnotherSchema = new Schema({
name: String,
active: Boolean,
priority: { type: Schema.Types.ObjectId, ref: 'Priority' } // see this : Schema.Types.ObjectId != String
});
Let's make it simple if you need those values to be cross-documents you need to use reference. If the values are only existing because of the parent document then you can choose embing.
For more information read this :
http://mongoosejs.com/docs/2.7.x/docs/embedded-documents.html
FYI : I used to struggle a lot with this. If you follow the path of embing all nested sub-document you will face a lot of "What Why I can't do that :'(. At the end I choosed the referencing way I felt more confortable with it. embing != referencing.