Mongoose database modeling - mongodb

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.

Related

MongoDB/ Mongoose many to many relationship - user, project and role

Our project has following requirements-
User can be part of multiple projects
A project can have multiple users.
User can be either Owner or Admin or Viewer of a project.
Obviously there can only one Owner of a project.
There can be multiple Admin or Viewer of a project.
Every user has a home/default project.
So I have created the following schemas - But I am not able to use populate and I find adding/ updating/ removing a lot of pain. Also when I query projects I want to show the email id of the users not the objectId but dont want to use email as a ref as user can change their email. Please help me with a better design considering the most popular operations on the schema-
User can create project
Add people to a project
View all of his projects and members
Change role of someone in the project
Remove someone from the project
View the projects and owner and members (emails not object ids) according to their right
The following working schemas are present in separate files user.js and project.js with required export of the models.
var userSchema = mongoose.Schema({
username: {type:String},
password: String,
email: {type:String, required:true, unique: true, index: true},
createdAt: { type: Date, default: Date.now },
firstName: String,
lastName: String,
home_project: {type:Schema.Types.ObjectId, ref:Project},
owner: [{type:Schema.Types.ObjectId, ref:Project}],
admin: [{type:Schema.Types.ObjectId, ref:Project}],
viewer: [{type:Schema.Types.ObjectId, ref:Project}]
});
var projectSchema = mongoose.Schema({
projectName: {type:String, required:true, unique: true, index: true},
createdAt: { type: Date, default: Date.now },
owner: {type:String, ref:User},
admin: [{type:String, ref:User}],
viewer: [{type:String, ref:User}]
});

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.

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

Can't update or query embedded sub-documents using MongoDB? Now what?

I took the NoSQL plunge against all my RDBMS prejudices from my past. But I trusted. Now I find myself 3 months into a project and the exact reasons we adhered to RDMS principles seem to be biting me in the butt. I think I just discovered here on stackoverflow that I can't work with twice embedded arrays. I followed the noSQL, embedded document approach like a good kool-aid drinker and feel like I've been betrayed. Before I swear off noSQL and go back and refactor my entire code-base to adhere to new 'normalized' model I'd like to here from some no-sql champions.
Here is my model using one big document with embedded docs and the works:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
User = mongoose.model('User');
var Entry = new Schema({
text: String,
ups: Number,
downs: Number,
rankScore: Number,
posted: {
type: Date,
default: Date.now
},
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
var boardSchema = new Schema({
theme: String,
created: {
type: Date,
default: Date.now
},
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
entered: {
type: Boolean,
default: false
},
entries: [Entry],
participants: [{
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User'},
date: { type: Date, default: Date.now },
topTen: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Entry'} ]
}]
});
mongoose.model('Board', boardSchema);
Basically, I want to query the document by Board._id, then where participants.user == req.user.id, I'd like to add to the topTen[] array. Note participants[] is an array within the document and topTen is an array within participants[]. I've found other similar questions but I was pointed to a Jira item which doesn't look like it will be implemented to allow the use of $ positional operation in multiple embedded arrays. Is there no way to do this now? Or if anyone has a suggestion of how to model my document so that I don't have to go full re-write with a new normalized reference model...please help!
Here are some of my query attempts from what I could find online. Nothing worked for me.
Board.update({_id: ObjectId('56910eed15c4d50e0998a2c9'), 'participants.user._id': ObjectId('56437f6a142974240273d862')}, {$set:{'participants.0.topTen.$.entry': ObjectId('5692eafc64601ceb0b64269b') }}
I read you should avoid such 'nested' designs but with the embedded model its hard not to. Basically this statement says to me "don't embed" go "ref".