Overwrite object in mongodb - mongodb

This should be simple but it is surprisingly difficult and extremely frustrating. I am trying to overwrite an 'Object' field in mongodb with a new Object that the user creates in my client webpage. I have validated that all other fields I am passing to the update operation are in fact being updated, with the exception of the javascript object. Instead of it updating with the object I am passing (While I validated is being populated with the object I am passing through), it just updates it back to {} instead of whats being passed:
{ nodes:[ { w: 120, h: 80,type: 'InHive',left: 184,top: 90,text: 'item',query: 'hey',name: 'sample',id: '7686132d-6fcf-4a3b-baa2-b1c628e0b2d6' } ], edges: [], ports: [],groups: [] }
When I attempt to update the data field outside of the meteor method, directly from the mongo console interface, it overwrites that field successfully with the javascript object. What am I doing wrong here, because I cant for the life of me figure this one out?
Server Method
'updateOneWorkflow': function(id, field, object) {
this.unblock;
if (Meteor.userId()) {
var _username = Meteor.user().username;
MYCOLLECTION.update({
_id: id
}, {
$set: {
[field]: object, //this just gets reset back to {} whenever this update method is called
"metadata.last_modified_dt": new Date(), //this gets updated
"metadata.modified_by": Meteor.userId(), //this gets updated
'metadata.modified_by_username': _username //This gets updated
}
});
} else {
throw new Meteor.Error(403, "You are not authorized to perform this function");
}
}
Client Call:
var _jsonformat = toolkit.exportData();
var currentid = Session.get('rulesRowClicked')._id;
console.log(_jsonformat);
Meteor.call('updateOneWorkflow' , currentid, 'data', _jsonformat, function(err, res){
if(err){
toastr.error('Failed to save result ' + err);
}
else{
toastr.success('Saved workflow');
}
});

I believe your problem is stemming from this line: [field]: object. I don't believe that's a proper method of dynamically accessing an object's field. Instead, try to dynamically update the field as so:
'updateOneWorkflow': function(id, field, object) {
this.unblock;
if (Meteor.userId()) {
var _username = Meteor.user().username;
var newObj = {
"metadata": {
"last_modified_dt": new Date(),
"modified_by": Meteor.userId(),
"modified_by_username": _username
}
};
newObj[field] = object;
MYCOLLECTION.update({
_id: id
}, {
$set: newObj
});
} else {
throw new Meteor.Error(403, "You are not authorized to perform this function");
}
}

The issue was crazier than I expected. If you are using Meteorjs and you are using the Aldeed Schema 2 collection framework, it seems to completely ignore updates/inserts of json objects even if you set the field type to Object, unless you set up the exact same schema as the object (including nested array objects) and attach it to your collection. Dumbest thing Ive ever seen, no idea why nothing warns you of this. I removed the schema attachment and it worked.

Related

Mongo `pre` hook not firing as expected on `save()` operation

