How to get the newly created embedded document with Mongoose? - node.js

I have a schema with embedded documents, the setup looks like this:
var Guests = new Schema({
email: {
type: String,
required: true
},
time: {
type: Date,
default: Date.now
}
});
var Devices = new Schema({
user: {
type: String,
required: true
},
time: {
type: Date,
default: Date.now
},
name: {
type: String,
required: true
},
guests: [Guests]
});
I create a new guest with the following code:
// Check if already invited
Device.findOne({ '_id': req.body.device_id, 'user': req.user.href, 'guests.email': req.body.guest_email }, function (err, guest) {
// Check for error
if (err) return handleError(err);
// If already invited
if (guest) {
return res.status(402).send({code: 'already_invited', message: 'This guests is already invited.'});
} else {
// If not invited yet, lets create the new guest
device.guests.push({
"email": req.body.guest_email
});
// Save the new guest to device
device.save(function (err) {
if (err) res.status(400).send(err);
// Get the saved guest ID, but how?
});
}
});
Everything works, but i don't know how to get the newly created embedded guest document. I especially need the ID, but i want the whole new guest in my response. Inside the device.save function, device.guests already has the ID of the newly created record, so i could loop through it and find it that way, but i'm wondering if theres an easier way to do it.

device.save(function (device, err) {
if (err) res.status(400).send(err);
// use device here. i.e res.status(200).json(device);
});

If you actually create a Guests entity with the new operator, the _id property will be populated. Try something like this:
var guest = new Guests({guest_email: "foo"});
device.guests.push(guest);

Related

Create subscribers for certain time in NodeJS and mongodb with mongoose [duplicate]

I currently have data saving and expiring to/from a database via a mongoose schema like so:
var UserSchema = new Schema({
createdAt: { type: Date, expires: '1m' },
name: String,
email: String
});
The only problem is that the document that's saved to the database is completely removed from the database. How would I refactor the above so that the name/email address stay in the database but if the user attempts to login after their expiry date then they're greeted with a message saying 'session has expired, renew session'. (or something similar)
I'm wanting to do it this way because then if the user logs in with an expired email address the server is still able to lookup the email address and spit out a "expired session" message rather than a "not found" error which is what happens when data is deleted.
So to reiterate, how do I keep expired data in a mongo/mongoose database so the app is able to find the email address the user is attempting to login with but if their session has expired they need to renew the session?
You should use concept of Schema Reference for this. Save your expired field in another table and join your main user_table and expire_table(wxample name)
var UserSchema = new Schema({
name: String,
email: String
});
//save date by-default
//expire in 1 min as in your example
var expireSchema = new Schema({
createdAt: { type: Date, default: Date.now, expires: '1m' },
user_pk: { type: Schema.Types.ObjectId, ref: 'user_expire'}
});
var userTable = mongoose.model('user_expire', UserSchema);
var expireTable = mongoose.model('expireMe', expireSchema);
//Save new user
var newUser = new userTable({
name: 'my_name',
email: 'my_email'
});
newUser.save(function(err, result) {
console.log(result, 'saved')
var newExpire = new expireTable({
user_pk:result._id
});
//use _id of new user and save it to expire table
newExpire.save(function(err, result) {
console.log('saved relation')
})
})
Now to detect whether session has expired or not
1. on executing this code before data gets expired
expireTable.findOne()
.populate('user_pk')
.exec(function (err, result) {
if (err) throw err;
console.log(result)
if(result == null) {
console.log('session has expired, renew session')
} else {
console.log('session is active')
}
});
//output - session is active
2. on executing this code after data gets expired
expireTable.findOne()
.populate('user_pk')
.exec(function (err, result) {
if (err) throw err;
console.log(result)
if(result == null) {
console.log('session has expired, renew session')
} else {
console.log('session is active')
}
});
//output - session has expired, renew session
The accepted answer is good, but with Mongo 3.0 and above, the
createdAt: { type: Date, default: Date.now, expires: '1m' }
does not work for me.
Instead I used
var expireSchema = new Schema({
expireAt: {
type: Date,
required: true,
default: function() {
// 60 seconds from now
return new Date(Date.now() + 60000);
}
},
user_pk: { type: Schema.Types.ObjectId, ref: 'user_expire'}
});
More info is here: Custom expiry times for mongoose documents in node js
EDIT
My comment above would also require invoking a Mongo function directly rather than via Mongoose syntax. This would be something like:
db.[collection-name].createIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } )
Additionally this is for the Expire Documents at a Specific Clock Time way of doing a ttl field.
And I still can't seem to get it to work, but might be because of the erratic way that the ttl reaper runs (once every 60 secs, but could be longer...)
EDIT
My issues were due to having an earlier incorrectly configured ttl index persisting on the expireAt field, this prevented my later (correctly defined) index from working. I fixed this just by deleting any non-_id earlier indexes and then re-adding my ttl index on the expireAt field.
Use db.[collection-name].getIndexes()
and
db.[collection-name].dropIndex({ "expireAt":1 }) to clear out before re-applying.
Also one other caveat - setting a Date snapshot in the default property of the expiredAt field means that the default value will always be a fixed date - instead set this Date value dynamically each time you create an instance of expireSchema:
var expireSchema = new Schema({
expireAt: Date,
user_pk: { type: Schema.Types.ObjectId, ref: 'user_expire' }
});
expireSchema
.virtual('expiryTime')
.get(function() {
//allow for reaper running at 60 second intervals to cause expireAt.fromNow message to be 'in the past'
var expiryTime = moment(this.expireAt).fromNow();
if(String(expiryTime).indexOf("ago")){
expiryTime = "in a few seconds";
}
return expiryTime;
});
var expireModel = mongoose.model('UserExpire', expireSchema);
expireModel.collection.ensureIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } );

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

