Mongoose - can't save a document's properties - node.js

So I have users and teams. I would like teams to have an array of users and I add a user to the team using the addUser function. I push to the teams user array successfully and save the results but right after that function I go to print out the same team and there are no users in the array. I am not sure if this is a .save() problem or what.
Any help is appreciated, thank you.
...
var userSchema = new Schema({
name: { type: String, required: true, unique: true },
age: Number
});
var teamSchema = new Schema({
name: { type: String, required: true, unique: true },
user: [{ type: Schema.ObjectId, ref: 'User' }]
});
userSchema.statics.createUser = function(opts, cb) {...};
teamSchema.statics.createTeam = function(opts, cb) {...};
teamSchema.statics.addUser = function(opts, cb) {
Team.find({_id: opts.team}).exec( function (err, team) {
team[0].user.push(opts.user);
team[0].save(function(err, save) {
console.log("--------------------");
console.log(team[0]); //team contains the added user here
console.log("--------------------");
return cb(null);
});
});
};
var User = mongoose.model('User', userSchema);
var Team = mongoose.model('Team', teamSchema);
var async = require('async');
var user1;
var user2;
var team;
async.waterfall([
function(d){User.createUser({name :'test1'},function(err, user){
user1 = user;
d(err);
});
},
function(d){User.createUser({name :'test2',age :20},function(err, user){
user2 = user;
d(err);
});
},
function(d){Team.createTeam({name :'team'},function(err, obj){
team = obj;
d(err);
});
},
function(d){Team.addUser({user : user1._id,team : team._id}, function(err){
console.log(team);
d(err);
});
},
function(d){Team.addUser({user : user2._id,team : team._id}, function(err){
console.log(team);
d(err);
});
}
],function(err){
if(err){
console.log(err);
}
else{
User.count({},function(err,count){console.log("Number of users:", count);});
Team.count({},function(err,count){console.log("Number of teams:", count);});
console.log(team);
}
});
returns:
--------------------
{ _id: 5583ed760958ab941a58bae9,
name: 'team',
__v: 1,
user: [ 5583ed760958ab941a58bae7 ] } //user1 added
--------------------
{ __v: 0, name: 'team', _id: 5583ed760958ab941a58bae9, user: [] }
//after that function call the team has no users
--------------------
{ _id: 5583ed760958ab941a58bae9,
name: 'team',
__v: 2,
user: [ 5583ed760958ab941a58bae7, 5583ed760958ab941a58bae8 ] }
//user2 added and user1 still there
--------------------
{ __v: 0, name: 'team', _id: 5583ed760958ab941a58bae9, user: [] } //no users again
Number of users: 2
Number of teams: 1 //only 1 team exists

