mongoose.js: _id of embedded document - node.js

I am trying to save a task to a list of tasks with mongoose and MongoDB. I want to save it redundantly in the tasks collection and in the corresponding list document as embedded document.
It works fine but one little thing: The list's embedded documents don't have their objectIds. But I need them in order to connect them logically with the documents in the tasks-collection.
My Schemas:
var TaskSchema = new Schema({
_id: ObjectId,
title: String,
list: ObjectId
});
var Task = mongoose.model('task', TaskSchema);
var ListSchema = new Schema({
_id: ObjectId,
title: String,
tasks: [Task.schema]
});
var List = mongoose.model('list', ListSchema);
My controller/router:
app.post('/lists/:list_id/tasks', function(req, res) {
var listId = req.params.list_id;
// 1. Save the new task into the tasks-collection.
var newTask = new Task();
newTask.title = req.body.title;
newTask.list = listId;
console.log('TaskId:' + newTask._id); // Returns undefined on the console!!!
newTask.save(); // Works fine!
// 2. Add the new task to the coresponding list.
list.findById(listId, function(err, doc){
doc.tasks.push(newTask);
doc.save(); // Saves the new task in the list but WITHOUT its objectId
});
res.redirect('/lists/' + listId)
});
Can I use mongoose a different way to achieve that? Or do I have to save the task and then query it before saving it in the list?
Thank you for advise :-)

I solved it by using an awesome feature called populate!
Also dtryon was right: You don't need to declare your _id ObjectIds in your models, they are getting added anyways. And you have to nest this stuff because your task is being saved asyncronously so you must get sure that runs before the other stuff.
Here is the solution:
Schemas:
var TaskSchema = new Schema({
title: String,
list: { type: Schema.ObjectId, ref: 'list' }
});
var Task = mongoose.model('task', TaskSchema);
var ListSchema = new Schema({
title: String,
tasks: [{ type: Schema.ObjectId, ref: 'task' }]
});
var List = mongoose.model('list', ListSchema);
Controller/router:
app.post('/lists/:list_id/tasks', function(req, res) {
var listId = req.params.list_id;
var newTask = new Task();
newTask.title = req.body.title;
newTask.list = listId;
// WHEN SAVING, WRAP THE REST OF THE CODE
newTask.save(function (err){
if (err) {
console.log('error saving new task');
console.log(err);
} else {
console.log('new task saved successfully');
list.findById(listId), function(err, doc){
doc.tasks.push(newTask);
doc.save(function (err){
if (err) {
console.log('error adding new task to list');
console.log(err);
} else {
console.log('new task saved successfully');
res.redirect('/lists/' + listId);
}
});
});
});
});
});
Now it references correctly and with the populate feature you can access the entries very comfortably. Works like charm :-)

I think this may have to do with async. You may not be seeing the ObjectId because you are calling list.findById potentially before you are pushing the newTask. Further, you will get into trouble doing a res.redirect outside of the async operation as well.
Try this:
app.post('/lists/:list_id/tasks', function(req, res) {
var listId = req.params.list_id;
// 1. Save the new task into the tasks-collection.
var newTask = new Task();
newTask.title = req.body.title;
newTask.list = listId;
console.log('TaskId:' + newTask._id); // Returns undefined on the console!!!
// WHEN SAVING, WRAP THE REST OF THE CODE
newTask.save(function (err){
if (err) {
console.log(err);
// do something
}
// 2. Add the new task to the coresponding list.
list.findById(listId, function(err, doc){
doc.tasks.push(newTask);
doc.save(); // Saves the new task in the list but WITHOUT its objectId
// REDIRECT AFTER SUCCESS
res.redirect('/lists/' + listId)
});
});
});

Related

Can't Update Data in MongoDB using Mongoose

These are my Schemas
const dataSchema = new mongoose.Schema({
email:String,
date:String,
amount:Number
})
const userSchema = new mongoose.Schema({
email: String,
password: String,
data:[{
type: mongoose.Schema.Types.ObjectId,
ref: "UserData",
}],
})
const User = new mongoose.model("User", userSchema);
const UserData = new mongoose.model("UserData", dataSchema);
I wish to update Data whenever a user post it. If the Userdata on the particular date already exists i wish to update it by adding the prev amount and new amount
app.post("/insert",(req,res)=>{
const username = req.cookies.username;
const Date = now.toLocaleDateString("en-Uk");
const Amount = req.body.value;
function createNewData(){
const userData = new UserData({
email:username,
date:Date,
amount: Amount
});
userData.save((err)=>{
if(err){
console.log(err);
}else{
console.log('newdatasaved');
res.redirect("/")
}
});
User.findOneAndUpdate({email:username},{$push:{data:userData._id}},(err)=>{
if(err){
console.log('cant push');
}else{
console.log('pushed data');
}
});
}
UserData.findOne({email:username,date:Date},(err,found)=>{
if(err){
createNewData();
console.log('cant find on particular date new created');
}else{
if(found){
let a = Number(found.amount);
let b = Number(Amount)+a;
UserData.findOneAndUpdate({email:username,date:Date},{$set:{amount:b}});
console.log('updated in existing');
res.redirect("/");
}
}
})
})
But it seems the data is always zero in database
See the amount section it is still zero.
Can anyone tell me what am i doing wrong. I have used set method but new data is unable to be posted.
You have to add callback to your update function.
UserData.findOneAndUpdate(
{email:username,date:Date},
{$set:{amount:b}},
function (error, success){}
)
If you don't want to use callback, then you have to use .exec()
UserData.findOneAndUpdate(
{email:username,date:Date},
{$set:{amount:b}},
).exec()
did you check the value of amount? Check the amount value in console.

