Mongoose saving for populate - node.js

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

Related

insert and insertOne not a function and update not creating mongo ID

I thought I could read my way to this solution, but I cant see what im doing wrong.
Here is my model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var inspectSchema = new Schema({
_id: Object, // Mongo ID
property: String, // Property ID
room: String, // The room Name
item: Array // The Items text
});
module.exports = mongoose.model('inspectModel', inspectSchema, 'inspect');
And here is where I try to insert or insertOne
var inspectModel = require('../../models/inspectModel');
var inspectTable = mongoose.model('inspectModel');
inspectTable.insert(
{
"property" : inspectRecord.property,
"room" : inspectRecord.room,
"item" : inspectRecord.item
},
function (err, res) {
if (err) { return reject({err:true, err:"addInspect ERROR" + err}) }
else {
show("=====RESOLVE addInspect=====")
return resolve();
}
})
I tried
inspectTable.insert
inspectModel.insert
inspectTable.insertOne
inspectModel.insertOne
No matter what I always get
TypeError: inspectTable.insert is not a function
I also tried just update with { upsert: true } but then the mongo ID becomes null.
Any ideas?
The method you're looking for is create:
inspectTable.create(
{
"property" : inspectRecord.property,
"room" : inspectRecord.room,
"item" : inspectRecord.item
}, ...
However, your schema definition of _id: Object is likely wrong. Just leave any definition of _id out of your schema and it will use the default ObjectId, which is likely what you want.
You can try this
var insert_table = new inspectTable(
{
"property" : inspectRecord.property,
"room" : inspectRecord.room,
"item" : inspectRecord.item
});
insert_table.save(function (err, res) {
if (err) { return reject({err:true, err:"addInspect ERROR" + err}) }
else {
show("=====RESOLVE addInspect=====")
return resolve();
}
});

Mongoose - Can't push a subdocument into an array in parent Document

I am trying to push a subdocument(ApplicationSchema) into my Job schema. But it doesn't seem to work.
Following is my Job Schema :
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
var ApplicationSchema = require('./Application');
const Job = new Schema({
skills : {
type : Array
},
active : {
type : Boolean,
default : false
},
applications: [ApplicationSchema],
userId : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
},{timestamps : true});
export default mongoose.model("Job", Job)
This is subdocument(ApplicationSchema). I have 5 more subdocuments in this schema.
I am pushing an object with a key-value pair of talentId and its value. But it doesn't work.
I get a new object in the array but the object I'm trying to push is not pushed.
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
var notesSchema = require('./notesSchema');
var documentSchema = require('./documentSchema');
var assessmentSchema = require('./assessmentSchema');
var interviewScheduleSchema = require('./interviewScheduleSchema');
var referenceSchema = require('./referenceSchema')
const ApplicationSchema = new Schema({
talentId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Talent'
},
applicationType: {
type: Number
}
notes: [notesSchema],
documents: [documentSchema],
assessment: [assessmentSchema],
interviewSchedule: [interviewScheduleSchema],
references: [referenceSchema]
},{
timestamps: true
});
export default ApplicationSchema;
Following is my code in the API endpoint
.post((req, res, next) => {
Job.findById(req.params.jobId)
.then((job) => {
if (job != null) {
job.applications.push(req.body);
job.save()
.then((job) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(job);
})
}
else {
err = new Error('Job ' + req.params.jobId + 'not found')
err.status = 404;
return next(err);
}
}, (err) => next(err))
.catch((err) => next(err));
})
req.body contains following object
{ talentId: '5a813e1eb936ab308c4cae51' }
If you already have the id of the job document then you can push application object direct by doing the following:
Job.update(
{ _id: req.params.jobId },
{ $push: { applications: req.body} },
callback
);
or you can use promise to handle this. and if you are only saving id of the application then you may want to change your job schema to store Id of the applications instead of whole application schema.
Please read the documentation carefully as this is very basic update query.
You have,
talentId: {type: mongoose.Schema.Types.ObjectId,
ref: 'Talent'}
But your req.body contains:
{ talentId: '5a813e1eb936ab308c4cae51' }
It should be:
{ talentId: mongoose.Types.ObjectId('5a813e1eb936ab308c4cae51') }
Turns out there was nothing wrong with code.
I was using import and export default syntax which didn't seem work well with this.
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
and
export default ApplicationSchema;
I replaced them with Common JS syntax and everything worked fine.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
and
module.exports = ApplicationSchema;
I did this for Job document file and every subdocument file and the code worked.

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