Perhaps try using an update and push which would also simplify your code?
findAndModify (mongodb command)
findOneAndUpdate (mongoose command)
Team.findOneAndUpdate({"_id" : opts.team} , {$addToSet : { "user" : opts.user}}, function (err, foundUpdatedTeam) {
return cb(err,foundUpdatedTeam);
}, {new : true});
// be sure to update your team variable to reflect what is also in the database.
something like this:
function(d){Team.addUser({user : user1._id,team : team._id}, function(err, updatedTeam)
{
team = updatedTeam;
console.log(team);
d(err);
});
Few Notes:
I would change the team schema from user to TeamUsers or something more descriptive..it's a little confusing now.
note the use of AddToSet, that ensures that if you already added the user you won't be adding him twice.
This code is shorter and cleaner, you don't need to find the object if you can use the dedicated function, if however you wish to add the user to multiple teams I'd use update which allows you to update multiple documents.

Related

Trying to update a field of a single object whilst also pushing to an array nodejs mongodb

Within this post function i am trying to award a badge and then after the badge has been awarded within an array to the usermodel i am trying to update a separate field in the user doc which i have named as points, the udoc.total points currently works however after the call is finished the users total points do not update and stay as 0
Usermodel schema -
var schema = new Schema({
email : {type:String, require:true},
username: {type:String, require:true},
password:{type:String, require:true},
creation_dt:{type:Date, require:true},
diabeticType:{type:String, require:true},
badges: [Badges],
totalPoints: {type:Number, require:true},
glucoseReading : [
{
bloodGlucoseLevel : {
type : String,
required : "Required"
},
dosageTime : {
type : String,
},
unitsOfInsulinForFood : {
type : String,
},
correctionDose : {
type : String,
},
creation_dt :{
type : String,
}
}
]
});
and then this is the post command where the badge is added to the users profile however the total points do not update
router.post("/:id/badge/:bid", function (req, res){{'useFindAndModify', false}
Badgemodel.findById(req.params.bid).then(doc => {
if(!doc)
{
return res.status(404).end();
}
else
{
var awardBadge = doc;
Usermodel.findByIdAndUpdate(req.params.id,
{$push : {badges : awardBadge}},
{safe : true, upsert: true}).then(udoc =>{
if(!doc)
{
return res.status(404).end();
}
else
{
console.log(udoc);
console.log(udoc.totalPoints);
udoc.totalPoints = udoc.totalPoints + awardBadge.value;
console.log(udoc.totalPoints);
return res.status(200).end();
}
})
}
})
})
I think you have an error in your User Schema.
You are trying to reference a array of documents of the Badges collection in your User Schema definition at badges: [Badges],.
But when you reference a doc from an other collection in mongoose the syntax is different.
Checkout the mongoose docs.
The schema should look like this:
badges: [{
type: Schema.Types.ObjectId,
ref: "UserModel" // or whatever the Model name is
}]
see also
https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-ref
Answer- I was storing the value for total points into a variable and this variable was not being sent back to the users info. I updated code by adding in another findbyidandupdate and included the $set function to update the users total points to the value of the variable udoc.total points.
router.post("/:id/badge/:bid", function (req, res){{'useFindAndModify', false}
Badgemodel.findById(req.params.bid).then(doc => {
if(!doc)
{
return res.status(404).end();
}
else
{
var awardBadge = doc;
Usermodel.findByIdAndUpdate(req.params.id,
{$push : {badges : awardBadge}},
{safe : true, upsert: true}).then(udoc =>{
if(!doc)
{
return res.status(404).end();
}
else
{
console.log(udoc);
console.log(udoc.totalPoints);
udoc.totalPoints = udoc.totalPoints + awardBadge.value;
Usermodel.findByIdAndUpdate({_id: req.params.id}, {$set: {totalPoints : udoc.totalPoints} }).then(doc => {
if(!doc)
{
return res.status(404).end();
}
else
{
return res.status(200).end();
res.send(doc);
}
})
console.log(udoc.totalPoints);
return res.status(200).end();
res.send(doc);
}
})
}
})
})

MongoDB and Nodejs insert ID with auto increment

I am new to NodeJs and MongoDB, i want to insert row with auto increment primary key 'id'. also defined a function called getNextSequence on mongo server.
this is working perfect on Mongodb server
> db.user.insert({
"id" : getNextSequence('user_id'),
"username" : "test",
"email" : "test#test.com",
"password" : "test123"
})
now i want to insert from NodeJs.I have tried this but not working
db.collection('user').insertOne({
id : "getNextSequence('user_id')",
username : query.name,
email: query.email,
password: query.pass
}, function(err, result) {
assert.equal(err, null);
console.log("row insterted ");
callback();
});
Assuming that getNextSequence is a server-script function (i.e. a method you defined and saved via db.system.js.save), it is not callable outside of the server. One way to go is to use eval, which forces the server to evaluate a string as a js code, even though it is not a good practice. Here is an example:
db.eval('getNextSequence(\'user_id\')', function(err, result) {
db.collection('users').insert({
"id" : result,
"username" : "test",
"email" : "test#test.com",
"password" : "test123"
});
});
Another way is to follow the mongo tutorial and to implement the getNextSequence directly in NodeJS. The syntax is pretty much the same:
function getNextSequence(db, name, callback) {
db.collection("counters").findAndModify( { _id: name }, null, { $inc: { seq: 1 } }, function(err, result){
if(err) callback(err, result);
callback(err, result.value.seq);
} );
}
You then use it in your nodeJS code like:
getNextSequence(db, "user_id", function(err, result){
if(!err){
db.collection('users').insert({
"_id": result,
// ...
});
}
});
Note: of course, you need to have set the counters collection as explained in the docs.
You can also use "mongoose-auto-increment".
The code has just 4 lines
var mongoose = require('mongoose');
var autoIncrement = require('mongoose-auto-increment');
autoIncrement.initialize(mongoose.connection);
userSchema.plugin(autoIncrement.plugin, 'user');
example :
npm i mongoose-auto-increment
connections.js :
const mongoose = require('mongoose');
require("dotenv").config;
const uri = process.env.MONGOURL;
mongoose.connect(uri, { useNewUrlParser: true }, (err) => {
if (!err) { console.log('MongoDB Connection Succeeded.') }
else { console.log('Error in DB connection : ' + err) }
});
require('../schema/userSchema');
userSchema.js :
var mongoose = require('mongoose'); // 1. require mongoose
var autoIncrement = require('mongoose-auto-increment'); // 2. require mongoose-auto-increment
var userSchema = new mongoose.Schema({
name: { type: String },
password: { type: String },
email: { type: String, unique: true, required: 'This field is required.' },
});
autoIncrement.initialize(mongoose.connection); // 3. initialize autoIncrement
userSchema.plugin(autoIncrement.plugin, 'user'); // 4. use autoIncrement
mongoose.model('user', userSchema);
To accomplish this, we will create a function that will keep trying to save the document untill it will have been saved with incremented _id
async function retryUntilSave(db, task) {
try {
const index = await db.collection('tasks').find().count() + 1;
const result = await db.collection('tasks').insertOne(Object.assign(task, { _id: index }))
} catch (error) {
if (error.message.includes("_id_ dup key")) {
console.log("ID already exists!")
console.log("Retrying...");
retryUntilSave(db, task)
} else {
console.log(error.message);
}
}
}
We can use task._id: index instead of Object.assign()
finally you can test this by making some concurrent requests
for (let index = 0; index < 20; index++) {
setTimeout(async () => {
await retryUntilSave(db, { title: "Some Task" })
}, 1000);
}
This function will handle easily if two or more tasks submitted at the same time because mogod throws error when we try to insert a document with duplicate _id, then we will retry saving the document again with incremented _id and this process will run until we save the document successfully !
You can also use "mongodb-autoincrement" module of node js. For example:
var autoIncrement = require("mongodb-autoincrement");
exports.yourMethod = function(newData, callback) {
autoIncrement.getNextSequence(db, your-collection-name, function (err, autoIndex) {
newData.id = autoIndex;
//save your code with this autogenerated id
});
}
You can use the below package on a model schema to auto-increment your collection field.
mongoose-auto-increment //you can download it from npm
Here I am not focusing on how to connect MongoDB. I just focus on how you can integrate auto increment in your model/collection/table.
const mongoose = require("mongoose"); //
const autoIncrement = require("mongoose-auto-increment");
const post_schema = new mongoose.Schema({
title: {
type: String,
required: true,
min: 3,
max: 225,
},
slug: {
type: String,
required: true,
},
});
autoIncrement.initialize(mongoose.connection);
post_schema.plugin(autoIncrement.plugin, {
model: "post", // collection or table name in which you want to apply auto increment
field: "_id", // field of model which you want to auto increment
startAt: 1, // start your auto increment value from 1
incrementBy: 1, // incremented by 1
});
module.exports = mongoose.model("post", post_schema);

Mongoose - trying to do 'JOINS' in MEAN stack

I am having a hard time understanding the async nature of NodeJS.
So, I have an articles object with this schema:
var ArticleSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
content: {
type: String,
default: '',
trim: true
},
creator: {
type: Schema.ObjectId,
ref: 'User'
}
});
and the User schema is:
var UserSchema = new Schema({
firstName: String,
lastName: String,
...
});
The problem is when I query for all the documents like so:
exports.list = function(req, res) {
// Use the model 'find' method to get a list of articles
Article.find().sort('-created').populate('creator', 'firstName lastName fullName').exec(function(err, articles) {
if (err) {
// If an error occurs send the error message
return res.status(400).send({
message: getErrorMessage(err)
});
} else {
// Send a JSON representation of the article
res.json(articles);
}
});
};
I get all the articles back successfully, but for some reasons, the article creator is returning different results
for locally authenticated users (localStrategy) and facebook authenticated users (facebook strategy) for locally authenticated users, I get:
articles = {
creator: {
id: 123,
firstName: 'Jason',
lastName: 'Dinh'
},
...
}
for fb authenticated users, I get:
articles = {
creator: {
id: 123
},
...
}
I can't seem to get a grip on PassportJS API, so what I want to do is
iterate through articles and for each article, find the user document using the article creator ID and add the user firstName and lastName to the articles object:
for each article in articles {
User.findOne({ '_id': articles[i].creator._id }, function(err, person){
//add user firstName and lastName to article
});
}
res.json(articles);
You can probably already see the problem here... my loop finishes before the documents are returned.
Now, I know that MongoDB doesn't have any 'joins' and what I want to do is essentially return a query that 'joins' two collections. I think I'm running into problems because I don't fundamentally understand the async nature of
node.
Any help?
You can use find instead of findOne and iterate inside your callback function.
User.find({ }, function(err, personList){
for each person in personList {
for each article in articles {
if (person._id === article.creator._id) {
//add user firstName and lastName to article
}
}
}
res.json(articles);
});
UPDATE:
Considering the scenario that #roco-ctz proposed (10M users), you could set a count variable and wait for it to be equal to articles.length:
var count = 0;
for each article in articles {
User.findOne({ '_id': articles[i].creator._id }, function(err, person){
//add user firstName and lastName to article
count += 1;
});
}
while (count < articles.length) {
continue;
}
res.json(articles);

