mongoose findOne() call not saving and no error in output - javascript

I'm doing a simple findOne() and saving the doc within it, but for some reason it's not working. I've outputted the object and the output in console is correct, but after the save(), I take a look at my mongodb and it didn't save. I'm not sure if there is some sort of option I'm supposed to set. Here is my code:
var1 = "data1";
var2 = "data1field1";
Model.findOne({'_id':some_id}).exec(function(err, doc) {
if (err) return console.error(err);
(doc.data[var1][var2][0] += 1;
console.log(doc.data.data1);
doc.save(function (err) {
if(err){console.log(err);}
console.log('success');
});
});
Here is my schema:
var modelSchema = new mongoose.Schema({
'data':{
'data1':{
'data1field1':[{type: Number}],
'data1field2':[{type: Number}]
},
'data2':{
'data2field1':[{type: Number}],
'data2field2':[{type: Number}]
}
}
});
var Model = mongoose.model('model', modelSchema);
module.exports.Model = Model;
Say I create an instance of this schema where data.data1.data1field1 is an array of two numbers [0,0], the output for "console.log(doc.data.data1);" would be:
{
data1field1:[1,0],
data1field2:[0,0] }
success
But the save does not happen. I'm new to mongoose/mongodb so there is probably a simple fundamental thing I'm missing.
NOTE: I cannot use Model.update({},{$inc {}}) because I'm using variables to select which data object to change, and because of the literals in .update(), it is impossible.
Thanks.

So mongoose has some smarts to automatically detect changes when you do simple property sets like myModel.name = 'Steve'. However, when you access deeply nested schemas and change properties deep in the graph, the top level document cannot auto-detect this. So you need to tell mongoose what you changed.
doc.markModified('data.' + var1 + '.' + var2);
If you get the correct incantation of that, mongoose will be able to save your change.

Related

Mongoose Update document

I am using Node.js and mongoose Module, I am looking for a way to count how many documents a user has and then if they have more then 0 documents it would edit their existing document and add the input so at the end it would have the previous text + the text that the user sent, so far this is how much I gotten.
const List = require('../Models/list.js')
List.countDocuments({}, function(err, count) {
if(count>0){
//edit document
}
else if(count=0){
const input = List.create({
User: User.name,
Songlist: args[0],
})
}
})
console.log('done')
here is how I think the code would look like
List.update(User.name) => update Songlist into List.Songlist + '|' + args[0]
I have never seen an update method like that. I am a nodejs developer. well, maybe there's a way like that.
Here's how I do to update a document
await Product.findByIdAndUpdate(id, //here where I have written "id" you have to write the id of the document you want to update.
{ //here in this object you have to put the variables of updated values
title: title,
description:description,
product_id:product_id,
category,
price,
});
there is also another method
await Product.findOneAndUpdate(name: 'asim', //let's suppose
{ //updated values
title:title,product: product
})
you can also read the documentation here https://mongoosejs.com/docs/tutorials/findoneandupdate.html

Firebase - Update specific fields simultaneously without replacing all of the data

I would like to be able to publish simultaneously in two directories of my Firebase database. I created a function for this, according to the example proposed in the "Update specific fields" section of the Firebase Javascript documentation:
function linkTwoUsers(user1, user2) {
// The two users are "connected".
var user1Data = {
userLink: user2
};
var user2Data = {
userLink: user1
};
var updates = {};
updates["/users/" + user1] = user1Data;
updates["/users/" + user2] = user2Data;
return database
.ref()
.update(updates)
.then(() => {
return res.status(200).end();
})
.catch(error => {
return res.status(500).send("Error: " + error.message);
});
}
The problem is that when I run the function, instead of uploading the directories, it replaces all the data present in it.
Here are the user directories before the function:
And then:
How do we make sure the data doesn't overwrite the others? Thank you for your help.
Try to narrow your path to just the property you are trying to update:
updates["/users/" + user1 + "/userLink/"] = user1;
updates["/users/" + user2 + "/userLink/"] = user2;
It seems as though you're creating an entirely new object when you set:
var userData = { someThing: stuff }
When you pass that in, it will override the original object. One way you might solve this (there might be a more efficient way) is to grab the objects from Firebase, add the new property and value to the object, then send the entire object back into Firebase.
In some javascript frameworks, you should be able to use the spread operator to set all of an object's props to another object like this:
var newObject = { ...originalObject }
newObject.userData = "something"
// then save newObject to firebase

Sequelize: can you use hooks to add a comment to a query?

Heroku recently posted a list of some good tips for postgres. I was most intreged by the Track the Source of Your Queries section. I was curious if this was something that's possible to use with Sequelize. I know that sequelize has hooks, but wasn't sure if hooks could be used to make actual query string adjustments.
I'm curious if it's possible to use a hook or another Sequelize method to append a comment to Sequelize query (without using .raw) to keep track of where the query was called from.
(Appending and prepending to queries would also be helpful for implementing row-level security, specifically set role / reset role)
Edit: Would it be possible to use sequelize.fn() for this?
If you want to just insert a "tag" into the SQL query you could use Sequelize.literal() to pass a literal string to the query generator. Adding this to options.attributes.include will add it, however it will also need an alias so you would have to pass some kind of value as well.
Model.findById(id, {
attributes: {
include: [
[Sequelize.literal('/* your comment */ 1'), 'an_alias'],
],
},
});
This would produce SQL along the lines of
SELECT `model`.`id`, /* your comment */ 1 as `an_alias`
FROM `model` as `model`
WHERE `model`.`id` = ???
I played around with automating this a bit and it probably goes beyond the scope of this answer, but you could modify the Sequelize.Model.prototype before you create a connection using new Sequelize() to tweak the handling of the methods. You would need to do this for all the methods you want to "tag".
// alias findById() so we can call it once we fiddle with the input
Sequelize.Model.prototype.findById_untagged = Sequelize.Model.prototype.findById;
// override the findbyId() method so we can intercept the options.
Sequelize.Model.prototype.findById = function findById(id, options) {
// get the caller somehow (I was having trouble accessing the call stack properly)
const caller = ???;
// you need to make sure it's defined and you aren't overriding settings, etc
options.attributes.include.push([Sequelize.literal('/* your comment */ 1'), 'an_alias']);
// pass it off to the aliased method to continue as normal
return this.findById_untagged(id, options);
}
// create the connection
const connection = new Sequelize(...);
Note: it may not be possible to do this automagically as Sequelize has use strict so the arguments.caller and arguments.callee properties are not accessible.
2nd Note: if you don't care about modifying the Sequelize.Model prototypes you can also abstract your calls to the Sequelize methods and tweak the options there.
function Wrapper(model) {
return {
findById(id, options) {
// do your stuff
return model.findById(id, options);
},
};
}
Wrapper(Model).findById(id, options);
3rd Note: You can also submit a pull request to add this functionality to Sequelize under a new option value, like options.comment, which is added at the end of the query.
This overrides the sequelize.query() method that's internally used by Sequelize for all queries to add a comment showing the location of the query in the code. It also adds the stack trace to errors thrown.
const excludeLineTexts = ['node_modules', 'internal/process', ' anonymous ', 'runMicrotasks', 'Promise.'];
// overwrite the query() method that Sequelize uses internally for all queries so the error shows where in the code the query is from
sequelize.query = function () {
let stack;
const getStack = () => {
if (!stack) {
const o = {};
Error.captureStackTrace(o, sequelize.query);
stack = o.stack;
}
return stack;
};
const lines = getStack().split(/\n/g).slice(1);
const line = lines.find((l) => !excludeLineTexts.some((t) => l.includes(t)));
if (line) {
const methodAndPath = line.replace(/(\s+at (async )?|[^a-z0-9.:/\\\-_ ]|:\d+\)?$)/gi, '');
if (methodAndPath) {
const comment = `/* ${methodAndPath} */`;
if (arguments[0]?.query) {
arguments[0].query = `${comment} ${arguments[0].query}`;
} else {
arguments[0] = `${comment} ${arguments[0]}`;
}
}
}
return Sequelize.prototype.query.apply(this, arguments).catch((err) => {
err.fullStack = getStack();
throw err;
});
};