TypeError: Cannot read property 'push' of undefined Mongoose

I am getting the below error which doing a app in Node.js using express.
I am using Mongoose for my DB operations below i have detailed my design
Party.js
var mongoose = require("mongoose");
var partySchema = new mongoose.Schema({
partyName: String,
songs: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Song"
}
]
});
module.exports = mongoose.model("Party", partySchema);
Song.js
var mongoose = require("mongoose");
var songsSchema = new mongoose.Schema({
videoId: String,
videoName: String
});
module.exports = mongoose.model("Song", songsSchema);
My App.js
app.post("/search/addSong", function(req, res) {
Party.find({partyName:"hello"},function(err,party){
if(err){
console.log("Failed in find");
} else {
console.log(party);
// console.log(typeof(req.body.videoId));
var videoId = req.body.videoId;
var newSong = [ {
videoId:req.body.videoId,
videoName:req.body.videoName
}
];
Song.create(newSong, function(err, createdSong){
if(err){
console.log("Error creating a new party");
} else {
console.log(createdSong);
party.songs.push(createdSong);// ERROR ON THIS LINE
party.save();
res.redirect("/search");
}
});
}
});
res.render("addSong");
});
I am able to create collection objects of the Party and Song individually, when I add the song to the party queue, I get the following error:
TypeError: Cannot read property 'push' of undefined
Can anyone please let me know what I a missing in here..!!
Thanks in advance.!!
find returns a cursor which is converted in an array. if you are expecting a single doc, call findOne.
app.post("/search/addSong", function(req, res) {
Party.findOne({partyName:"hello"},function(err,party){
if(err){
console.log("Failed in find");
} else {
console.log(party);
// console.log(typeof(req.body.videoId));
var videoId = req.body.videoId;
var newSong = [ {
videoId:req.body.videoId,
videoName:req.body.videoName
}
];
Song.create(newSong, function(err, createdSong){
if(err){
console.log("Error creating a new party");
} else {
console.log(createdSong);
party.songs.push(createdSong);// ERROR ON THIS LINE
party.save(function(err){
res.redirect("/search");
});
}
});
}
});
res.render("addSong");
});
Also put redirect in the callback of save, it will make sure that redirect is called only when save is completed, otherwise it would redirect without saving, giving the asynchronous nature of javascript.

Mongoose sub Docs CastError