I am using pre and post hooks in my MongoDB/Node backend in order to compare a pre-save and post-save version of a document so I can generate notes via model triggers based on what's changed. In one of my models/collections this is working, but in another, it's not working as expected, and I'm not sure why.
In the problem case, some research has determined that even though I am calling a pre hook trigger on an operation that uses a save(), when I console out the doc state passed in that pre hook, it's already had the change applied. In other words, the hook is not firing before the save() operation, but after, from what I can tell.
Here is my relevant model code:
let Schema = mongoose
.Schema(CustomerSchema, {
timestamps: true
})
.pre("save", function(next) {
const doc = this;
console.log("doc in .pre: ", doc); // this should be the pre-save version of the doc, but it is the post-save version
console.log("doc.history.length in model doc: ", doc.history.length);
trigger.preSave(doc);
next();
})
.post("save", function(doc) {
trigger.postSave(doc);
})
.post("update", function(doc) {
trigger.postSave(doc);
});
module.exports = mongoose.model("Customer", Schema);
The relevant part of the save() operation that I'm doing looks like this (all I'm doing is pushing a new element to an array on the doc called "history"):
exports.updateHistory = async function(req, res) {
let request = new CentralReqController(
req,
res,
{
// Allowed Parameters
id: {
type: String
},
stageId: {
type: String
},
startedBy: {
type: String
}
},
[
// Required Parameters
"id",
"stageId",
"startedBy"
]
);
let newHistoryObj = {
stageId: request.parameters.stageId,
startDate: new Date(),
startedBy: request.parameters.startedBy,
completed: false
};
let customerToUpdate = await Customer.findOne({
_id: request.parameters.id
}).exec();
let historyArray = await customerToUpdate.history;
console.log("historyArray.length before push in update func: ", historyArray.length);
historyArray.push(newHistoryObj);
await customerToUpdate.save((err, doc) => {
if (doc) console.log("history update saved...");
if (err) return request.sendError("Customer history update failed.", err);
});
};
So, my question is, if a pre hook on a save() operation is supposed to fire BEFORE the save() happens, why does the document I look at via my console.log show a document that's already had the save() operation done on it?
You are a bit mistaken on what the pre/post 'save' hooks are doing. In pre/post hook terms, save is the actual save operation to the database. That being said, the this you have in the pre('save') hook, is the object you called .save() on, not the updated object from the database. For example:
let myCustomer = req.body.customer; // some customer object
// Update the customer object
myCustomer.name = 'Updated Name';
// Save the customer
myCustomer.save();
We just updated the customers name. When the .save() is called, it triggers the hooks, like you stated above. Only the difference is, the this in the pre('save') hook is the same object as myCustomer, not the updated object from the database. On the contrary, the doc object in the `post('save') hook IS the updated object from the database.
Schema.pre('save', function(next) {
console.log(this); // Modified object (myCustomer), not from DB
)};
Schema.post('save', function(doc) {
console.log(doc); // Modified object DIRECTLY from DB
});

how to set a trigger to add to a date in meteor using quickform

I have MemberProfiles and MemberPayments collections.The MemberProfile has expiryDate field which is set to current date at insert.I need to extend expirDate of a unique MemberProfile whenever a MemberPayment is added to that MemberProfile.
MemberProfiles = new Mongo.Collection('memberProfiles');
MemberProfileSchema = new SimpleSchema({
expiryDate: {
type: Date,
autoValue: function () {
return moment().toDate();
},
autoform: {
type: "hidden"
}
}
// to insert into Memb erProfiles
{{> quickForm collection="MemberProfiles" id="insertMemberProfileForm" type="insert" class="new-recipe-form"}}
//the code for MemberPayments collection
MemberPayments = new Mongo.Collection('memberPayments');
MemberPayments.before.insert(function (userId, doc) {
let memberProfile= MemberProfiles.direct.findOne({profile: doc.memberId});
MemberProfiles.update(doc.memberId, {
$set: {
expiryDate: moment().add(31, 'days');
,
}
}
)
});
I have added all the necessary packages but still this doesnt work.I am getting error Cannot set property 'expiryDate' of undefined
It is challenging to try and resolve issues like this without having a more complete example of the app or reference to the complete project in github or somewhere else.
However, when I read through your code I noticed an issue in your MemberProfiles.update() function. I also noticed that it appears you are only processing your form from the client side (e.g. because your quickform is not using a Meteor Method) so you will have to manually call the SimpleSchema .clean() method to generate your autovalue. Keep in mind that your client side approach might work ok now, but once you remove the insecure package you will either have to implement a Meteor Method to perform the insert or configure your collection allow/deny rules to allow client side insert (this is dangerous).
Since you are using moment.js you need to be careful that you always pull the date from the moment object before storing in mongodb. In this case, you are trying to set expiryDate to the value returned from moment().add(31, 'days') which is just another moment object.
Also, I would assume you want to add 31 days to the current value of expiryDate, however you are never initializing moment with the expiryDate. Therefore, you will always be setting the expiryDate to 31 days from the time the function executes.
Lastly, you have a syntax error (; inside your $set object) and your findOne selector includes {profile: doc.memberId} however your MemberProfiles schema says there is only a _id and expiryDate field in your collection.
Try this new logic that addresses the above issues and see if that resolves your issue.
MemberPayments.before.insert(function (userId, doc) {
let memberProfile = MemberProfiles.direct.findOne({profile: doc.memberId});
if (memberProfile) {
if (!memberProfile.expiryDate) {
console.log("expiryDate was not previously set!");
} else {
MemberProfiles.update({profile: doc.memberId}, {
$set: {
expiryDate: moment(memberProfile.expiryDate).add(31, 'days').toDate()
}
});
}
} else {
console.log("memberProfile not found");
}
});
Now that this is fixed, you need to resolve the issue of your autovalue not being generated on the client side. You do this by calling the SimpleSchema .clean() method. Since you are not using Meteor Methods to process your quickForm (and therefore doing everything client side), you need to add the below AutoForm hook to ensure that the SimpleSchema .clean() method is called before the doc is saved (which will then execute your autovalue logic).
AutoForm.hooks({
insertMemberProfileForm: {
before: {
insert: function(doc) {
MemberProfileSchema.simpleSchema().clean(doc);
return doc;
}
}
}
});
You should put the above code in the onRendered() callback of the template that creates your quickform (e.g. the template that contains the below code in the HTML).
{{> quickForm collection="MemberProfiles" id="insertMemberProfileForm" type="insert" class="new-recipe-form"}}

How to find a document from mongodb using mongoose based on query and then insert some data to it and save back?

I have my Model as
var Model = {"name":String,"email":String,"notes":[{"time":Date,"title":String,"description":String}]
And I want to find document based on the email, and then add a note to the array. And then save it back.
What I tried is,
var updatedNote = {};
Model.findOne({'email':'test#test.com'},function(err, note){
for(var property in note._doc){
if(note._doc.hasOwnProperty(property)){
updatedNote[property] = note._doc[property];
};
}
updatedNote.notes.push(newNote);
note._doc = updatedNote;
note.save(function(err){
if(err){
console.log(error);
}
else {
res.redirect('/notes');
}
})
});
But it is throwing error as "Object does not have save method". I don't want to use findByIdAndUpdate() as i am leaving this responsibility of generating id on mongo.
I don't understand what most of that code is doing. If I wanted to add a note to the document (I'm assuming newNote is defined elsewhere), I'd just do:
Model.findOne({'email':'test#test.com'},function(err, note){
note.notes.push(newNote);
note.save(function(err){});
});

how to tell when mongoosejs findOneAndUpdate method succeeds?

How can I tell when findOneAndUpdate successfully updates a document? huh variable always returns the same thing (whether id is in the database or not) and doc is always null.
var query = {id : id };
var huh = schemaModel.findOneAndUpdate(query, obj, function(doc) {
console.log(doc);
if(doc) {
callback(doc);
} else {
errback('');
}
}
);
console.log(huh);
You are only passing one parameter to the callback in your findOneAndUpdate query.
I think that your query succeeds, but doc will always come null when you successfully update the object as it is the first parameter which is the err.
Also, I do not see the code for your callback function, so I am just presuming that it can be accessed in the scope of your function.
var query = {id : id };
var huh = schemaModel.findOneAndUpdate(query, obj, function(err, doc) {
if(err) {
return "Error spotted!";
} else {
return "Found & Updated";
}
}
);
console.log(huh);
By returning those values, you are basically assigning them to the huh variable and it should log accordingly. It serves as a logging mechanism.

getting values from WriteResult mongo

I was trying to get familiar with the WriteResult object in mongo, but I can't access any of its values. The docs say the number of values inserted is stored in WriteResult.nInserted. Trying to access nInserted is crashing my server.
var readings = new Readings({
val1: parseInt(Data[0]),
val2: parseInt(Data[1]),
val3: parseInt(Data[2]),
val4: parseInt(Data[3]),
val5: parseInt(Data[4]),
val6: parseInt(Data[5]),
})
var result = readings.save(function (err, post){
if(err){return next(err)}
res.status(201).json(readings)
})
if(result.nInserted > 0){
console.log('wrote to database')
}
else{
console.log('could not write to database')
}
I know the data is being written to the database. I see it in the mongo shell.
The save method on a model instance doesn't return anything. All results are reported via the callback method, so you'd use something like this:
readings.save(function (err, doc, numberAffected){
if(err){return next(err)}
if (numberAffected > 0) {
console.log('updated an existing doc');
} else {
console.log('added a new doc');
}
res.status(201).json(doc)
})
Mongoose doesn't give you access to the full WriteResult, but as long as err is null you can rest assured the save succeeded and it's only a matter of whether an existing doc was updated or a new one was added. Because you're creating a new doc here, numberAffected will always be 0.