Mongoose saving for populate

I'm new to Mongoose and Nodejs developement in general and I've got a bit of confusion around how to properly set up saving my records. Here are my two schemas:
Download
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var downloadSchema = Schema({
title : String,
description : String,
_project : { type: Schema.Types.ObjectId, ref: 'Project' }
});
module.exports = mongoose.model('Download', downloadSchema);
Project
...
var projectSchema = Schema({
name : String,
url : String,
pwd : String,
_downloads : [{type: Schema.Types.ObjectId, ref: 'Download' }]
});
module.exports = mongoose.model('Project', projectSchema);
This appears to be working correctly. The documentation explains my use-case of saving a download and linking a project, but I'm not sure how to properly populate the Project._downloads. Here's what I've done:
Express route handler:
function createDownload(req, res) {
// the Project Id is passed in the req.body as ._project
var dldata = req.body;
Project.findOne({ _id : dldata._project }, function(err, project) {
var dload = new Download(dldata);
dload.save( function (err, download) {
project._downloads.push(download._id);
project.save( function(err){
var msg = {};
if(err) {
msg.status = 'error';
msg.text = err;
}else {
msg.status = 'success';
msg.text = 'Download created successfully!';
}
res.json(msg);
});
});
});
}
This seems overcomplicated to me. Am I supposed to be manually pushing to the ._downloads array, or is that something Mongoose is supposed to handle internally based on the schema? Is there a better way to achieve it so that I can do:
Download.find().populate('_project').exec( ...
as well as:
Project.findOne({_id : _projectId}).populate('_downloads').exec( ...
According to the mongoose docs there are 2 ways to add subdocs to the parent object:
1) by using the push() method
2) by using the create() method
So I think that your code can be a bit simplified by eliminating the operation of saving a new Download item:
function createDownload(req, res) {
var dldata = req.body;
Project.findOne({ _id : dldata._project }, function(err, project) {
// handle error
project._downloads.push(dldata);
project.save(function(err) {
// handle the result
});
});
}
or
function createDownload(req, res) {
var dldata = req.body;
Project.findOne({ _id : dldata._project }, function(err, project) {
// handle error
project._downloads.create(dldata);
project.save(function(err) {
// handle the result
});
});
}

Initializing a ref array in Mongoose