What are MongoDB Modifiers and Operators?

I am working on a Meteor application and one of the features I'm building is a form that inserts a new document into an array (inserts a shipping address to a user's profile where a user can have multiple addresses). The error I keep getting is:
Exception while invoking method 'addAddress' Error: When the modifier option is true, validation object must have at least one operator
I have been unsuccessfully trying to figure out the answer on Stackoverflow, Github, etc. but could not find a solution. I now want to take the approach of understanding exactly what the error means - so my question is what exactly are modifier options and operators in MongoDB? From what I understand, modifiers provide constraints on what type of data is returned from a query, and operators are used to modify data. Are these definitions correct?
Does anyone know what the error I'm getting might mean? Here is my sample code:
My click event to capture data on a form and call a method to add an address:
Template.editAddress.events({
'click .addAddress': function(e, tmpl) {
e.preventDefault();
var currentUserId = Meteor.userId();
console.log(currentUserId);
var addressDetails = {
address: {
streetAddress: $('#streetAddress').val()
}
};
console.log(addressDetails);
Meteor.call('addAddress', addressDetails, currentUserId, function(error) {
if (error) {
alert(error.reason);
} else {
console.log('success!');
Router.go('Admin');
}
});
}
});
My method to insert the address:
Meteor.methods({
'addAddress': function(addressDetails, currUserId) {
var currentUserId = currUserId;
console.log('user to add address to is ' + currUserId);
Meteor.users.update(currentUserId, {$addToSet:
{
'address.streetAddress': addressDetails.streetAddress
}
});
}
});
Note that when I type that query in the console, it works:
db.users.update({_id: 'Qdf89k3fd93jfdk'}, {$addToSet: {'address.streetAddress': '12345 fake st'}});
Thank you in advance!
Your addressDetails object doesn't have a field streetAddress, so addressDetails.streetAddress returns undefined. Use addressDetails.address.streetAddress instead in the update. And also, like Joshua pointed out, use an object as selector with { _id: currUserId }. So the whole function should be:
Meteor.users.update( { _id: currentUserId }, {$addToSet:
{
'address.streetAddress': addressDetails.address.streetAddress
}
});
}
One more thing, you should not pass the userId from the client. Any method you define is callable from the client and like that, I would be able to call your method 'addAddress' from the browser console with any userId to update their address. Instead, use the this.userId object in method calls (see here) and check that it is not null, i.e. user is logged in.
if (! this.userId)
throw new Meteor.Error(401, "You must be logged in!");
var currentUserId = this.userId;
It looks like you're passing in the document id directly into the MongoDB query method, rather than constructing an object with an _id property with a value of said document id.
i.e try
var currentUserId = { _id: currUserId };

