Node.js and for loops acting weird - javascript

I am working on a project for my internship, where they are using node.js and mongoDB in their back-end. This set up really doesn't work so well at the moment, because we in fact have relational data and mongoDB really isn't the best for that.
My problem is, I have to get some data from the DB that require a "join" on two tables. MongoDB doesn't allow this, so I am trying to do it manually.
I have to find a gig, and for each job type (JobTypeLine) the gig has, find the info about staff members invited. RolodexMembersInterested is an array that contains IDs which allows us to find the staff info in the RolodexMember "table".
The json structures look like this:
Gig:
{
_id: {type: Schema.ObjectId},
CreatedAt: {type: Date, default: Date.now()},
EventName: {type: String, required: true},
Headline: {type: String, required: true},
Date: {type: Date, required: false},
EndDate: {type: Date, required: false},
Address: {type: String, required: false},
Description: {type: String, required: true},
CompanyName: {type: String, required: true},
CompanyID: {type: String, required: true},
Exposed: {type: Boolean, default: false},
HashTags: {type: []},
JobTypeLine: {type: [
{
JobType: {type: String, required: true},
Briefing: {type: String, required: false},
Quantity: {type: Number, required: true},
StartTime: {type: String, required: true},
EndTime: {type: String, required: true},
NumberOfHours: {type: String, required: false},
HourlySalary: {type: Number, required: false},
RolodexMembersInvited: {type: [], default: []},
RolodexMembersAssigned: {type: [], default: []},
RolodexMembersInterested: {type: [], default: []}
}
]},
},
RolodexMember:
{
_id:{type:Schema.ObjectId},
companyId: {type: String, required: true},
userId: {type: String, required: false},
userEmail: {type: String, required: true},
comment: {type: String, required: false},
payRate: {type: Number, required: false},
name: {type: String, required: false},
phone: {type: String, required: false},
pictureUrl: {type: String, required: false},
isHot: {type: Boolean, required: false},
isInPaySystem: {type: Boolean, required: false},
numberOfPreviousGigs: {type: Number, required: true, default: 0},
previousGigs: [],
otherAttributes: [
{
attributeName: {type: String, required: false },
attributeValue: {type: String, required: false }
}
],
})
So I wrote a method to "simulate" my join :
exports.getMembersInvitedToGig = function(req,res) {
var jobTypes = [];
var rolodexInvited = [];
gigsModel.findOne({_id: req.body.gigId}, function(err, gig) {
if(err) {
console.log(err);
res.send(404, "Couldn't find the gig with id: " + req.body.gigId);
} else {
for(var i=0;i<gig.JobTypeLine.length;i++){
rolodexInvited = [];
for(var j=0;j<gig.JobTypeLine[i].RolodexMembersInvited.length;j++) {
var userId = gig.JobTypeLine[i].RolodexMembersInvited[j];
getUserData(userId, function(data,success) {
if (success) {
rolodexInvited[j] = data;
}
});
}
jobTypes[i] = rolodexInvited;
}
res.send(200,jobTypes);
}
});
}
function getUserData (userId, callback) {
var RolodexItem = mongoose.model('RolodexItems');
RolodexItem.findOne({ _id: userId }, function (err, doc) {
if (err) {
console.log("There was an error in finding the rolodex item: " + err);
} else {
callback(doc,true);
}
});
}
My problem is that the method return and empty array filled other empty arrays (as many as I have jobTypes). I know the getUserData function works just fine and finds the right staff info etc. the problem is, it seems like node doesn't wait for the data to be returned inside the for(j) loop and just continues on (and eventually ends gets to the res.send(200,jobTypes) while jobTypes is actually still empty. I have tried adding a callback to the getUserData to solve that problem, but it didn't change anything.
(Now I know using a relational database would solve the problem all together, but I don't really get a say in this, so I have to make this work as it is). There might also be some really bad practices in there, as I just based myself on functions previously made by other people, and I have noticed they weren't really good at it. I have never worked with node.js previously though, so I haven't had time to look as some proper practices/etc. (Feel free to point out anything that is badly written, I'd love to learn the "good" way to do it)

The problem is that your getUserData function is asynchronous (because findOne is asynchronous). So your for loop does not wait for your getUserData function to complete.
You should use the async library (have a look to the each function) : https://github.com/caolan/async

Related