How to parse request (or end points) to query mongoose

I am going to to implement a web api like this
POST /groups/:groupname/chats/:chatType
There could be several groups and each group has at most two chats, a private one or a public one.
Currently, in my /models directory, two related schemas look like this:
// group.js
...
var groupSchema = new mongoose.Schema({
groupname: {type: String, required: true, index:{unique: true}},
privateChat: {type: mongoose.Schema.Types.ObjectId, ref: 'Chat'},
publicChat: {type: mongoose.Schema.Types.ObjectId, ref: 'Chat'}
});
module.exports = mongoose.model('Group', groupSchema)
// chat.js
var chatSchema = new mongoose.Schema({
chatType: String, // public or private
message: [{text: String}]
});
module.exports = mongoose.model('Chat', chatSchema);
So the question is how can I post a message like "Hello World!" to
/groups/boygroup/chats/private
...
I have finished the request GET /groups/:groupname by findOne() method like this:
router.get('/groups/:groupname', function(req, res) {
var groupname = req.body.groupname || req.params.groupname;
Group.findOne({
groupname: groupname
}, function(err, group) {
if (err)
return next(err);
if (!group) {
res.status(404).send({
success: false,
message: "group not found"
});
} else {
res.json(group);
}
});
});
But I have no idea how to get to a specific chat in a specific group. Maybe my mongoose schema is not good.
Any suggestion is appreciated.