Mongoose - RangeError: Maximum Call Stack Size Exceeded

I am trying to bulk insert documents into MongoDB (so bypassing Mongoose and using the native driver instead as Mongoose doesn't support bulk insert of an array of documents). The reason I'm doing this is to improve the speed of writing.
I am receiving the error "RangeError: Maximum Call Stack Size Exceeded" at console.log(err) in the code below:
function _fillResponses(globalSurvey, optionsToSelectRegular, optionsToSelectPiped, responseIds, callback) {
Response.find({'_id': {$in: responseIds}}).exec(function(err, responses) {
if (err) { return callback(err); }
if (globalSurvey.questions.length) {
responses.forEach(function(response) {
console.log("Filling response: " + response._id);
response.answers = [];
globalAnswers = {};
globalSurvey.questions.forEach(function(question) {
ans = _getAnswer(question, optionsToSelectRegular, optionsToSelectPiped, response);
globalAnswers[question._id] = ans;
response.answers.push(ans);
});
});
Response.collection.insert(responses, function(err, responsesResult) {
console.log(err);
callback()
});
} else {
callback();
}
});
}
So similar to: https://stackoverflow.com/questions/24356859/mongoose-maximum-call-stack-size-exceeded
Perhaps it's something about the format of the responses array that Mongoose returns that means I can't directly insert using MongoDB natively? I've tried .toJSON() on each response but no luck.
I still get the error even with a very small amount of data but looping through and calling the Mongoose save on each document individually works fine.
EDIT: I think it is related to this issue: http://howtosjava.blogspot.com.au/2012/05/nodejs-mongoose-rangeerror-maximum-call.html
My schema for responses is:
var ResponseSchema = new Schema({
user: {
type: Schema.ObjectId,
ref: 'User'
},
randomUUID: String,
status: String,
submitted: Date,
initialEmailId: String,
survey: String,
answers: [AnswerSchema]
});
So, answers are a sub-document within responses. Not sure how to fix it though....
I was having this same issue and I started digging through the mongoose source code (version 3.8.14). Eventually it led me to this line within
mongoose/node_modules/mongodb/lib/mongodb/collection/core.js -> insert(...) -> insertWithWriteCommands(...) ->
mongoose/node_modules/mongodb/lib/mongodb/collection/batch/ordered.js -> bulk.insert(docs[i]) -> addToOperationsList(...) -> bson.calculateObjectSize(document, false);
var bsonSize = bson.calculateObjectSize(document, false);
Apparently, this calls BSON.calculateObjectSize, which calls calculateObjectSize which then infinitely recurses. I wasn't able to dig that far in to what caused it, but figured that it may have something to do with the mongoose wrapper binding functions to the Schema. Since I was inserting raw data into mongoDB, once I decided to change the bulk insert in mongoose to a standard javascript object, the problem went away and bulk inserts happened correctly. You might be able to do something similar.
Essentially, my code went from
//EDIT: mongoose.model needs lowercase 'm' for getter method
var myModel = mongoose.model('MyCollection');
var toInsert = myModel();
var array = [toInsert];
myModel.collection.insert(array, {}, function(err, docs) {});
to
//EDIT: mongoose.model needs lowercase 'm' for getter method
var myModel = mongoose.model('MyCollection');
var toInsert = { //stuff in here
name: 'john',
date: new Date()
};
var array = [toInsert];
myModel.collection.insert(array, {}, function(err, docs) {});
Confirmed, but not a bug. Model.collection.insert() bypasses Mongoose and so you're telling the node driver to insert an object that contains mongoose internals like $__, etc. The stack overflow is probably because bson is trying to compute the size of an object that references itself indirectly.
Long story short, use Document.toObject(), that's what its for: http://mongoosejs.com/docs/api.html#document_Document-toObject
Response.find({}).exec(function(err, responses) {
if (err) {
return callback(err);
}
if (true) {
var toInsert = [];
responses.forEach(function(response) {
console.log("Filling response: " + response._id);
response.answers = [];
[{ name: 'test' }].forEach(function(ans) {
response.answers.push(ans);
});
toInsert.push(response.toObject());
});
Response.collection.insert(toInsert, function(err, responsesResult) {
console.log(err);
});
} else {
callback();
}
});
Also, the code you specified won't work even if you fix the stack overflow. Since you're trying to insert() docs that are already in the database, all the inserts will fail because of _id conflicts. You'd really be much better off just using a stream() to read the results one at a time and then save() them back into the db.
guys! I've faced that weird error today. It happened because of I had a Schema with ref properties and tried to pass in create/update whole related document. I've changed argument to _id only and that did the trick. Works like a charm. I found the answer here (scroll down to February 21, 2013, 8:05 pm gustavohenke comment).
I have faced similar issue.
//manyvalues is array of objects
schema.methods.somemethod = function(manyvalues,callback) {
this.model(collection).collection.insertMany(manyvalues,callback);
}
But this caused error [RangeError: Maximum call stack size exceeded].
So I have created new model from manyvalues and used it as below and it worked.
schema.methods.somemethod = function(manyvalues,callback){
var list = JSON.parse(JSON.stringify(manyvalues));//created a new object.
this.model(collection).collection.insertMany(list,callback);
}
The problem may be caused if manyvalues is changed internally.
This also happens if there's a duplication of of the _id value. Most situations will be when you might create an new record from an existing record.
Deleting the _id and inserting the record and letting Mongoose/MongoDb take care of the creation of the id.
I had the same issue. Mongoose version is 5.13.14. My stack trace is:
RangeError: Maximum call stack size exceeded
at minimize (...\node_modules\mongoose\lib\document.js:3564:18)
at minimize (...\node_modules\mongoose\lib\document.js:3576:18)
at minimize (...\node_modules\mongoose\lib\document.js:3576:18)
at minimize (...\node_modules\mongoose\lib\document.js:3576:18)
at minimize (...\node_modules\mongoose\lib\document.js:3576:18)
at minimize (...\node_modules\mongoose\lib\document.js:3576:18)
at minimize (...\node_modules\mongoose\lib\document.js:3576:18)
I found 2 ways to fix the issue:
Using toObject() method:
const model = await MyModel.findOne(conditions);
return model?.toObject();
Using minimize: false in toJSON option of the schema:
export const MySchema = new Schema({
...
}, {
...
toJSON: {
getters: true,
// !!! HERE !!!
minimize: false,
},
...
});
Check for circular references in the responses object. I Faced a similar issue due to circular references.
I had a similar problem, it was that I was querying a field that didn't exist in the schema using the $ne(other query operators may have a similar problem)
var TestSchema = new Schema({
test:[]
});
...
models.Test.findOne({"test2": {$ne: "t"} })...
In the example above I am testing for test2 instead of test

Categories