Creating and updating documents synchronously with mongoose

I'm wondering what the best approach would be to realize the following situation in Node.js and mongoose:
I'm having a collection with users and a collection with groups. I want users to be able to create new groups and add people to them (very similar to people creating a new whatsapp group). I use the following schemas for the user and group documents:
var userSchema = mongoose.Schema({
email: String,
hashed_password: String,
salt: String,
name: String,
groups: [{
_id: { type: Schema.Types.ObjectId, required: true, ref: 'groups' },
name: String
}]
});
var groupSchema= mongoose.Schema({
name: String,
creator: Schema.Types.ObjectId,
users: [{
_id: { type: Schema.Types.ObjectId, required: true, ref: 'users' },
rankInGroup: { type: Number, required: true }
}]
});
At this moment I have a function that takes the following arguments: the email address (groupCreator) of the user who is creating the group, the name of the new group (newGroupName), and the userids of the users (newGroupMembers) that need to be added to the group. This function first finds the users that need to be added to the group (using this method) and then adds the user ids of these users to the users array in the group document like this:
function(groupCreator, newGroupName, newGroupMembers , callback) {
userModel.findOne({
email: emailGroupCreator
}, function(err,creator){
//load document of user who creates the group into creator variable
var newGroup = new groupModel({
name: newGroupName,
creator: creator._id,
users: [{_id: creator._id, rank: 0}] //add group creator to the group
});
userModel.find({
email: { $in: newGroupMembers }
}, function(err,users){
//for now assume no error occurred fetching users
users.forEach(function(user) {
newGroup.users.push({_id: user._id, rank: 0}); //add user to group
user.groups.push({_id:newGroup._id,name:newGroup.name}); //add group to user
}
newGroup.save(function (err) {
//for now assume no error occurred
creator.groups.push({_id: newGroup._id, name: newGroup.name}); //add group to creator
creator.save(function (err) {
//for now assume no error occurred
/* save/update the user documents here by doing something like
newMembers.forEach(function(newMember) {
newMember.save(function (err) {
if(err){
callback(500, {success: false, response: "Group created but failure while adding group to user: "+newMember.email});
}
});
});
callback(200, {success: true, response: "Group created successfully!"});
*/
});
}
});
}
So I want this function to:
Find the user document of the group creator based on its email address stored in groupCreator
Create a new group (newGroup) and add the creator to its users array
Find all the users that need to be added to the group
Update the groups array in all the user documents with the groupid (newGroup._id) of the newly created group
Make a callback if all this is successfully executed
The problem here is that the updating of all the user documents of the users added to the group happens asynchronously, but I want to be sure that all the user documents are updated correctly before I return a success or failure callback. How can I update all the user documents before I continue with the rest of the code (maybe not using a foreach)? Is my initial approach of retrieving all the user documents good or are there better ways to do this?
So the bottom line question is; how can I save multiple user documents and continue with the rest of the code (send a callback to notify success or failure) after all the save actions are performed, or is there a way to save all the documents at once?
NB The reason why I want (some) of the same information in both the user and the group document is because I don't want to load all the group info for a user if he logs in, only the basic group information. See also this source under the section many-to-many relationships.
JohnnyHK pointed me in the right direction; async.each make it possible to iterate over the documents and update them one at a time. After that the rest of the code gets executed. This is how my code looks now:
async.each(groupMembersDocs, function (newMember, loopCallback) {
//console.log('Processing user ' + newMember.email + '\n');
userModel.update({
email: newMember.email
}, {
$push: { 'groups' : {_id: newGroup._id, name: newGroup.name} }
}, function (err, data) {
if(err){
console.log("error: "+err);
loopCallback('Failure.. :'+err);
} else{
newGroup.users.push({_id: newMember._id, rank: -1});
loopCallback();
}
});
}, function (err){
if(err){
console.log("error: "+err);
callback(500, {success: false, response: "Error while adding users to group"});
} else{
newGroup.save(function (err) {
callback(201, {success: true, response: "Group created successfully"});
});
}
})

Skip or Disable validation for mongoose model save() call

I'm looking to create a new Document that is saved to the MongoDB regardless of if it is valid. I just want to temporarily skip mongoose validation upon the model save call.
In my case of CSV import, some required fields are not included in the CSV file, especially the reference fields to the other document. Then, the mongoose validation required check is not passed for the following example:
var product = mongoose.model("Product", Schema({
name: {
type: String,
required: true
},
price: {
type: Number,
required: true,
default: 0
},
supplier: {
type: Schema.Types.ObjectId,
ref: "Supplier",
required: true,
default: {}
}
}));
var data = {
name: 'Test',
price: 99
}; // this may be array of documents either
product(data).save(function(err) {
if (err) throw err;
});
Is it possible to let Mongoose know to not execute validation in the save() call?
[Edit]
I alternatively tried Model.create(), but it invokes the validation process too.
This is supported since v4.4.2:
doc.save({ validateBeforeSave: false });
Though there may be a way to disable validation that I am not aware of one of your options is to use methods that do not use middleware (and hence no validation). One of these is insert which accesses the Mongo driver directly.
Product.collection.insert({
item: "ABC1",
details: {
model: "14Q3",
manufacturer: "XYZ Company"
},
}, function(err, doc) {
console.log(err);
console.log(doc);
});
You can have multiple models that use the same collection, so create a second model without the required field constraints for use with CSV import:
var rawProduct = mongoose.model("RawProduct", Schema({
name: String,
price: Number
}), 'products');
The third parameter to model provides an explicit collection name, allowing you to have this model also use the products collection.
I was able to ignore validation and preserve the middleware behavior by replacing the validate method:
schema.method('saveWithoutValidation', function(next) {
var defaultValidate = this.validate;
this.validate = function(next) {next();};
var self = this;
this.save(function(err, doc, numberAffected) {
self.validate = defaultValidate;
next(err, doc, numberAffected);
});
});
I've tested it only with mongoose 3.8.23
schema config validateBeforeSave=false
use validate methed
// define
var GiftSchema = new mongoose.Schema({
name: {type: String, required: true},
image: {type: String}
},{validateBeforeSave:false});
// use
var it new Gift({...});
it.validate(function(err){
if (err) next(err)
else it.save(function (err, model) {
...
});
})

MeanJS MongoDb insert issue

I'm creating an application using MEANJS. I've a mongoose schema defined like this:
var UserdetailSchema = new Schema({
fullName: {
type: String,
trim: true
},
userName: {
type: String,
trim: true
},
mobile: {
type: String,
default: '',
trim: true
},
address: {
type: String,
trim: true
},
suburb: {
type: String
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Userdetail', UserdetailSchema);
What I'm trying to achieve is after login user is redirected to edit view to update rest of the info like "mobile, suburb, address" etc which is in userdetails schema.
This is my controller. I've changed default create() method to the following: I'm redirecting to the edit it as soon as the first step of inserting is complete.
// Create new Userdetail for current user
function create(FullName,UserName) {
// Create new Userdetail object
var userdetail = new Userdetails ({
fullName: FullName,
userName: UserName
});
// Redirect after save
userdetail.$save(function(response) {
$location.path('userdetails/' + response._id+'/edit');
console.log(response._id);
// Clear form fields
//$scope.name = '';
}, function(errorResponse) {
console.log(errorResponse.data.message);
$scope.error = errorResponse.data.message;
});
};
To create a user details I'm only inserting fullName, and userName as a first step and updating it later.
issue is, it is only allowing me 1 userdetails to insert and if I try to insert another userdetails of another user. it gives an error "Name already exists", though there is no name in the schema.
Server side code to create userdetails
/**
* Create a Userdetail
*/
exports.create = function(req, res) {
var userdetail = new Userdetail(req.body);
userdetail.user = req.user;
userdetail.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(userdetail);
}
});
};
I got it working after droping my collection "userdetails" from shell and trying inserting again. I followed this link Mongodb- Add unique index on an existing collection . It was more of MongoDB issue.

Resources