NodeJS + Mongoose: Updating all fields on a Mongoose model - node.js

I'm building out an api using Node, MongoDB and Mongoose. One thing that is bugging me is that you can't seem to set multiple fields at once:
app.put('/record/:id', function(req, res) {
Record.findById(req.params.id, function(err, doc) {
if (!err) {
doc.update(req.params);
doc.save();
...
However, it seems that you have to work out the update query and run it on the Model object rather than on the document object. Unless you want to assign individual properties and run save() at the end.
Is there any way of accomplishing this without having to write a Mongo query?

jsaak's answer is good but doesn't work for nested objects. I elaborated on his answer by searching and setting nested objects.
I added these functions to a utility.js file
var _ = require('underscore');
exports.updateDocument = function(doc, SchemaTarget, data) {
for (var field in SchemaTarget.schema.paths) {
if ((field !== '_id') && (field !== '__v')) {
var newValue = getObjValue(field, data);
console.log('data[' + field + '] = ' + newValue);
if (newValue !== undefined) {
setObjValue(field, doc, newValue);
}
}
}
return doc;
};
function getObjValue(field, data) {
return _.reduce(field.split("."), function(obj, f) {
if(obj) return obj[f];
}, data);
}
function setObjValue(field, data, value) {
var fieldArr = field.split('.');
return _.reduce(fieldArr, function(o, f, i) {
if(i == fieldArr.length-1) {
o[f] = value;
} else {
if(!o[f]) o[f] = {};
}
return o[f];
}, data);
}
implement as:
var util = require('./utility');
app.put('/record/:id', function(req, res) {
Record.findById(req.params.id, function(err, doc) {
if (!err) {
utils.updateDocument(doc, Record, req.params);
doc.save();
...

Maybe this has changed since this question was first asked, but you can update multiple paths in Mongoose with the set method ike:
// object
doc.set({
path : value,
path2 : {
path : value
}
});
doc.save();
References
http://mongoosejs.com/docs/api.html#document_Document-set

direct updating is not recommended according to this document:
http://mongoosejs.com/docs/2.7.x/docs/updating-documents.html
i solved it like this:
Book.findOne({isbn: req.params.isbn}, function (err, book){
if (err) {
res.send(422,'update failed');
} else {
//update fields
for (var field in Book.schema.paths) {
if ((field !== '_id') && (field !== '__v')) {
if (req.body[field] !== undefined) {
book[field] = req.body[field];
}
}
}
book.save();
}
});

If you want to update the entire document , you can delete the document based on its id and store the entire object again.
That object must contain data for each and every fields of the mongo document.
Here is an example.
mongoDBCollectionObject.findOneAndRemove({ // -- it will delete the entire document
_id: req.body.fieldsdata._id // here fiedsdata is exact copy with modification of previous data
}, function(err, data) {
var newFieldsData = new mongoDBCollectionObject(fieldsdata); //-- fieldsdata updated data
newFieldsData.save(function(err, data) { // save document to that collection with updated data
if (err) {
console.log(err);
} else
res.json({
success: true
});
});
})

To clarify the question, it looks like you are taking the Request parameters and using those to find and update the given document.
Is there any way of accomplishing this without having to write a Mongo query?
The obvious answer is to update the Model object with the value from the Request. Which is what you suggest...
Unless you want to assign individual properties and run save() at the end.
But it seems like you don't want to do this? It sounds like you want to update the Model object directly from the Request object?
You can do this if you really want. You just loop through req.params and set the doc values where appropriate.
for(var i in req.params) {
if(req.params[i] != doc[i]){
doc[i] = req.params[i];
}
}
It should be as simple as this. However, you only want to do this if you have a whole bunch of validation code on the Model objects. The whole point to the Model is that you don't want to get random data in the DB. The line above will generically "set" the correct values, but you'll definitely need to include code for authentication, authorization and validation around that simple for loop.

try to updating the collection without the find, like this
Record.update({_id:req.params.id}, {$set: { field: request.field }}, {upsert: true}, function(err{...})
The option upsert create the document if not exist.

In case you have a new object and want to update whole object in the database, you can update multiple fields at once like this:
find the object
get all schema paths (fields)
save the new object.
SomeModel.findOne({ 'id': 'yourid' },function (err, oldObject) {
if (err) return handleError(err);
// get all schema paths (fields)
SomeModel.schema.eachPath(function(path) {
// leave __id and __v alone
if (path != '_id' && path != '__v') {
// update the data from new object
oldObject[path] = newObject[path];
}
})
oldObject.save(function(err) {
if (err)
console.log(err)
});
})

A neat and clean approach would be using async await and findOneAndRemove along with create Here is the sample code
try {
let resp = await this.findOneAndRemove({ _id: req.body._id });
let entry = await this.create(req.body);
} catch (err) {
}
Don't Forget to mark this whole function as async

Related

Retrieve data from MongoDB and save it to global object in Node.js and Express.js

I'm trying to get data from MongoDB collection and then save it to a global object.Later I need to parse it to HTML template.
Here is my code:
When user log onto his profile: then we need to get his projects and here we call findeprojects() function
usrRouter.route('/profile')
.all(function (req,res,next) {
if(!req.user){
res.redirect('/');
}
next();
})
.get(function (req,res,userObj) {
// var proj = findprojects();
userObj = req.user;
var pro = {};
pro = findprojects(userObj);
res.render('index',{name:userObj.username, email:userObj.email});
//res.sendFile('profile.html',{root:path.join(__dirname,'../public'),},{name:userObj.username});
});
Here is findeprojects function code:
var findprojects = function(obj) {
var usern = obj.username;
mongodb.connect(url,function(err, db){
if(err) throw err;
var collection = db.collection('projects');
//console.log(usern);
collection.find({'pusername':usern});
cursor =db.collection('projects').find({ 'pusername': usern }).toArray(function(err,items){
//console.log(items);
var i;
for(i=0; i<items.length;){
userProjects.createdBy = items[i].pusername;
userProjects.proName = items[i].projectName;
userProjects.proType = items[i].projectType;
userProjects.proDesc = items[i].projectDesc;
//return userProjects;
i = i+1;
}
});
console.log(userProjects);
});
};
I have declared global object at the top like:
userProjects = {
createdBy:'',
proName:'',
proType:'',
proDesc:''
};
But when I console userprojects object after calling the findeprojects() function it displays empty values.
why dont you use mongoose to model your stuff.
its more intuitive and you no need to declare the global object and do the mapping in the for loop that you are doing.
also your approach is a bit wrong in terms of when you iterate through for aren't you overwriting ?
say you have two documents where pusername is abdul.
so in your case you loose first object which will get overwritten by the second one.
i see that you commented out a return statement but even that wont work properly.
from a design point of view your approach is not efficient.
in mongoose you can do:
{
var userProjectSchema = new mongoose.Schema({
createdBy: { type: String }
, proName: String
, proType: String
, proDesc: String
});
// Find a single document by username.
userProjectSchema.findOne({ pusername : 'abdul' }, function(err, resDoc) {
if (err) return console.error(err);
// do your html stuff here
});
// Find all documents.
userProjectSchema.find(function(err, results) {
if (err) return console.error(err);
// do your html stuff here
});
}

mongoose & lodash: __.map returns array of nulls

We have a many-to-many partnership relationship in Mongoose. The object is suppoosed to iterate over all objects it references before saving. We're trying to use lodash to generate functions with the right context, but all we get is an array of null
Here's the code:
PartnerSchema.pre("save", function(done) {
var updaters = __.map(this.partner, function(partner) { // this.partner is an array of ObjectIds
// this runs, and the argument is fine
var update = function updateThing(cb) {
Company.findOne({_id: partner}, function(err, company) {
if (err) return cb(err);
company.partners = __.map(company.partners, function(partner) {
if (partner._id === this._id) {
return this;
}
else {
return partner;
}
})
company.save(function(err, rowsAffected) {
cb(err);
})
})
}
console.log(update); // [Function]
return update;
})
// updaters is now [null, null]
async.parrallel(updaters, function() {
done();
})
})
EDIT: we found out why no error was emitted. Mongoose ate them all!. Downgrade to 4.0.8 shows - at least - the error now. Undefined is not a function, just as expected.
var updaters = _.map([1,2,3], function(i) {
var update = function updateThing(j) { return console.log(i, j) };
console.log(update);
return update
})
that sample code working fine on lodash website
Try to check your this.partner argument and check that you not redefine your update and updaters variables anywhere in that module
If you had problems - just go from that sample code in new file and extend it so that you can find what's wrong

get Selected fields in .populate() waterline-postgresql .populate('fieldName',{select:[]})

select query is not working in .populate() of waterline-postgresql.
Model.find(query).populate(assoc.alias,{select:['field1','field2']});
This is not working in waterline-postgresql adapter.
Is this not supported or Am I making any mistake?
select is not supported in .populate(). You can see this github issue. In populate select is not working currently.
This is feature request and it is open issue. hope in next release
waterline team will introduce this feature.
Since there's no official way to do that I did some changes in order to have that. Maybe it's not the best way but it works.
User.find({ belongs_to: user.id })
.populate('person_id')
.exec(function findCB (err, usersFound)
{
console.log(usersFound[0].toJSON())
if (err)
{
return res.json(401, {error: err});
}
if (usersFound.length == 0)
{
return res.json(200, {message: 'No user asigned'});
}
else
{
// An array to store all the final user objects
var asignedArray = [];
for (index in usersFound)
{
var myObj = {},
key,
value;
// Object in format {key: value}
myObj['id'] = usersFound[index].id;
myObj['fullname'] = usersFound[index].person_id.first_name + ' ' +
usersFound[index].person_id.second_name + ' ' +
usersFound[index].person_id.last_name;
myObj['email'] = usersFound[index].email;
myObj['job'] = usersFound[index].person_id.job;
myObj['permission_level'] = usersFound[index].permission_level;
// Adding the object to the main array
asignedArray.push(myObj);
}
return res.json(200, {
users: asignedArray
});
}
});
I hope it could be useful for you.

Document update error while inserting views dynamically in cloudant couchdb

I am using a nosql database(cloudant-couchdb) for the first time and it has been going good so far. But I am stuck with these two problems:
I am trying to add views dynamically. I am able to add the first view, but I get errors while trying to insert more views stating Document update conflict. How would it be an 'update' since I am inserting a new view everytime and not updating it?
Is it possible to pass a parameter in a map function? Something like - if(doc.name == someVariable)?
Here is my code below:
app.put("/listSections", function(req, res) {
var module = req.body.name;
var obj = {};
obj[module] = { "map": function (doc) {
if (doc.name == "Developer") {
//emit something
}
}
});
db.insert({views: obj},
'_design/section', function (error, response) {
if (error) {
console.log("error: " + error);
}
console.log("module: " + module );
}
);
My approach was wrong. I just found creating multiple views is a bad practice. So I created one (general) view, and passed queries as parameters that answer my second question (how to pass dynamic parameters). Here is what I did:
app.get("/listSections", function(req, res) {
var moduleId = req.query.id;
db.view('section','getSections', { key: moduleId }, function(err, body)
{
//store values
}
}
Here I pass the moduleId dynamically to my map function that sets the key as moduleId.
Also, I found this link to be pretty useful for couchdb queries - http://sitr.us/2009/06/30/database-queries-the-couchdb-way.html

Retrieving all records asynchronously then performing an action

In a node.js server, using the mongodb native driver, I want to retrieve records from a cursor and then output them as JSON. I have this (simplified)
var ans = {ids: []};
cursor.each(function(err, doc) {
if (doc) {
ans.ids.push(doc.tag);
}
});
cursor.count(function(err, result) {
ans.count = result;
res.send(JSON.stringify(ans));
});
and the result is something like {ids:[], count: 3}. In other words the query appears to run without returning any records. I assume that this is because the data's already been sent before the cursor.each callbacks have run. How do I re-structure this to make sure the sending happens after the iterating?
I have found the answer. The example for cursor.each says "If the item is null then the cursor is exhausted/empty and closed", so: (error handling omitted)
var ans = {ids: []};
cursor.each(function(err, doc) {
if (doc) {
ans.ids.push(doc.tag);
}
else {
cursor.count(function(err, result) {
ans.count = result;
res.send(JSON.stringify(ans));
});
}
});

Resources