Say I have the following schemas:
var promoGroupSchema = new Schema({
title: String,
offers: [{Schema.Types.ObjectId, ref: 'Offer']
});
and
var offerSchema = new Schema({
type: String
});
How do you initialize a promoGroup with new offers? The following won't work since save() is asynchronous. Now, I know I could put a function as a parameter of the save function, but that gets ugly with more offers.
var offer1 = new offerSchema({
type: "free bananas!"
});
var offer2 = new offerSchema({
type: "free apples!"
});
offer1.save();
offer2.save();
var newPromoGroup = new promoGroupSchema({
title: "Some title here",
offers: [offer1._id, offer2._id]
});
From what I read, Mongoose gives the object an _id as soon as you create them, can I rely on those?
You should access _id in the save callback. If you have a lot of offers to group, using a library like async will make your life easier.
var myOffers = [...]; // An array with offers you want to group together
// Array of functions you want async to execute
var saves = myOffers.map(function(offer) {
return function(callback) {
offer.save(callback);
}
}
// Run maximum 5 save operations in parallel
async.parallelLimit(saves, 5, function(err, res) {
if(err) {
console.log('One of the saves produced an error:', err);
}
else {
console.log('All saves succeeded');
var newPromoGroup = new promoGroupSchema({
title: "Some title here",
offers: _.pluck(myOffers, '_id') // pluck: see underscore library
});
}
});
You could also try to use Promises.

How do you get a grunt task to save a new simple mongodb document?

I've got a problem with using mongoose and custom grunt tasks together. All i want to do is make a task that behaves like a simple put request, by taking the parameters I give the task in the command line and processing/saving them to the database. However, when I expect to find it in the DB after adding it.. I can't find it anywhere.
The goal is to create a new company and save 4 simple parameters save from a "grunt addcompany:a:b:c:d" command.
Here is the "company" model (I've kept it very basic):
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var Company = new Schema({
name: String,
email: String,
info: String,
also: String
});
module.exports = mongoose.model('Companies', Company);
This is at the top of my Gruntfile.js:
var schemaCompany = require('./models/company'),
mongoose = require('mongoose'),
db = mongoose.connection,
Company = mongoose.model('Companies', schemaCompany);
This is the task:
grunt.registerTask('addcompany', 'add a company', function(n,e,i,a) {
var done = this.async();
mongoose.connect('mongodb://localhost/app-test');
db.on('open', function () {
var co = new Company({
name: n,
email: e,
info: i,
also: a
});
co.save(function (err) {
if (err) return handleError(err);
console.log('success!');
});
console.log(co);
db.close();
});
});
When I type this in the CLI:
grunt addcompany:name:email:description:more_stuff
The CLI returns with:
Running "addcompany:name:email:description:more_stuff" (addcompany) task
{
"name": "name",
"email": "email",
"info": "description",
"also": "more_stuff",
"_id" : ObjectID(" ~object id here~ "),
"__v" : 0
}
Although it creates an Object ID, it never saves anywhere. Nothing is showing up in the companies collection in the app-test db. What am I missing?
Thank you for your help!
Try this:
db.on('open', function () {
var co = new Company({
name: n,
email: e,
info: i,
also: a
});
co.save(function (err) {
// log the doc
console.log(co);
// log the error
console.log(err);
if (err) return handleError(err);
console.log('success!');
// close the database after 'co' is saved.
db.close();
done(true);
});
});

Automatically remove referencing objects on deletion in MongoDB

Let's suppose I have a schema like this:
var Person = new Schema({
name: String
});
var Assignment = new Schema({
name: String,
person: ObjectID
});
If I delete a person, there can still be orphaned assignments left that reference a person that does not exist, which creates extraneous clutter in the database.
Is there a simple way to ensure that when a person is deleted, all corresponding references to that person will also be deleted?
You can add your own 'remove' Mongoose middleware on the Person schema to remove that person from all other documents that reference it. In your middleware function, this is the Person document that's being removed.
Person.pre('remove', function(next) {
// Remove all the assignment docs that reference the removed person.
this.model('Assignment').remove({ person: this._id }, next);
});
If by "simple" you mean "built-in", then no. MongoDB is not a relational database after all. You need to implement your own cleaning mechanism.
The remove() method is deprecated.
So using 'remove' in your Mongoose middleware is probably not best practice anymore.
Mongoose has created updates to provide hooks for deleteMany() and deleteOne().
You can those instead.
Person.pre('deleteMany', function(next) {
var person = this;
person.model('Assignment').deleteOne({ person: person._id }, next);
});
In case if anyone looking for the pre hook but for deleteOne and deleteMany functions this is a solution that works for me:
const mongoose = require('mongoose');
...
const PersonSchema = new mongoose.Schema({
name: {type: String},
assignments: [{type: mongoose.Schema.Types.ObjectId, ref: 'Assignment'}]
});
mongoose.model('Person', PersonSchema);
....
const AssignmentSchema = new mongoose.Schema({
name: {type: String},
person: {type: mongoose.Schema.Types.ObjectId, ref: 'Person'}
});
mongoose.model('Assignment', AssignmentSchema)
...
PersonSchema.pre('deleteOne', function (next) {
const personId = this.getQuery()["_id"];
mongoose.model("Assignment").deleteMany({'person': personId}, function (err, result) {
if (err) {
console.log(`[error] ${err}`);
next(err);
} else {
console.log('success');
next();
}
});
});
Invoking deleteOne function somewhere in service:
try {
const deleted = await Person.deleteOne({_id: id});
} catch(e) {
console.error(`[error] ${e}`);
throw Error('Error occurred while deleting Person');
}
You can leave the document as is, even when the referenced person document is deleted. Mongodb clears references which point to non-existing documents, this doesn't happen immediately after deleting the referenced document. Instead, when you perform action on the document, e.g., update. Moreover, even if you query the database before the references are cleared, the return is empty, instead of null value.
Second option is to use $unset operator as shown below.
{ $unset: { person: "<person id>"} }
Note the use of person id to represent the value of the reference in the query.
you can use soft delete. Do not delete person from Person Collection instead use isDelete boolean flag to true.
Use $pull. Suppose you have a structure like this.
Stuff Collection:
_id: ObjectId('63dd23c633c17a718c4c5db7')
item: "Item 1"
user: ObjectID('63de669153bc12ecb9081b9e')
User collection:
_id: ObjectId('63de669153bc12ecb9081b9e')
stuff: array[ObjectId('63dd23c633c17a718c4c5db7'), ObjectId('63de3a69715ec134e161b0ea')]
Then after you remove the stuff:
const stuff = Stuff.findById(req.params.id)
const user = User.findById(req.params.id)
await stuff.remove()
// here you can use $pull to update
await user.updateOne({
$pull: {
stuff: stuff.id
}
})
you can simply call the model that needs to be deleted and delete that document like this:
PS: This answer is not specific to the question schema.
const Profiles = require('./profile');
userModal.pre('deleteOne', function (next) {
const userId = this.getQuery()['_id'];
try {
Profiles.deleteOne({ user: userId }, next);
} catch (err) {
next(err);
}
});
// in user delete route
exports.deleteParticularUser = async (req, res, next) => {
try {
await User.deleteOne({
_id: req.params.id,
});
return res.status(200).json('user deleted');
} catch (error) {
console.log(`error`, error);
return next(error);
}
};

Resources