Setting a virtual field in a Model based on an async query from another model

I want to have a user setting (in a user model) that is derived from the sum of values in another model.
What I have tried to do is create a virtual value using a query like this:
var schemaOptions = {
toObject: {
virtuals: true
}
,toJSON: {
virtuals: true
}
};
/**
* User Schema
*/
var UserSchema = new Schema({
firstname: String,
lastname: String,
email: String,
username: String,
provider: String,
phonenumber: Number,
country: String,
emailverificationcode: {type:String, default:'verifyme'},
phoneverificationcode: {type:Number, default:4321 },
emailverified: {type:Boolean, default:false},
phoneverified: {type:Boolean,default:false},
}, schemaOptions)
UserSchema
.virtual('credits')
.get(function(){
//Load Credits model
var Credit = mongoose.model('Credit');
Credit.aggregate([
{ $group: {
_id: '5274d0e5a84be03f42000002',
currentCredits: { $sum: '$amount'}
}}
], function (err, results) {
if (err) {
return 'N/A'
} else {
return results[0].currentCredits.toString();
//return '40';
}
}
);
})
Now, this gets the value but it fails to work correctly (I cannot retrieve the virtual 'value' credits). I think this is because of the async nature of the call.
Can someone suggest the correct way to achieve this?
Once again many thanks for any input you can provide.
Edit:
So I am trying to follow the suggested way but no luck so far. I cannot get my 'getCredits' method to call.
Here is what I have so far:
UserSchema.method.getCredits = function(cb) {
//Load Credits model
var Credit = mongoose.model('Credit');
Credit.aggregate([
{ $group: {
_id: '5274d0e5a84be03f42000002',
currentCredits: { $sum: '$amount'}
}}
], function (err, results) {
cb(results);
}
);
};
var User = mongoose.model('User');
User.findOne({ _id : req.user._id })
.exec(function (err, tempuser) {
tempuser.getCredits(function(result){
});
})
Any ideas? Thanks again
There are a few issues with your implementation:
UserSchema.method.getCredits
^^^^^^ should be 'methods'
Also, you have to make sure that you add methods (and virtuals/statics) to your schema before you create the model, otherwise they won't be attached to the model.
So this isn't going to work:
var MySchema = new mongoose.Schema(...);
var MyModel = mongoose.model('MyModel', MySchema);
MySchema.methods.myMethod = ... // too late, model already exists
Instead, use this:
var MySchema = new mongoose.Schema(...);
MySchema.methods.myMethod = ...
var MyModel = mongoose.model('MyModel', MySchema);
I would also advise you to always check/propagate errors.

Resources