I am new to MongoDB and MongooseJS. I'm also new to nodeJs.
I have an Angularjs project using Typescript. This project work with a "container" json, which itself contains some properties and a testList, which is a json object containing some properties and a fileList, containing an itemList.
So it's like this :
export class Container{
data:string="";
testList:Test[];
}
export class Test {
moredata:string="";
fileList:File[];
}
export class File {...}
etc.
I send this JSON to my nodejs server. I'm using bodyparser to get the json from the req.body object.
Server side, my mongoose Schema are exactly like my angularjs classes, so they look like this :
/*************** mongoose schemas **************/
// item.js
var mongoose = require('mongoose');
module.exports = mongoose.model('Item', {
Content : {type : Object, default: ''}
});
// file.js
var mongoose = require('mongoose');
var Item = require('./item');
var schema = new mongoose.Schema({
Data : {type : String, default: ''},
ItemList: {type: [Item], default:[]}
});
// test.js
var mongoose = require('mongoose');
var File = require('./file');
var schema = new mongoose.Schema({
Data: {type:String},
FileList: {type:[File], default:[]}
});
// container.js
var mongoose = require('mongoose');
var Test = require('./test');
var schema = new mongoose.Schema({
Name : {type : String, default: '', index:true, unique:true, required: true, dropDups:true},
Test : {type:[Test], default:[]}
});
If I try to create a new Container object (Mongoose object) and assign it the json from req.body, it bugs : CastError.
If I re-create each sub document from JSON and save the main doc, it bugs too : CastError.
I don't know how to achieve this. It worked before but my mongoose schema where using [mongoose.Schema.Types.Mixed] type for sub docs, and not the "real" types. Fact is, with Mixed I had no _id on sub docs, which I want.
Using real sub docs types, I can see in logs that the _id is created, but all the lists are empty.
Here is my nodejs code :
/*************** nodejs **************/
app.post('/api/container', bodyParser.urlencoded({extended:true}), bodyParser({limit:'50mb'}), bodyParser.json(), function(req, res) {
var test = req.body._test;
var testList = [];
var fileList;
var itemList;
var itemObj;
var fileObj;
var testObj;
for(var i in test){
fileList = [];
for(var j in test[i]._fileList){
itemList = [];
for(var k in test[i]._fileList[j]._itemList){
itemObj = new Item({
Content : test[i]._fileList[j]._itemList[k]._content
});
itemList.push(itemObj);
console.log('item pushed : ' + itemObj + ' and length : ' + itemList.length);
// logs gives info OK.
}
fileObj = new File({
Data: locales[i]._fileList[j]._data,
ItemList: itemList
});
fileList.push(fileObj);
console.log('file pushed : ' + fileObj);
// logs gives info NOK. The ItemList is empty : [], instead of a 70+ item list.
}
testObj = new Test({
Data: locales[i]._data,
FileList: fileList
});
testList.push(testObj);
console.log('test pushed : ' + i);
// once again, logs are NOK : the FileList is empty : []
}
// use mongoose to save the project in the database
new Container({
Name : req.body._name.toLowerCase().trim(),
Test: testList
}).save(function(err, container, count){
if(err){
console.log('erreur : ');
console.log(err);
// we enter here as we have this error :
/*
{ [CastError: Cast to undefined failed for value "
{
_id: 5727ebf95a76ff0011374928,
FileList: [],
Data: 'data'
},
{
_id: 5727ebf95a76ff0011374970,
FileList: [],
Data: 'other data'
}" at path "Test"]
message: 'Cast to undefined failed for value "
{ _id: 5727ebf95a76ff0011374928,\n FileList: [],\n Data: \'data\' },
{ _id: 5727ebf95a76ff0011374970,\n FileList: [],\n Data: \'other data\'}"
at path "Test"',
name: 'CastError',
type: undefined,
value: [{"_id":"5727ebf95a76ff0011374928","FileList":[],"Data":"data"},{"_id":"5727ebf95a76ff0
011374970","FileList":[],"Data":"other data"}],
path: 'Test' }
*/
res.status(403).json({error: 'error'});
} else {
console.log('saved ! ');
res.json(container);
}
});
});
I'm not used to post here, I'm more a reader :) Anyway if my post is not appropriated please inform me and I'll move / edit it correctly.
Thanks for your time.
Checked, and working !
So my error was to use the models of my objects in mongoose schema instead of their Schema.
I'm now exporting both models and schema on each object, and using schema for schema definitions, and models for requests. Finally rid of this bug ! :)
/* Mongoose object definitions */
// grab the mongoose module
var mongoose = require('mongoose');
var child = require('./child').schema; // getting the child schema
// define our parent model
var parentSchema = new mongoose.Schema({
Name : {type : String, default: ''},
ChildrenList: {type: [Child], default:[]}
});
var parentModel = mongoose.model('Parent', parentSchema);
// module.exports allows us to pass this to other files when it is called
module.exports = {
model: parentModel, // exporting model for requests
schema: parentSchema // exporting schema for others schema using "Parent"
};
And the code used for requests :
var Child = require('./models/child').model; // here using model
var Parent = require('./models/parent').model; // here using model
new Parent({
Name : req.body._name.toLowerCase().trim(),
ChildrenList : childrenList // variable defined somewhere
}).save(function(err, parent, count){
if(err){
res.status(403).json({error: 'too bad'});
} else {
res.json(parent);
}
});

Resources