How to make sure that objects do not have the same two element in mongoose schema?

I am making the following mongoose schema and i want to make sure that no object has the same autherFirstName and autherLastName. object may have one in common but not both of them
const authorShcema = new mongoose.Schema({
autherFirstName: {type: String, minLength: 2, required: true},
autherLastName: {type: String, minLength: 2, required: true},
autjorDob: {type: Date, required: true},
authorImage: {type: String},
authorBooks: [{type: mongoose.Schema.Types.ObjectId, ref: "Book"}],
});
https://mongoosejs.com/docs/2.7.x/docs/indexes.html
Create a composite unique index
authorShcema.index({ autherFirstName: 1, autherLastName: 1 }, { unique: true });

Mongoose not saving changes to a document

I'm sorry if this might be a duplicate question but I'm quite having a hard time understanding Mongoose. I am working on a Node.js project that implements Mongoose and MongoDB. What I want to accomplish is to modify and save some users' data through a call from a specific endpoint.
Mongoose Schema looks like this
var UserSchema = new Schema({
isAdmin: {type: Boolean, default: false},
name: String,
surname: String,
nickname: { type: String },
email: { type: String, lowercase: true, required: true, trim: true, unique: true, dropDubs: true },
password: { type: String, required: true },
salt: { type: String },
verified: { type: Boolean, default: false },
bio: {
type: { type: String, enum: [0,1] }, // 0='Appassionato', 1='Giocatore'
birthday: String,
height: Number,
number: Number,
role: { type: String, enum: [0,1,2,3] }, // 0='Playmaker', 1='Ala', 2='Guardia', 3='Centro'
team: String,
city: String,
aboutMe: String,
},
newsletter: {type: Boolean, default: false},
lastCheckin: {type: mongoose.Schema.Types.ObjectId, ref: 'Checkin'},
follows: [{type: mongoose.Schema.Types.ObjectId, ref: 'Structure'}],
score: { type: Number, default: 0 },
profilePicture: String,
lastLogin: {type: Date},
facebook: {
id: String,
accessToken: String,
profileImage : String
}
}, {
collection: 'users',
retainKeyOrder: true,
timestamps: true,
}).plugin(mongoosePaginate);
Following is the code for when the endpoint gets interrogated
exports.updateUser = (req,res) => {
var userId = req.params.userId;
var updates = req.body;
User.findOneAndUpdate({_id: userId}, {$set: updates}, (err, saved) => {
if (!err) {
console.log("Ritorno questo: " + saved);
return res.status(202).json(saved);
} else {
return res.status(500).json(saved);
}
});
};
As far as I understood, the method findOneAndUpdate exposed by Mongoose should find the document I'm looking for and then modify it and save it. This doesn't happen though.
Through PostMan I'm sending this JSON
{"bio.aboutMe":"Hello this is just a brief description about me"}
But PostMan is responding with the non-modified object. What am I missing here?
What you need to do is to add {new:true}, it give you back the updated document.
In the documentation :
If we do need the document returned in our application there is
another, often better, option:
> Tank.findByIdAndUpdate(id, { $set: { size: 'large' }}, { new: true },
> function (err, tank) { if (err) return handleError(err);
> res.send(tank); });
This is something I don't really like as there is another option if we don't want to have the document → update
So what you need to do is :
User.findOneAndUpdate({_id: userId}, {$set: updates}, {new:true}.....

Mongoose (mongodb) $push data as subdocument, validate unique?

I have a User document which has a Notes subdocument.
I'm using the following code to push new notes for the user with the given email address.
UserSchema.statics.addNotesToUser = function (email, notes, callback) {
return this.updateOne(
{'email': email},
{$push: {notes}},
callback
)
};
This is working fine, however it's ignoring my unique constraint on the NoteSchema. these are my schemas
const NoteSchema = new Schema({
_id: false,
id: {type: String, required: true, trim: true, unique: true},
content: {type: String, required: true, trim: true, lowercase: true},
added: {type: Date, default: Date.now},
used: {type: Date, default: Date.now},
book: {
name: {type: String, required: true}
}
});
const UserSchema = new Schema({
email: {type: String, required: true, trim: true, lowercase: true, unique: true},
notes: [NoteSchema]
});
I'm wondering how I can make sure that when pushing new notes to my user, I can validate if the ID of the notes is unique.
Thank you.
To achieve uniqueness constraint like functionality in subdocuments, hope that's OK.
let notesId = [];
notes.forEach(function(val,index){
notesId.push(val.id)
})
db.yourCollection.update(
{ 'email': email, 'NoteSchema.id': { '$ne': { $each: notesId } }},
{$push: {notes} },
callback)

Adding a date to my MongoDB

I was wondering if anyone could help me add a dynamic date to my database. The idea is I want to sort the database by people who have registered. So below I have a user schema of the values my database holds I have tried different things like adding a variable that holds a date object but no luck. I have tried searching around with no clear answer. Is there someone that can help me out?
var userSchema = new Schema({
firstName: {type: String, required: true, validate: nameValidator},
lastName: {type: String, required: true, validate: nameValidator},
addressOne: {type: String, required: true, validate: addressValidator},
addressTwo: {type: String, validate: addressValidator},
city: {type: String, required: true, validate: cityValidator},
state: {type: String, required: true, validate: stateValidator},
zipcode: {type: String, required: true, validate: zipValidator},
country: {type: String, required: true, validate: countryValidator},
});
You can specify date datatype using Date. All supported data types can be found here.
var userSchema = new Schema({
firstName: {
type: String,
required: true,
validate: nameValidator
},
lastName: {
type: String,
required: true,
validate: nameValidator
},
registrationDate: {
type: Date,
required: false,
default: Date.now
}
);
Above code will also give registrationDate as current time of saving userSchema by default.

Can't save JavaScript objects with Mongoose

I'm doing a MMO Real-Time Browser game, and I'm storing data using Mongoose (MongoDB).
First of all, I'll show you the structure of my object:
var playerSchema = new Schema({
name: { type: String, required: true, trim: true, index: { unique: true } },
resources: {
wood: { type: Number, required: true, default: 500},
stone: { type: Number, required: true, default: 300},
iron: { type: Number, required: true, default: 0},
cereal: { type: Number, required: true, default: 0}
},
resourcesPerHour: {
woodPerHour: { type: Number, required: true, default: 40},
stonePerHour: { type: Number, required: true, default: 20},
ironPerHour: { type: Number, required: true, default: 0},
},
res: {type: Array, required:true, default: []},
buildings: { type: Array, required: true, default: []},
researches: { type: Array, required: true, default: []}
});
As you can see, res, buildings, and researches are arrays. I'm gonna show you one of them (they all have the same structure):
var buildingSchema = new Schema({
id: {type: String, requried: true},
name: { type: String, required: true, trim: true, index: { unique: true } },
level: { type: Number, required: true, default: 0},
scalingValue: {type: Number, required: true, default: 2},
costs: {
wood: { type: Number, required: true, default:0},
stone: { type:Number, required:true, default:0},
iron: {type:Number, required:true, default:0},
cereal: {type:Number, required:true, default:0}
}
});
OK, imagine I have a player, with all data initializated. When I try to update something, I can only update information out of that lists. Look this example:
player.findOne({name:req.session.name}, function(err, doc){
doc.resources.wood -= 200;
doc.buildings[id%100].costs.wood *= 2;
doc.save(function(err){
if(err)console.log(err);
});
}
When I look the model on database, it only stored the resources.wood, not the building[i].costs.wood. I don't know why it's failing, but all objects inside the arrays are created using a new variable, where variable is a Schema (like buildingSchema).
One more thing, I added a console.log(doc.buildings[i].costs.wood); just before the doc.save() and it's ok. So it means all the data is modified fine, but in the doc.saveit only save the 'not-in-list' data.
EDIT: console.log(err); doesn't print nothing, so it means the save function worked.
When you use the Array type in your schema (same as the Mixed type in the docs), you need to explicitly mark the field as modified or Mongoose won't save any changes you make to it.
doc.markModified('buildings');
doc.save(function(err){
if(err)console.log(err);
});
The better approach may be to declare buildings as containing an array of buildingSchema:
buildings: [buildingSchema],
That way Mongoose will be able to track the changes you make to the buildings array automatically.
https://groups.google.com/forum/#!topic/mongoose-orm/5m7ZPkhFyrc
Subdocuments that are not inside of an array are not supported by mongoose. My recommendation is to drop mongoose completely and just use direct queries to the db. You'll find the node native driver is quite easy to use.

Categories